Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions contracts/adapters/mellow/MellowDepositQueueAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {AbstractAdapter} from "../AbstractAdapter.sol";
import {IMellowDepositQueueAdapter} from "../../interfaces/mellow/IMellowDepositQueueAdapter.sol";
import {IMellowFlexibleDepositGateway} from "../../interfaces/mellow/IMellowFlexibleDepositGateway.sol";
import {MellowFlexibleDepositPhantomToken} from "../../helpers/mellow/MellowFlexibleDepositPhantomToken.sol";

import {NotImplementedException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol";

/// @title Mellow Flexible vaults deposit queue adapter
/// @notice Implements logic allowing CAs to interact with the deposit queue of Mellow flexible vaults, allowing deposits and matured deposit claiming.
contract MellowDepositQueueAdapter is AbstractAdapter, IMellowDepositQueueAdapter {
bytes32 public constant override contractType = "ADAPTER::MELLOW_DEPOSIT_QUEUE";
uint256 public constant override version = 3_10;

/// @notice The asset deposited into the vault through the queue
address public immutable asset;

/// @notice The phantom token representing the pending deposits in the queue
address public immutable phantomToken;

/// @notice The referral address for the deposits
address public immutable referral;

/// @notice Constructor
constructor(address _creditManager, address _depositQueueGateway, address _referral, address _phantomToken)
AbstractAdapter(_creditManager, _depositQueueGateway)
{
asset = IMellowFlexibleDepositGateway(_depositQueueGateway).asset();
phantomToken = _phantomToken;
referral = _referral;

if (MellowFlexibleDepositPhantomToken(phantomToken).depositQueueGateway() != _depositQueueGateway) {
revert InvalidDepositQueueGatewayException();
}

_getMaskOrRevert(asset);
_getMaskOrRevert(phantomToken);
}

/// @notice Initiates a deposit through the queue with exact amount of assets
/// @param assets The amount of assets to deposit
/// @dev `referral` and `merkleProof` are ignored, since the first is hard-coded on deployment, and the second
/// is on off-chain parameter that is not required
/// @dev Returns true in order to price a new pending deposit using safe prices (to enforce a HF buffer on position opening)
function deposit(uint256 assets, address, bytes32[] calldata) external creditFacadeOnly returns (bool) {
_deposit(assets);
return true;
}

/// @notice Initiates a deposit through the queue with the entire balance of the asset, except the specified amount
/// @param leftoverAmount The amount of assets to leave on the credit account
/// @dev Returns true in order to price a new pending deposit using safe prices (to enforce a HF buffer on position opening)
function depositDiff(uint256 leftoverAmount) external creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();

uint256 amount = IERC20(asset).balanceOf(creditAccount);

if (amount <= leftoverAmount) return false;

unchecked {
amount = amount - leftoverAmount;
}

_deposit(amount);
return true;
}

/// @dev Internal implementation for `deposit` and `depositDiff`
function _deposit(uint256 assets) internal {
_executeSwapSafeApprove(
asset, abi.encodeCall(IMellowFlexibleDepositGateway.deposit, (assets, referral, new bytes32[](0)))
);
}

/// @notice Cancels a pending deposit request
function cancelDepositRequest() external creditFacadeOnly returns (bool) {
_execute(abi.encodeCall(IMellowFlexibleDepositGateway.cancelDepositRequest, ()));
return false;
}

/// @notice Claims a specific amount from mature deposits
function claim(uint256 amount) external creditFacadeOnly returns (bool) {
_claim(amount);
return false;
}

/// @dev Internal implementation for `claim`
function _claim(uint256 amount) internal {
_execute(abi.encodeCall(IMellowFlexibleDepositGateway.claim, (amount)));
}

/// @notice Claims mature deposits, represented by the corresponding phantom token
function withdrawPhantomToken(address pt, uint256 amount) external creditFacadeOnly returns (bool) {
if (pt != phantomToken) revert IncorrectStakedPhantomTokenException();
_claim(amount);
return false;
}

/// @dev Not implemented, as there is no way to go from the phantom token to the asset
function depositPhantomToken(address, uint256) external view creditFacadeOnly returns (bool) {
revert NotImplementedException();
}

/// @notice Serialized adapter parameters
function serialize() external view returns (bytes memory serializedData) {
serializedData = abi.encode(creditManager, targetContract, asset, phantomToken, referral);
}
}
102 changes: 102 additions & 0 deletions contracts/adapters/mellow/MellowRedeemQueueAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {AbstractAdapter} from "../AbstractAdapter.sol";
import {IMellowRedeemQueueAdapter} from "../../interfaces/mellow/IMellowRedeemQueueAdapter.sol";
import {IMellowFlexibleRedeemGateway} from "../../interfaces/mellow/IMellowFlexibleRedeemGateway.sol";
import {MellowFlexibleRedeemPhantomToken} from "../../helpers/mellow/MellowFlexibleRedeemPhantomToken.sol";

