|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +pragma solidity ^0.8.28; |
| 3 | + |
| 4 | +import {Library} from "./Library.sol"; |
| 5 | +import {BaseAccount} from "../accounts/BaseAccount.sol"; |
| 6 | +import {IERC20} from "forge-std/src/interfaces/IERC20.sol"; |
| 7 | +import {IERC4626} from "forge-std/src/interfaces/IERC4626.sol"; |
| 8 | +import {IMetaMorphoV1_1} from "./interfaces/morpho/IMetaMorphoV1_1.sol"; |
| 9 | + |
| 10 | +/** |
| 11 | + * @title MorphoVaultV1PositionManager |
| 12 | + * @dev Contract for managing Morpho Vault V1.1 through deposit and withdraw operations. |
| 13 | + * It leverages BaseAccount contract to interact with the Morpho Vault V1.1 protocol, enabling automated position management. |
| 14 | + */ |
| 15 | +contract MorphoVaultV1PositionManager is Library { |
| 16 | + /** |
| 17 | + * @title MorphoVaultV1PositionManagerConfig |
| 18 | + * @notice Configuration struct for Morpho Vault V1.1 Position Manager |
| 19 | + * @dev Used to define parameters for interacting with Morpho Vault V1.1 protocol |
| 20 | + * @param inputAccount The Base Account from which transactions will be initiated |
| 21 | + * @param outputAccount The Base Account that will receive withdrawals |
| 22 | + * @param vaultAddress Address of the Morpho Vault V1 |
| 23 | + * @param assetAddress Address of the underlying asset to manage |
| 24 | + */ |
| 25 | + struct MorphoVaultV1PositionManagerConfig { |
| 26 | + BaseAccount inputAccount; |
| 27 | + BaseAccount outputAccount; |
| 28 | + address vaultAddress; |
| 29 | + address assetAddress; |
| 30 | + } |
| 31 | + |
| 32 | + /// @notice Holds the current configuration for the MorphoVaultV1PositionManager. |
| 33 | + MorphoVaultV1PositionManagerConfig public config; |
| 34 | + |
| 35 | + /** |
| 36 | + * @dev Constructor initializes the contract with the owner, processor, and initial configuration. |
| 37 | + * @param _owner Address of the contract owner. |
| 38 | + * @param _processor Address of the processor that can execute functions. |
| 39 | + * @param _config Encoded configuration parameters for the MorphoVaultV1PositionManager. |
| 40 | + */ |
| 41 | + constructor(address _owner, address _processor, bytes memory _config) Library(_owner, _processor, _config) {} |
| 42 | + |
| 43 | + /** |
| 44 | + * @notice Deposits assets to MorphoVaultV1. |
| 45 | + * @param amount The amount to deposit (0 for all available balance). |
| 46 | + */ |
| 47 | + function deposit(uint256 amount) external onlyProcessor { |
| 48 | + MorphoVaultV1PositionManagerConfig memory storedConfig = config; |
| 49 | + IERC20 asset = IERC20(storedConfig.assetAddress); |
| 50 | + |
| 51 | + uint256 depositAmount = amount; |
| 52 | + if (amount == 0) { |
| 53 | + depositAmount = asset.balanceOf(address(storedConfig.inputAccount)); |
| 54 | + } |
| 55 | + |
| 56 | + require(depositAmount > 0, "No assets to deposit"); |
| 57 | + |
| 58 | + //Approve the MorphoVaultV1 to spend the base asset from the input account |
| 59 | + bytes memory encodedApproveCall = abi.encodeCall(IERC20.approve, (storedConfig.vaultAddress, depositAmount)); |
| 60 | + |
| 61 | + storedConfig.inputAccount.execute(address(asset), 0, encodedApproveCall); |
| 62 | + |
| 63 | + // Deposit the base asset to the MorphoVaultV1 |
| 64 | + bytes memory encodedDepositCall = |
| 65 | + abi.encodeCall(IERC4626.deposit, (depositAmount, address(storedConfig.inputAccount))); |
| 66 | + |
| 67 | + storedConfig.inputAccount.execute(storedConfig.vaultAddress, 0, encodedDepositCall); |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * @notice Withdraws assets from MorphoVaultV1 position. |
| 72 | + * @param amount The amount to withdraw (0 to withdraw the entire balance). |
| 73 | + */ |
| 74 | + function withdraw(uint256 amount) external onlyProcessor { |
| 75 | + MorphoVaultV1PositionManagerConfig memory storedConfig = config; |
| 76 | + IMetaMorphoV1_1 vault = IMetaMorphoV1_1(storedConfig.vaultAddress); |
| 77 | + |
| 78 | + uint256 withdrawAmount = amount; |
| 79 | + if (amount == 0) { |
| 80 | + withdrawAmount = vault.maxWithdraw(address(storedConfig.inputAccount)); |
| 81 | + } |
| 82 | + |
| 83 | + require(withdrawAmount > 0, "No vault tokens to withdraw"); |
| 84 | + |
| 85 | + // Withdraw from MorphoVaultV1 to output account |
| 86 | + bytes memory encodedWithdrawCall = abi.encodeCall( |
| 87 | + IERC4626.withdraw, (withdrawAmount, address(storedConfig.outputAccount), address(storedConfig.inputAccount)) |
| 88 | + ); |
| 89 | + |
| 90 | + storedConfig.inputAccount.execute(storedConfig.vaultAddress, 0, encodedWithdrawCall); |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * @dev Internal initialization function called during construction |
| 95 | + * @param _config New configuration |
| 96 | + */ |
| 97 | + function _initConfig(bytes memory _config) internal override { |
| 98 | + config = validateConfig(_config); |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * @dev Updates the MorphoVaultV1PositionManager configuration. |
| 103 | + * Only the contract owner is authorized to call this function. |
| 104 | + * @param _config New encoded configuration parameters. |
| 105 | + */ |
| 106 | + function updateConfig(bytes memory _config) public override onlyOwner { |
| 107 | + // Validate and update the configuration. |
| 108 | + config = validateConfig(_config); |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * @notice Validates the provided configuration parameters |
| 113 | + * @dev Checks for validity of input account, output account, base asset, and market proxy address |
| 114 | + * @param _config The encoded configuration bytes to validate |
| 115 | + * @return MorphoVaultV1PositionManagerConfig A validated configuration struct |
| 116 | + */ |
| 117 | + function validateConfig(bytes memory _config) internal view returns (MorphoVaultV1PositionManagerConfig memory) { |
| 118 | + // Decode the configuration bytes into the MorphoVaultV1PositionManagerConfig struct. |
| 119 | + MorphoVaultV1PositionManagerConfig memory decodedConfig = |
| 120 | + abi.decode(_config, (MorphoVaultV1PositionManagerConfig)); |
| 121 | + |
| 122 | + // Ensure the Vault address is valid (non-zero). |
| 123 | + if (decodedConfig.vaultAddress == address(0)) { |
| 124 | + revert("Vault address can't be zero address"); |
| 125 | + } |
| 126 | + |
| 127 | + // Ensure the input account address is valid (non-zero). |
| 128 | + if (decodedConfig.inputAccount == BaseAccount(payable(address(0)))) { |
| 129 | + revert("Input account can't be zero address"); |
| 130 | + } |
| 131 | + |
| 132 | + // Ensure the output account address is valid (non-zero). |
| 133 | + if (decodedConfig.outputAccount == BaseAccount(payable(address(0)))) { |
| 134 | + revert("Output account can't be zero address"); |
| 135 | + } |
| 136 | + |
| 137 | + // Ensure the asset address is the same as the asset address of the vault |
| 138 | + if (decodedConfig.assetAddress != IMetaMorphoV1_1(decodedConfig.vaultAddress).asset()) { |
| 139 | + revert("Vault asset and given asset are not same"); |
| 140 | + } |
| 141 | + |
| 142 | + return decodedConfig; |
| 143 | + } |
| 144 | + |
| 145 | + function balance() external view returns (uint256) { |
| 146 | + return IMetaMorphoV1_1(config.vaultAddress).previewRedeem( |
| 147 | + IMetaMorphoV1_1(config.vaultAddress).balanceOf(address(config.inputAccount)) |
| 148 | + ); |
| 149 | + } |
| 150 | +} |
0 commit comments