Source Code
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x8994aa44aa75fac76cad94102df7554e5b2b49eb
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0xA709252c...Be1AB3028 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
MorphoVaultStrategyImpl
Compiler Version
v0.8.30+commit.73712a01
Optimization Enabled:
Yes with 10 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { BasePhiEthStrategy } from "../BasePhiEthStrategy.sol";
import { IPhiEth } from "../../interfaces/IPhiEth.sol";
import { IMorphoVault } from "../../interfaces/morpho/IMorphoVault.sol";
import { IUniversalRewardsDistributor } from "../../interfaces/morpho/IUniversalRewardsDistributor.sol";
import { IMorphoVaultStrategy } from "../../interfaces/morpho/IMorphoVaultStrategy.sol";
import { IWETH } from "../../interfaces/IWETH.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title MorphoVaultStrategyImpl
* @notice PhiEth strategy implementation for Morpho Vault protocol
* @dev Deposits ETH into Morpho Vault to earn yield following ERC4626 standard
*/
contract MorphoVaultStrategyImpl is BasePhiEthStrategy, IMorphoVaultStrategy {
using SafeERC20 for IERC20;
// ============ Constants ============
/// @notice Morpho Vault (Moonwell Flagship ETH) on Base
address private constant MORPHO_VAULT = 0xa0E430870c4604CcfC7B38Ca7845B1FF653D0ff1;
// ============ Immutable Variables ============
/// @notice Morpho Vault contract (ERC4626)
IMorphoVault private immutable morphoVault;
/// @notice WETH contract
IWETH private immutable weth;
// ============ Constructor ============
/**
* @notice Constructor sets up the immutable Morpho contracts
*/
constructor() {
morphoVault = IMorphoVault(MORPHO_VAULT);
weth = IWETH(WETH_ADDRESS);
// Verify that the vault's underlying asset is WETH
// For base, Morpho Vault is expected to be WETH-based
// if (morphoVault.asset() != WETH_ADDRESS) revert VaultAssetIsNotWETH();
}
// ============ Strategy Initialization ============
function name() external pure returns (string memory) {
return "Morpho";
}
/**
* @notice Strategy-specific initialization
* @dev No additional setup required for Morpho Vault
*/
function _initializeStrategy() internal override {
// No additional setup required for Morpho Vault strategy
// The vault interface is standard ERC4626
}
// ============ Core Strategy Functions ============
/**
* @notice Deposits incoming ETH into Morpho Vault following ERC4626 standard
* @dev Uses deposit() function - "Asset-First Approach"
*/
function convertETHToLST() external payable override isPhiEth whenInitialized {
if (msg.value == 0) revert AmountMustBeGreaterThanZero();
// Convert ETH to WETH
weth.deposit{ value: msg.value }();
// Approve WETH for Morpho Vault (ERC4626 requires approval)
weth.approve(address(morphoVault), msg.value);
// Deposit WETH to Morpho Vault using ERC4626 deposit function
// deposit(assets, receiver) returns shares received
uint256 sharesReceived = morphoVault.deposit(msg.value, address(this));
emit ETHDeposited(msg.value, sharesReceived);
}
/**
* @notice Withdraws ETH from Morpho Vault and sends to recipient
* @param amount Amount of ETH to withdraw
* @param recipient Address to receive the ETH
*/
function withdrawETH(uint256 amount, address recipient) external override isPhiEth whenInitialized {
_validateWithdrawal(amount, recipient);
_withdrawFromMorpho(amount, recipient);
}
/**
* @notice Returns the balance in ETH terms (converted from vault shares)
* @return The balance in ETH terms using ERC4626 convertToAssets function
*/
function balanceInETH() external view override returns (uint256) {
uint256 vaultShares = morphoVault.balanceOf(address(this));
return morphoVault.convertToAssets(vaultShares);
}
/**
* @notice Claims rewards and sends directly to yield receiver
* @param rewardClaimData Array of reward claim data from Morpho API
* @return rewardTokens Array of reward token addresses
* @return amounts Array of actual amounts claimed
* @dev Only callable by PhiEth creator
* @dev Requires reward data from Morpho API: https://rewards.morpho.org/v1/users/{address}/distributions
* @dev NOTE: This assumes URD.claim() sends to msg.sender. If it can send to arbitrary address,
* we can optimize to send directly to yieldReceiver
* @notice WELL token on Base WELL_TOKEN = 0xA88594D404727625A9437C3f886C7643872296AE;
* @notice MORPHO token on Base MORPHO_TOKEN = 0xBAa5CC21fd487B8Fcc2F632f3F4E8D37262a0842;
*/
function claimRewards(IMorphoVaultStrategy.RewardClaimData[] calldata rewardClaimData)
external
onlyPhiEthOwner
whenInitialized
returns (address[] memory rewardTokens, uint256[] memory amounts)
{
if (rewardClaimData.length == 0) revert NoRewardDataProvided();
// Get yield receiver from PhiEth
address yieldReceiver = IPhiEth(phiEth).yieldReceiver();
rewardTokens = new address[](rewardClaimData.length);
amounts = new uint256[](rewardClaimData.length);
for (uint256 i = 0; i < rewardClaimData.length; i++) {
IMorphoVaultStrategy.RewardClaimData memory claimData = rewardClaimData[i];
if (claimData.reward == address(morphoVault)) {
revert CannotClaimVaultShares();
}
// Validate distributor address
if (claimData.distributor == address(0)) {
revert InvalidDistributorAddress();
}
// Get URD contract
IUniversalRewardsDistributor urd = IUniversalRewardsDistributor(claimData.distributor);
uint256 balanceBefore = IERC20(claimData.reward).balanceOf(address(this));
// For now, assuming it claims to this contract:
uint256 claimedAmount = urd.claim(
address(this), // account (this contract)
claimData.reward, // reward token address
claimData.claimable, // total claimable amount from API
claimData.proof // merkle proof from API
);
uint256 balanceAfter = IERC20(claimData.reward).balanceOf(address(this));
uint256 actualReceived = balanceAfter - balanceBefore;
// Transfer claimed rewards to yield receiver
if (actualReceived > 0) {
SafeERC20.safeTransfer(IERC20(claimData.reward), yieldReceiver, actualReceived);
}
rewardTokens[i] = claimData.reward;
amounts[i] = claimedAmount;
}
emit RewardsClaimed(rewardTokens, amounts);
}
// ============ Internal Functions ============
/**
* @notice Internal function to withdraw from Morpho Vault
* @param amount Amount of ETH to withdraw
* @param receiver Address to receive ETH
*/
function _withdrawFromMorpho(uint256 amount, address receiver) internal {
// Withdraw WETH from Morpho Vault using ERC4626 withdraw function
// withdraw(assets, receiver, owner) returns shares burned
uint256 sharesBurned = morphoVault.withdraw(amount, address(this), address(this));
// Convert WETH to ETH and send to receiver
weth.withdraw(amount);
_safeTransferETH(receiver, amount);
emit ETHWithdrawn(sharesBurned, amount, receiver);
}
/**
* @notice Override internal withdrawal function for efficiency
* @param amount Amount to withdraw
* @param receiver Address to receive the withdrawal
*/
function _withdrawFromStrategy(uint256 amount, address receiver) internal override {
_withdrawFromMorpho(amount, receiver);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IBasePhiEthStrategy } from "../interfaces/IBasePhiEthStrategy.sol";
import { IMerklDistributor } from "../interfaces/IMerklDistributor.sol";
import { IPhiEth } from "../interfaces/IPhiEth.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title BasePhiEthStrategy
* @notice Abstract base contract for all PhiEth strategies
* @dev Provides common functionality and enforces interface compliance
*/
abstract contract BasePhiEthStrategy is IBasePhiEthStrategy {
// ============ Constants ============
/// @notice Base network WETH address
address internal constant WETH_ADDRESS = 0x4200000000000000000000000000000000000006;
// ============ State Variables ============
/// @notice The PhiEth token contract address
address public override phiEth;
/// @notice Whether the strategy is currently unwinding
bool public override isUnwinding;
/// @notice Initialization status
bool internal initialized;
// ============ Modifiers ============
/**
* @notice Restricts function access to the PhiEth token contract
*/
modifier isPhiEth() {
if (msg.sender != phiEth) revert CallerIsNotPhiEth();
_;
}
/**
* @notice Restricts function access to the PhiEth creator/owner
*/
modifier onlyPhiEthOwner() {
if (msg.sender != IPhiEth(phiEth).getCreator()) revert CallerIsNotPhiEthOwner();
_;
}
/**
* @notice Ensures the strategy is initialized
*/
modifier whenInitialized() {
if (!initialized) revert NotInitialized();
_;
}
// ============ Initialization ============
function isInitialized() external view override returns (bool) {
return initialized;
}
/**
* @notice Initializes the strategy with the PhiEth token address
* @param _phiEth The address of the PhiEth token
* @dev This function must be called after deployment and can only be called once
*/
function initialize(address _phiEth) external virtual {
if (initialized) revert AlreadyInitialized();
if (_phiEth == address(0)) revert InvalidPhiEthAddress();
phiEth = _phiEth;
initialized = true;
// Register Merkl operator
address merklDistributor = IPhiEth(phiEth).merklDistributor();
address phiEthOwner = IPhiEth(phiEth).getCreator();
(bool success,) = merklDistributor.call(
abi.encodeWithSignature("toggleOperator(address,address)", address(this), phiEthOwner)
);
if (!success) revert FailedToRegisterOperator();
emit MerklOperatorRegistered(phiEthOwner);
// Call strategy-specific initialization
_initializeStrategy();
emit StrategyInitialized(_phiEth);
}
/**
* @notice Strategy-specific initialization logic
* @dev Override this function in derived contracts for custom initialization
*/
function _initializeStrategy() internal virtual { }
// ============ Administrative Functions ============
/**
* @notice Sets whether the strategy is currently unwinding
* @param isUnwinding_ New unwinding status
*/
function setIsUnwinding(bool isUnwinding_) external override onlyPhiEthOwner whenInitialized {
isUnwinding = isUnwinding_;
emit UnwindingStatusChanged(isUnwinding_);
}
/**
* @notice Allows the owner to unwind the strategy in small amounts into ETH
* @param ethAmount Amount of ETH to unwind to
*/
function unwindToETH(uint256 ethAmount) external override onlyPhiEthOwner whenInitialized {
if (ethAmount == 0) revert AmountMustBeGreaterThanZero();
_withdrawFromStrategy(ethAmount, phiEth);
}
/**
* @notice Toggle this contract's operator status on Merkl
* @dev Operator is always the PhiEth creator
* @dev Only callable by PhiEth owner
*/
function toggleMerklOperator() external onlyPhiEthOwner {
address merklDistributor = IPhiEth(phiEth).merklDistributor();
if (merklDistributor == address(0)) revert MerklDistributorNotSet();
address phiEthOwner = IPhiEth(phiEth).getCreator();
(bool success,) = merklDistributor.call(
abi.encodeWithSignature("toggleOperator(address,address)", address(this), phiEthOwner)
);
if (!success) revert FailedToRegisterOperator();
emit MerklOperatorRegistered(phiEthOwner);
}
/**
* @notice Claims Merkl rewards for this contract
* @param tokens Array of token addresses being claimed
* @param amounts Array of amounts to claim (from Merkl API)
* @param proofs Array of Merkle proofs for each claim
* @dev Only callable by PhiEth owner (who is the operator)
*/
function claimMerklRewards(
address[] calldata tokens,
uint256[] calldata amounts,
bytes32[][] calldata proofs
) external onlyPhiEthOwner whenInitialized {
address merklDistributor = IPhiEth(phiEth).merklDistributor();
if (tokens.length != amounts.length || amounts.length != proofs.length) {
revert ArrayLengthMismatch();
}
// Prepare arrays for claim
uint256 length = tokens.length;
address[] memory users = new address[](length);
// All claims are for this contract
for (uint256 i = 0; i < length; i++) {
users[i] = address(this);
}
// Get current balances
uint256[] memory balancesBefore = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
balancesBefore[i] = IERC20(tokens[i]).balanceOf(address(this));
}
// Claim from Merkl distributor
IMerklDistributor(merklDistributor).claim(
users,
tokens,
amounts,
proofs
);
// Transfer all claimed tokens to yield receiver
address yieldReceiver = IPhiEth(phiEth).yieldReceiver();
for (uint256 i = 0; i < length; i++) {
uint256 balanceAfter = IERC20(tokens[i]).balanceOf(address(this));
uint256 claimedAmount = balanceAfter - balancesBefore[i];
if (claimedAmount > 0) {
_safeTransferToken(IERC20(tokens[i]), yieldReceiver, claimedAmount);
emit MerklRewardsClaimed(tokens[i], claimedAmount);
}
}
}
// ============ Abstract Functions (Must be implemented by derived contracts) ============
/**
* @notice Deposits incoming ETH into the underlying protocol
* @dev Must be implemented by each strategy
*/
function convertETHToLST() external payable virtual override;
/**
* @notice Withdraws ETH from the strategy and sends it to the recipient
* @param amount Amount of ETH to withdraw
* @param recipient Address to receive the ETH
* @dev Must be implemented by each strategy
*/
function withdrawETH(uint256 amount, address recipient) external virtual override;
/**
* @notice Returns the total ETH balance represented by the strategy's holdings
* @return The balance in ETH terms
* @dev Must be implemented by each strategy
*/
function balanceInETH() external view virtual override returns (uint256);
// ============ Internal Helper Functions ============
/**
* @notice Internal function to withdraw from the strategy
* @param amount Amount to withdraw
* @param receiver Address to receive the withdrawal
* @dev This should be implemented by each strategy to handle their specific withdrawal logic
*/
function _withdrawFromStrategy(uint256 amount, address receiver) internal virtual {
// Default implementation calls the public withdrawETH function
// Override this in derived contracts for more efficient internal calls
this.withdrawETH(amount, receiver);
}
/**
* @notice Validates basic withdrawal parameters
* @param amount Amount to withdraw
* @param recipient Recipient address
*/
function _validateWithdrawal(uint256 amount, address recipient) internal pure {
if (amount == 0) revert AmountMustBeGreaterThanZero();
if (recipient == address(0)) revert InvalidRecipientAddress();
}
/**
* @notice Safely transfers ETH to a recipient
* @param recipient The address to receive ETH
* @param amount The amount of ETH to transfer
*/
function _safeTransferETH(address recipient, uint256 amount) internal {
if (amount == 0) return;
(bool success,) = payable(recipient).call{ value: amount }("");
require(success, "ETH transfer failed");
}
/**
* @notice Safely transfers ERC20 tokens
* @param token The token contract
* @param recipient The address to receive tokens
* @param amount The amount of tokens to transfer
*/
function _safeTransferToken(IERC20 token, address recipient, uint256 amount) internal {
if (amount == 0) return;
SafeERC20.safeTransfer(token, recipient, amount);
}
// ============ Receive Function ============
/**
* @notice Allows the contract to receive ETH
* @dev This is needed for strategies that receive ETH from protocol withdrawals
*/
receive() external payable virtual {
// Allow receiving ETH from strategy operations
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IBasePhiEthStrategy } from "./IBasePhiEthStrategy.sol";
/**
* @title IPhiEth
* @notice Interface for the PhiEth token contract
* @dev Defines the complete PhiEth token interface including events and errors
*/
interface IPhiEth is IERC20 {
// ============ Events ============
/**
* @notice Emitted when rewards are claimed and distributed
* @param rewardToken Address of the reward token
* @param amount Amount of reward tokens claimed
* @param recipient Address that received the rewards
*/
event RewardsClaimed(address indexed rewardToken, uint256 amount, address indexed recipient);
/**
* @notice Emitted when yield is harvested and distributed
* @param protocolFee Amount sent to protocol fee recipient
* @param yieldAmount Amount sent to yield receiver
*/
event YieldHarvested(uint256 protocolFee, uint256 yieldAmount);
/**
* @notice Emitted when rebalance threshold is updated
* @param oldThreshold Previous threshold value
* @param newThreshold New threshold value
*/
event RebalanceThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
/**
* @notice Emitted when yield receiver is updated
* @param oldReceiver Previous yield receiver
* @param newReceiver New yield receiver
*/
event YieldReceiverUpdated(address indexed oldReceiver, address indexed newReceiver);
/**
* @notice Emitted when strategy is changed
* @param oldStrategy Previous strategy address
* @param newStrategy New strategy address
*/
event StrategyChanged(address indexed oldStrategy, address indexed newStrategy);
/**
* @notice Emitted when dust amount is ignored during rebalance
*/
event DustAmountIgnored(address indexed strategy, uint256 amount);
// ============ Errors ============
/**
* @notice Thrown when caller is not the creator
*/
error CallerNotCreator();
/**
* @notice Thrown when yield receiver is zero address
*/
error YieldReceiverIsZero();
/**
* @notice Thrown when rebalance threshold exceeds maximum
*/
error RebalanceThresholdExceedsMax();
/**
* @notice Thrown when current strategy has balance and cannot be changed
*/
error CurrentStrategyHasBalance();
/**
* @notice Thrown when withdrawal amount exceeds available ETH balance
*/
error AmountExceedsETHBalance();
/**
* @notice Thrown when unable to send ETH
*/
error UnableToSendETH();
error StrategyAlreadyInitializedForOtherPhiEth();
error InvalidZeroStrategyAddress();
error StrategyNotWhitelisted();
error CallerNotFactory();
// ============ Core Functions ============
/**
* @notice Deposits ETH and/or WETH and mints PhiEth tokens
* @param wethAmount Amount of WETH to deposit (in addition to msg.value ETH)
*/
function deposit(uint256 wethAmount) external payable;
/**
* @notice Withdraws ETH by burning PhiEth tokens
* @param amount Amount of PhiEth tokens to burn and ETH to withdraw
*/
function withdraw(uint256 amount) external;
/**
* @notice Rebalances the position against the strategy
* @dev Converts excess ETH to LSTs when balance exceeds threshold
*/
function rebalance() external;
/**
* @notice Harvests yield from the strategy and distributes to recipients
*/
function harvest() external;
// ============ View Functions ============
/**
* @return the address of merklDistributor
*/
function merklDistributor() external view returns (address);
/**
* @notice Calculate the amount of yield accumulated
* @return The yield accumulated in ETH
*/
function yieldAccumulated() external view returns (uint256);
/**
* @notice Get the total underlying ETH balance (contract + strategy)
* @return The total amount of underlying ETH held
*/
function underlyingETHBalance() external view returns (uint256);
/**
* @notice Get the creator address
* @return The creator address
*/
function getCreator() external view returns (address);
/**
* @notice Get the current yield receiver address
* @return The yield receiver address
*/
function yieldReceiver() external view returns (address);
/**
* @notice Get the current rebalance threshold
* @return The rebalance threshold value
*/
function rebalanceThreshold() external view returns (uint256);
/**
* @notice Get the current strategy
* @return The strategy contract address
*/
function strategy() external view returns (IBasePhiEthStrategy);
// ============ Configuration Functions ============
/**
* @notice Update the rebalance threshold (creator only)
* @param rebalanceThreshold_ The new rebalance threshold value
*/
function setRebalanceThreshold(uint256 rebalanceThreshold_) external;
/**
* @notice Update the yield receiver address (creator only)
* @param yieldReceiver_ The new yield receiver address
*/
function setYieldReceiver(address yieldReceiver_) external;
/**
* @notice Change the strategy (creator only)
* @param strategy_ The new strategy contract
*/
function changeStrategy(IBasePhiEthStrategy strategy_) external;
/**
* @notice Transfer PhiEth tokens to recipient
* @param recipient The recipient address
* @param amount The amount to transfer
* @return True if transfer succeeded
*/
function transfer(address recipient, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title IMorphoVault
* @notice Complete interface for Morpho Vault (ERC4626 compliant + MetaMorpho extensions)
*/
interface IMorphoVault is IERC20 {
// ============ ERC4626 Standard Functions ============
/// @notice Returns the address of the underlying asset token
function asset() external view returns (address);
/// @notice Returns total amount of underlying assets held by vault
function totalAssets() external view returns (uint256);
/// @notice Converts asset amount to share amount
/// @param assets Amount of assets to convert
/// @return shares Equivalent amount of shares
function convertToShares(uint256 assets) external view returns (uint256 shares);
/// @notice Converts share amount to asset amount
/// @param shares Amount of shares to convert
/// @return assets Equivalent amount of assets
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/// @notice Maximum amount of assets that can be deposited for receiver
/// @param receiver Address receiving the shares
/// @return maxAssets Maximum deposit amount
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/// @notice Preview amount of shares that would be minted for assets
/// @param assets Amount of assets to deposit
/// @return shares Amount of shares that would be minted
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/// @notice Deposits assets and mints shares to receiver
/// @param assets Amount of assets to deposit
/// @param receiver Address to receive shares
/// @return shares Amount of shares minted
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/// @notice Maximum amount of shares that can be minted for receiver
/// @param receiver Address receiving the shares
/// @return maxShares Maximum mint amount
function maxMint(address receiver) external view returns (uint256 maxShares);
/// @notice Preview amount of assets needed to mint shares
/// @param shares Amount of shares to mint
/// @return assets Amount of assets needed
function previewMint(uint256 shares) external view returns (uint256 assets);
/// @notice Mints shares by depositing assets
/// @param shares Amount of shares to mint
/// @param receiver Address to receive shares
/// @return assets Amount of assets deposited
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/// @notice Maximum amount of assets that can be withdrawn by owner
/// @param owner Address owning the shares
/// @return maxAssets Maximum withdrawal amount
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/// @notice Preview amount of shares that would be burned for assets
/// @param assets Amount of assets to withdraw
/// @return shares Amount of shares that would be burned
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/// @notice Withdraws assets by burning shares
/// @param assets Amount of assets to withdraw
/// @param receiver Address to receive assets
/// @param owner Address owning the shares
/// @return shares Amount of shares burned
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/// @notice Maximum amount of shares that can be redeemed by owner
/// @param owner Address owning the shares
/// @return maxShares Maximum redeem amount
function maxRedeem(address owner) external view returns (uint256 maxShares);
/// @notice Preview amount of assets that would be received for shares
/// @param shares Amount of shares to redeem
/// @return assets Amount of assets that would be received
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/// @notice Redeems shares for assets
/// @param shares Amount of shares to redeem
/// @param receiver Address to receive assets
/// @param owner Address owning the shares
/// @return assets Amount of assets received
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
// ============ MetaMorpho Specific Functions ============
/// @notice Returns the current fee rate (in basis points, where 10000 = 100%)
function fee() external view returns (uint256);
/// @notice Returns the fee recipient address
function feeRecipient() external view returns (address);
/// @notice Returns vault curator/owner
function owner() external view returns (address);
/// @notice Returns vault curator
function curator() external view returns (address);
/// @notice Returns if vault is paused
function paused() external view returns (bool);
/// @notice Returns the vault's symbol
function symbol() external view returns (string memory);
/// @notice Returns the vault's name
function name() external view returns (string memory);
/// @notice Returns the vault's decimals
function decimals() external view returns (uint8);
/// @notice Returns the timelock duration
function timelock() external view returns (uint256);
/// @notice Returns guardian address
function guardian() external view returns (address);
/// @notice Returns supply queue (ordered list of markets)
function supplyQueue(uint256 index) external view returns (bytes32);
/// @notice Returns supply queue length
function supplyQueueLength() external view returns (uint256);
/// @notice Returns withdraw queue (ordered list of markets)
function withdrawQueue(uint256 index) external view returns (bytes32);
/// @notice Returns withdraw queue length
function withdrawQueueLength() external view returns (uint256);
/// @notice Returns last total assets value
function lastTotalAssets() external view returns (uint256);
// ============ Market Management ============
/// @notice Market configuration
struct MarketConfig {
uint256 cap; // Supply cap for this market
bool enabled; // Whether market is enabled
uint256 removableAt; // Timestamp when market can be removed
}
/// @notice Returns market configuration
/// @param marketId Market identifier
function config(bytes32 marketId) external view returns (MarketConfig memory);
/// @notice Returns if market is in supply queue
function isSupplyMarket(bytes32 marketId) external view returns (bool);
/// @notice Returns if market is in withdraw queue
function isWithdrawMarket(bytes32 marketId) external view returns (bool);
// ============ Events ============
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
);
event UpdateLastTotalAssets(uint256 updatedTotalAssets);
event AccrueInterest(uint256 newTotalAssets, uint256 feeShares);
// MetaMorpho specific events
event SetFee(address indexed caller, uint256 newFee);
event SetFeeRecipient(address indexed newFeeRecipient);
event SetCurator(address indexed newCurator);
event SetIsAllocator(address indexed allocator, bool newIsAllocator);
event SetSkimRecipient(address indexed newSkimRecipient);
event SetSupplyQueue(address indexed caller, bytes32[] newSupplyQueue);
event SetWithdrawQueue(address indexed caller, bytes32[] newWithdrawQueue);
event SetCap(address indexed caller, bytes32 indexed id, uint256 cap);
event SubmitMarketRemoval(address indexed caller, bytes32 indexed id);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @notice The pending root struct for a merkle tree distribution during the timelock.
struct PendingRoot {
/// @dev The submitted pending root.
bytes32 root;
/// @dev The optional ipfs hash containing metadata about the root (e.g. the merkle tree itself).
bytes32 ipfsHash;
/// @dev The timestamp at which the pending root can be accepted.
uint256 validAt;
}
/// @dev This interface is used for factorizing IUniversalRewardsDistributorStaticTyping and
/// IUniversalRewardsDistributor.
/// @dev Consider using the IUniversalRewardsDistributor interface instead of this one.
interface IUniversalRewardsDistributorBase {
function root() external view returns (bytes32);
function owner() external view returns (address);
function timelock() external view returns (uint256);
function ipfsHash() external view returns (bytes32);
function isUpdater(address) external view returns (bool);
function claimed(address, address) external view returns (uint256);
function acceptRoot() external;
function setRoot(bytes32 newRoot, bytes32 newIpfsHash) external;
function setTimelock(uint256 newTimelock) external;
function setRootUpdater(address updater, bool active) external;
function revokePendingRoot() external;
function setOwner(address newOwner) external;
function submitRoot(bytes32 newRoot, bytes32 ipfsHash) external;
function claim(
address account,
address reward,
uint256 claimable,
bytes32[] memory proof
)
external
returns (uint256 amount);
}
/// @dev This interface is inherited by the UniversalRewardsDistributor so that function signatures are checked by the
/// compiler.
/// @dev Consider using the IUniversalRewardsDistributor interface instead of this one.
interface IUniversalRewardsDistributorStaticTyping is IUniversalRewardsDistributorBase {
function pendingRoot() external view returns (bytes32 root, bytes32 ipfsHash, uint256 validAt);
}
/// @title IUniversalRewardsDistributor
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @dev Use this interface for UniversalRewardsDistributor to have access to all the functions with the appropriate
/// function signatures.
interface IUniversalRewardsDistributor is IUniversalRewardsDistributorBase {
function pendingRoot() external view returns (PendingRoot memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
/**
* @title IMorphoVaultStrategy
* @notice Extended interface for Morpho Vault strategy with reward claiming functionality
* @dev Basic strategy functions are defined in IBasePhiEthStrategy
* @dev This interface defines Morpho Vault specific functionality
*/
interface IMorphoVaultStrategy {
event RewardsClaimed(address[] rewardTokens, uint256[] amounts);
// ============ Structs ============
/**
* @notice Struct for reward claim data (from Morpho API)
* @param reward Reward token address
* @param claimable Total claimable amount from API
* @param proof Merkle proof from API
* @param distributor URD contract address from API
*/
struct RewardClaimData {
address reward;
uint256 claimable;
bytes32[] proof;
address distributor;
}
// ============ Morpho-Specific Errors ============
// Note: Basic errors are defined in IBasePhiEthStrategy
error VaultAssetIsNotWETH();
error ExceedsMaxWithdrawable(uint256 requested, uint256 maxWithdrawable);
error NoRewardDataProvided();
error InvalidRewardToken(address token);
error InvalidDistributorAddress();
error UseClaimRewardsWithData();
error CannotWithdrawVaultShares();
error CannotWithdrawWETH();
error NotARewardToken(address token);
error InsufficientRewardBalance(address token, uint256 requested, uint256 available);
error CannotClaimVaultShares();
// ============ Morpho-Specific Functions ============
/**
* @notice Claims rewards using Universal Rewards Distributor (URD)
* @dev Requires reward data from Morpho API: https://rewards.morpho.org/v1/users/{address}/distributions
* @param rewardClaimData Array of reward claim data from API
* @return rewardTokens Array of reward token addresses
* @return amounts Array of actual amounts claimed
*/
function claimRewards(RewardClaimData[] calldata rewardClaimData)
external
returns (address[] memory rewardTokens, uint256[] memory amounts);
}// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
interface IWETH {
function balanceOf(address) external view returns (uint256);
function deposit() external payable;
function withdraw(uint256) external;
function approve(address guy, uint256 wad) external returns (bool);
function allowance(address src, address guy) external view returns (uint256);
function transfer(address dst, uint256 wad) external returns (bool);
function transferFrom(address src, address dst, uint256 wad) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
/**
* @title IBasePhiEthStrategy
* @notice Interface for all PhiEth strategies with events and errors
* @dev Defines the complete contract interface including events and custom errors
*/
interface IBasePhiEthStrategy {
// ============ Events ============
/**
* @notice Emitted when a strategy is initialized
* @param phiEth Address of the PhiEth token
*/
event StrategyInitialized(address indexed phiEth);
/**
* @notice Emitted when unwinding status changes
* @param isUnwinding New unwinding status
*/
event UnwindingStatusChanged(bool isUnwinding);
/**
* @notice Emitted when ETH is deposited into the strategy
* @param ethAmount Amount of ETH deposited
* @param sharesReceived Amount of strategy tokens received
*/
event ETHDeposited(uint256 ethAmount, uint256 sharesReceived);
/**
* @notice Emitted when ETH is withdrawn from the strategy
* @param sharesRedeemed Amount of strategy tokens redeemed
* @param ethReceived Amount of ETH received
* @param recipient Address that received the ETH
*/
event ETHWithdrawn(uint256 sharesRedeemed, uint256 ethReceived, address indexed recipient);
// Merkl Events
event MerklOperatorRegistered(address operator);
event MerklOperatorToggled(address indexed operator);
event MerklRewardsClaimed(address indexed token, uint256 amount);
// ============ Errors ============
/**
* @notice Thrown when caller is not the PhiEth token contract
*/
error CallerIsNotPhiEth();
/**
* @notice Thrown when caller is not the PhiEth owner
*/
error CallerIsNotPhiEthOwner();
/**
* @notice Thrown when strategy is already initialized
*/
error AlreadyInitialized();
/**
* @notice Thrown when strategy is not initialized
*/
error NotInitialized();
/**
* @notice Thrown when PhiEth address is invalid (zero address)
*/
error InvalidPhiEthAddress();
/**
* @notice Thrown when amount must be greater than zero
*/
error AmountMustBeGreaterThanZero();
/**
* @notice Thrown when recipient address is invalid (zero address)
*/
error InvalidRecipientAddress();
/**
* @notice Thrown when there is insufficient balance for operation
*/
error InsufficientBalance();
/**
* @notice Thrown when token address is invalid
*/
error InvalidTokenAddress();
/**
* @notice Thrown when ETH transfer fails
*/
error ETHTransferFailed();
/**
* @notice Thrown when token transfer fails
*/
error TokenTransferFailed();
// Merkl Errors
error MerklDistributorNotSet();
error FailedToRegisterOperator();
error ArrayLengthMismatch();
// ============ Core Strategy Functions ============
function initialize(address phiEth) external;
/**
* @notice Deposits incoming ETH into the underlying protocol and mints LST
* @dev The LST tokens remain in this contract to earn yield
*/
function convertETHToLST() external payable;
/**
* @notice Withdraws ETH from the strategy and sends it to the recipient
* @param amount Amount of ETH to withdraw
* @param recipient Address to receive the ETH
*/
function withdrawETH(uint256 amount, address recipient) external;
/**
* @notice Returns the total ETH balance represented by the strategy's holdings
* @return The balance in ETH terms
*/
function balanceInETH() external view returns (uint256);
// ============ Administrative Functions ============
/**
* @notice Sets whether the strategy is currently unwinding
* @param isUnwinding_ New unwinding status
*/
function setIsUnwinding(bool isUnwinding_) external;
/**
* @notice Allows the owner to unwind the strategy in small amounts into ETH
* @param ethAmount Amount of ETH to unwind to
*/
function unwindToETH(uint256 ethAmount) external;
// ============ View Functions ============
/**
* @notice Returns the address of the PhiEth token
* @return The PhiEth token address
*/
function phiEth() external view returns (address);
/**
* @notice Returns whether the strategy is currently unwinding
* @return True if strategy is unwinding
*/
function isUnwinding() external view returns (bool);
function isInitialized() external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
// Merkl reward distributor interface
interface IMerklDistributor {
// ============ Structs ============
/**
* @notice Stores claim history for a user-token pair
* @param amount Total cumulative amount claimed
* @param timestamp Last claim timestamp
* @param merkleRoot Merkle root used for the last claim
*/
struct Claim {
uint208 amount;
uint48 timestamp;
bytes32 merkleRoot;
}
// ============ State-Changing Functions (Write) ============
/**
* @notice Claims rewards for specified users based on Merkle proofs
* @param users Array of user addresses receiving rewards
* @param tokens Array of token addresses being claimed
* @param amounts Array of cumulative amounts to claim
* @param proofs Array of Merkle proofs validating each claim
*/
function claim(
address[] calldata users,
address[] calldata tokens,
uint256[] calldata amounts,
bytes32[][] calldata proofs
) external;
/**
* @notice Toggles operator status for a given user-operator pair
* @param user The user granting/revoking operator permission
* @param operator The operator address to toggle
*/
function toggleOperator(address user, address operator) external;
// ============ View Functions (Read) ============
/**
* @notice Returns claim history for a user-token pair
* @param user The user address to query
* @param token The token address to query
* @return Claim struct containing amount, timestamp, and merkleRoot
*/
function claimed(address user, address token) external view returns (Claim memory);
/**
* @notice Checks if an operator is authorized to claim for a user
* @param user The user who granted permission
* @param operator The operator to check
* @return 1 if authorized, 0 if not authorized
*/
function operators(address user, address operator) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}{
"remappings": [
"@uniswap/v4-core/=lib/v4-periphery/lib/v4-core/",
"@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/community-contracts/=lib/openzeppelin-community-contracts/",
"@solady/=lib/solady/src/",
"@optimism/=lib/optimism/packages/contracts-bedrock/",
"forge-std/=lib/forge-std/src/",
"v4-periphery/=lib/v4-periphery/",
"@axelar-network/axelar-gmp-sdk-solidity/=lib/openzeppelin-community-contracts/lib/axelar-gmp-sdk-solidity/",
"@ensdomains/=lib/v4-periphery/lib/v4-core/node_modules/@ensdomains/",
"@openzeppelin-contracts-upgradeable/=lib/openzeppelin-community-contracts/lib/@openzeppelin-contracts-upgradeable/",
"@openzeppelin-contracts/=lib/openzeppelin-community-contracts/lib/@openzeppelin-contracts/",
"@zk-email/contracts/=lib/openzeppelin-community-contracts/lib/zk-email-verify/packages/contracts/",
"@zk-email/email-tx-builder/=lib/openzeppelin-community-contracts/lib/email-tx-builder/packages/contracts/",
"axelar-gmp-sdk-solidity/=lib/openzeppelin-community-contracts/lib/axelar-gmp-sdk-solidity/contracts/",
"ds-test/=lib/v4-periphery/lib/v4-core/lib/forge-std/lib/ds-test/src/",
"email-tx-builder/=lib/openzeppelin-community-contracts/lib/email-tx-builder/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"forge-gas-snapshot/=lib/v4-periphery/lib/permit2/lib/forge-gas-snapshot/src/",
"halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
"hardhat/=lib/v4-periphery/lib/v4-core/node_modules/hardhat/",
"kontrol-cheatcodes/=lib/optimism/packages/contracts-bedrock/lib/kontrol-cheatcodes/src/",
"lib-keccak/=lib/optimism/packages/contracts-bedrock/lib/lib-keccak/contracts/",
"openzeppelin-community-contracts/=lib/openzeppelin-community-contracts/contracts/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts-v5/=lib/optimism/packages/contracts-bedrock/lib/openzeppelin-contracts-v5/",
"openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/",
"optimism/=lib/optimism/",
"permit2/=lib/v4-periphery/lib/permit2/",
"safe-contracts/=lib/optimism/packages/contracts-bedrock/lib/safe-contracts/contracts/",
"solady-v0.0.245/=lib/optimism/packages/contracts-bedrock/lib/solady-v0.0.245/src/",
"solady/=lib/solady/src/",
"solmate/=lib/v4-periphery/lib/v4-core/lib/solmate/",
"v4-core/=lib/v4-periphery/lib/v4-core/src/",
"zk-email-verify/=lib/openzeppelin-community-contracts/lib/zk-email-verify/"
],
"optimizer": {
"enabled": true,
"runs": 10
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false
}Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AmountMustBeGreaterThanZero","type":"error"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"CallerIsNotPhiEth","type":"error"},{"inputs":[],"name":"CallerIsNotPhiEthOwner","type":"error"},{"inputs":[],"name":"CannotClaimVaultShares","type":"error"},{"inputs":[],"name":"CannotWithdrawVaultShares","type":"error"},{"inputs":[],"name":"CannotWithdrawWETH","type":"error"},{"inputs":[],"name":"ETHTransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"maxWithdrawable","type":"uint256"}],"name":"ExceedsMaxWithdrawable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"FailedToRegisterOperator","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"available","type":"uint256"}],"name":"InsufficientRewardBalance","type":"error"},{"inputs":[],"name":"InvalidDistributorAddress","type":"error"},{"inputs":[],"name":"InvalidPhiEthAddress","type":"error"},{"inputs":[],"name":"InvalidRecipientAddress","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"InvalidRewardToken","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[],"name":"MerklDistributorNotSet","type":"error"},{"inputs":[],"name":"NoRewardDataProvided","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"NotARewardToken","type":"error"},{"inputs":[],"name":"NotInitialized","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"TokenTransferFailed","type":"error"},{"inputs":[],"name":"UseClaimRewardsWithData","type":"error"},{"inputs":[],"name":"VaultAssetIsNotWETH","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ethAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesReceived","type":"uint256"}],"name":"ETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"sharesRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ethReceived","type":"uint256"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"}],"name":"ETHWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"operator","type":"address"}],"name":"MerklOperatorRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"}],"name":"MerklOperatorToggled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"MerklRewardsClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"rewardTokens","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"RewardsClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"phiEth","type":"address"}],"name":"StrategyInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isUnwinding","type":"bool"}],"name":"UnwindingStatusChanged","type":"event"},{"inputs":[],"name":"balanceInETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes32[][]","name":"proofs","type":"bytes32[][]"}],"name":"claimMerklRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"reward","type":"address"},{"internalType":"uint256","name":"claimable","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"address","name":"distributor","type":"address"}],"internalType":"struct IMorphoVaultStrategy.RewardClaimData[]","name":"rewardClaimData","type":"tuple[]"}],"name":"claimRewards","outputs":[{"internalType":"address[]","name":"rewardTokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"convertETHToLST","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_phiEth","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isUnwinding","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"phiEth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"isUnwinding_","type":"bool"}],"name":"setIsUnwinding","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toggleMerklOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"ethAmount","type":"uint256"}],"name":"unwindToETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.