import {NotImplementedException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol";

/// @title Mellow Flexible vaults redemption queue adapter
/// @notice Implements logic allowing CAs to interact with the redemption queue of Mellow flexible vaults, allowing redemptions and matured redemption claiming.
contract MellowRedeemQueueAdapter is AbstractAdapter, IMellowRedeemQueueAdapter {
bytes32 public constant override contractType = "ADAPTER::MELLOW_REDEEM_QUEUE";
uint256 public constant override version = 3_10;

/// @notice The vault token that is redeemed through the queue
address public immutable vaultToken;

/// @notice The phantom token representing the pending redemptions in the queue
address public immutable phantomToken;

/// @notice Constructor
constructor(address _creditManager, address _redeemQueueGateway, address _phantomToken)
AbstractAdapter(_creditManager, _redeemQueueGateway)
{
vaultToken = IMellowFlexibleRedeemGateway(_redeemQueueGateway).vaultToken();
phantomToken = _phantomToken;

if (MellowFlexibleRedeemPhantomToken(phantomToken).redeemQueueGateway() != _redeemQueueGateway) {
revert InvalidRedeemQueueGatewayException();
}

_getMaskOrRevert(vaultToken);
_getMaskOrRevert(phantomToken);
}

/// @notice Initiates a redemption through the queue with exact amount of shares
/// @param shares The amount of shares to redeem
/// @dev Returns true in order to price a new pending redemption using safe prices (to enforce a HF buffer on position opening)
function redeem(uint256 shares) external creditFacadeOnly returns (bool) {
_redeem(shares);
return true;
}

/// @notice Initiates a redemption through the queue with the entire balance of the vault token, except the specified amount
/// @param leftoverAmount The amount of vault tokens to leave on the credit account
/// @dev Returns true in order to price a new pending redemption using safe prices (to enforce a HF buffer on position opening)
function redeemDiff(uint256 leftoverAmount) external creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();

uint256 amount = IERC20(vaultToken).balanceOf(creditAccount);

if (amount <= leftoverAmount) return false;

unchecked {
amount = amount - leftoverAmount;
}

_redeem(amount);
return true;
}

/// @dev Internal implementation for `redeem` and `redeemDiff`
function _redeem(uint256 shares) internal {
_executeSwapSafeApprove(vaultToken, abi.encodeCall(IMellowFlexibleRedeemGateway.redeem, (shares)));
}

/// @notice Claims a specific amount from mature redemptions
function claim(uint256 amount) external creditFacadeOnly returns (bool) {
_claim(amount);
return false;
}

/// @dev Internal implementation for `claim`
function _claim(uint256 amount) internal {
_execute(abi.encodeCall(IMellowFlexibleRedeemGateway.claim, (amount)));
}

/// @notice Claims mature redemptions, represented by the corresponding phantom token
function withdrawPhantomToken(address pt, uint256 amount) external creditFacadeOnly returns (bool) {
if (pt != phantomToken) revert IncorrectStakedPhantomTokenException();
_claim(amount);
return false;
}

/// @dev Not implemented, as there is no way to go from the phantom token to the asset
function depositPhantomToken(address, uint256) external view creditFacadeOnly returns (bool) {
revert NotImplementedException();
}

/// @notice Serialized adapter parameters
function serialize() external view returns (bytes memory serializedData) {
serializedData = abi.encode(creditManager, targetContract, vaultToken, phantomToken);
}
}
98 changes: 98 additions & 0 deletions contracts/helpers/mellow/MellowFlexibleDepositGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2025.
pragma solidity ^0.8.23;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

import {IMellowFlexibleDepositGateway} from "../../interfaces/mellow/IMellowFlexibleDepositGateway.sol";
import {IMellowDepositQueue} from "../../integrations/mellow/IMellowDepositQueue.sol";
import {IMellowFlexibleVault} from "../../integrations/mellow/IMellowFlexibleVault.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {MellowFlexibleDepositor} from "./MellowFlexibleDepositor.sol";

/// @title Mellow Flexible Vaults deposit gateway
/// @notice Acts as an intermediary between Gearbox Credit Acocunts and the Mellow deposit queue to avoid unexpected balance changes,
/// and allow partial claiming of matured deposits.
contract MellowFlexibleDepositGateway is IMellowFlexibleDepositGateway {
using SafeERC20 for IERC20;

bytes32 public constant override contractType = "GATEWAY::MELLOW_DEPOSIT_QUEUE";
uint256 public constant override version = 3_10;

/// @notice The deposit queue contract
address public immutable mellowDepositQueue;

/// @notice The asset deposited into the vault through the queue
address public immutable asset;

/// @notice The vault token received from deposits
address public immutable vaultToken;

/// @notice The master depositor contract
address public immutable masterDepositor;

/// @notice Mapping of accounts to corresponding depositor contracts,
/// which interact directly with the queue
mapping(address => address) public accountToDepositor;

constructor(address _mellowDepositQueue) {
mellowDepositQueue = _mellowDepositQueue;
asset = IMellowDepositQueue(mellowDepositQueue).asset();
vaultToken = IMellowFlexibleVault(IMellowDepositQueue(mellowDepositQueue).vault()).shareManager();
masterDepositor = address(new MellowFlexibleDepositor(mellowDepositQueue, asset, vaultToken));
}

/// @notice Deposits assets into the vault through the queue
/// @param assets The amount of assets to deposit
/// @param referral The referral address for the deposit
function deposit(uint256 assets, address referral, bytes32[] calldata) external {
address depositor = _getDepositorForAccount(msg.sender);
IERC20(asset).safeTransferFrom(msg.sender, depositor, assets);
MellowFlexibleDepositor(depositor).deposit(assets, referral);
}

/// @notice Cancels a pending deposit request
function cancelDepositRequest() external {
address depositor = _getDepositorForAccount(msg.sender);
MellowFlexibleDepositor(depositor).cancelDepositRequest();
}

/// @notice Claims a specific amount from mature deposits
function claim(uint256 amount) external {
address depositor = _getDepositorForAccount(msg.sender);
MellowFlexibleDepositor(depositor).claim(amount);
}

/// @notice Returns the amount of assets pending for a deposit
function getPendingAssets(address account) external view returns (uint256) {
address depositor = accountToDepositor[account];
if (depositor == address(0)) {
return 0;
}
return MellowFlexibleDepositor(depositor).getPendingAssets();
}

/// @notice Returns the amount of shares claimable from mature deposits
function getClaimableShares(address account) external view returns (uint256) {
address depositor = accountToDepositor[account];
if (depositor == address(0)) {
return 0;
}
return MellowFlexibleDepositor(depositor).getClaimableShares();
}

/// @dev Internal function to get the depositor contract for an account or create a new one if it doesn't exist
function _getDepositorForAccount(address account) internal returns (address) {
address depositor = accountToDepositor[account];
if (depositor == address(0)) {
depositor = Clones.clone(masterDepositor);
MellowFlexibleDepositor(depositor).setAccount(account);
accountToDepositor[account] = depositor;
}
return depositor;
}
}
Loading