Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
43707c9
feat: add RedemptionVaultWithAave contract and integrate Aave V3 for …
dmytro-horbatenko Feb 13, 2026
d05d40d
feat: add RedemptionVaultWithMorpho contract and integrate Morpho Vau…
dmytro-horbatenko Feb 16, 2026
462e01e
refactor: update Aave V3 interface to use getReserveAToken
dmytro-horbatenko Feb 16, 2026
e76018c
fix: withdrawal amount check in RedemptionVaultWithAave
dmytro-horbatenko Feb 16, 2026
db24ac1
refactor: replace IERC4626Vault with IMorphoVault in RedemptionVaultW…
dmytro-horbatenko Feb 17, 2026
74b10e9
feat: add RedemptionVaultWithMToken contract
dmytro-horbatenko Mar 2, 2026
64618d2
feat: add DepositVaultWithAave and DepositVaultWithMorpho contracts f…
dmytro-horbatenko Mar 3, 2026
d78beb8
feat: add DepositVaultWithMToken contract
dmytro-horbatenko Mar 3, 2026
cf6ae91
refactor: update aave vaults to support multiple pools
dmytro-horbatenko Mar 4, 2026
714194d
chore: checkAndRedeemMToken tests
dmytro-horbatenko Mar 4, 2026
c50988f
fix: correct mTokenAAmount calculation to use ceil rounding and add n…
dmytro-horbatenko Mar 4, 2026
675402c
feat: implement ceiling division for mToken calculations and enhance …
dmytro-horbatenko Mar 5, 2026
8fc4cf5
Merge branch 'main' into feat/aave-and-morpho-vaults
dmytro-horbatenko Mar 9, 2026
23a59c1
chore: enhance new vault tests
dmytro-horbatenko Mar 9, 2026
8dbcbcd
feat: implement auto-invest fallback for Aave, Morpho, and MToken de…
dmytro-horbatenko Mar 11, 2026
8dfa398
refactor: update _autoInvest function to accept pool and vault parame…
dmytro-horbatenko Mar 11, 2026
e54995e
chore: aave and morpho dvs integration tests
dmytro-horbatenko Mar 11, 2026
cb1033e
chore: greenlist per token contracts generation
kostyamospan Mar 11, 2026
67c35b9
chore: update documentation and formatting in contract templates
dmytro-horbatenko Mar 12, 2026
62d441b
fix: convert balances to base18 before subtraction in _checkAndRedeem…
dmytro-horbatenko Mar 30, 2026
840a2a0
Merge branch 'main' into feat/aave-and-morpho-vaults
kostyamospan Apr 2, 2026
4703429
fix: shorten revert messages
dmytro-horbatenko Apr 2, 2026
478497f
Merge branch 'main' into feat/aave-and-morpho-vaults
dmytro-horbatenko Apr 2, 2026
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
16 changes: 11 additions & 5 deletions config/constants/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ export type RedemptionVaultType =
| 'redemptionVault'
| 'redemptionVaultBuidl'
| 'redemptionVaultSwapper'
| 'redemptionVaultUstb';
| 'redemptionVaultMToken'
| 'redemptionVaultUstb'
| 'redemptionVaultAave'
| 'redemptionVaultMorpho';

export type DepositVaultType = 'depositVault' | 'depositVaultUstb';
export type DepositVaultType =
| 'depositVault'
| 'depositVaultUstb'
| 'depositVaultAave'
| 'depositVaultMorpho'
| 'depositVaultMToken';

export type LayerZeroTokenAddresses = {
oft?: string;
Expand All @@ -26,11 +34,9 @@ export type TokenAddresses = {
customFeed?: string;
dataFeed?: string;
token?: string;
depositVault?: string;
depositVaultUstb?: string;
layerZero?: LayerZeroTokenAddresses;
axelar?: AxelarTokenAddresses;
} & Partial<Record<RedemptionVaultType, string>>;
} & Partial<Record<DepositVaultType | RedemptionVaultType, string>>;

export type VaultType = RedemptionVaultType | DepositVaultType;

Expand Down
136 changes: 136 additions & 0 deletions contracts/DepositVaultWithAave.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

import "./DepositVault.sol";
import "./interfaces/aave/IAaveV3Pool.sol";

/**
* @title DepositVaultWithAave
* @notice Smart contract that handles mToken minting and invests
* proceeds into Aave V3 Pool
* @dev If `aaveDepositsEnabled` is false, regular deposit flow is used
* @author RedDuck Software
*/
contract DepositVaultWithAave is DepositVault {
using DecimalsCorrectionLibrary for uint256;
using SafeERC20 for IERC20;

/**
* @notice mapping payment token to Aave V3 Pool
*/
mapping(address => IAaveV3Pool) public aavePools;

/**
* @notice Whether Aave auto-invest deposits are enabled
* @dev if false, regular deposit flow will be used
*/
bool public aaveDepositsEnabled;

/**
* @dev leaving a storage gap for futures updates
*/
uint256[50] private __gap;

/**
* @notice Emitted when an Aave V3 Pool is configured for a payment token
* @param caller address of the caller
* @param token payment token address
* @param pool Aave V3 Pool address
*/
event SetAavePool(
address indexed caller,
address indexed token,
address indexed pool
);

/**
* @notice Emitted when an Aave V3 Pool is removed for a payment token
* @param caller address of the caller
* @param token payment token address
*/
event RemoveAavePool(address indexed caller, address indexed token);

/**
* @notice Emitted when `aaveDepositsEnabled` flag is updated
* @param enabled Whether Aave deposits are enabled
*/
event SetAaveDepositsEnabled(bool indexed enabled);

/**
* @notice Sets the Aave V3 Pool for a specific payment token
* @param _token payment token address
* @param _aavePool Aave V3 Pool address for this token
*/
function setAavePool(address _token, address _aavePool)
external
onlyVaultAdmin
{
_validateAddress(_token, false);
_validateAddress(_aavePool, false);
require(
IAaveV3Pool(_aavePool).getReserveAToken(_token) != address(0),
"DVA: token not in pool"
);
aavePools[_token] = IAaveV3Pool(_aavePool);
emit SetAavePool(msg.sender, _token, _aavePool);
}

/**
* @notice Removes the Aave V3 Pool for a specific payment token
* @param _token payment token address
*/
function removeAavePool(address _token) external onlyVaultAdmin {
require(address(aavePools[_token]) != address(0), "DVA: pool not set");
delete aavePools[_token];
emit RemoveAavePool(msg.sender, _token);
}

/**
* @notice Updates `aaveDepositsEnabled` value
* @param enabled whether Aave auto-invest deposits are enabled
*/
function setAaveDepositsEnabled(bool enabled) external onlyVaultAdmin {
aaveDepositsEnabled = enabled;
emit SetAaveDepositsEnabled(enabled);
}

/**
* @dev overrides original transfer to tokens receiver function
* in case of Aave deposits are disabled, it will act as the original transfer
* otherwise it will take payment tokens from user, supply them to Aave V3 Pool
* and aTokens will be minted to tokens receiver
* @param tokenIn token address
* @param amountToken amount of tokens to transfer in base18
* @param tokensDecimals decimals of tokens
*/
function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
) internal override {
if (!aaveDepositsEnabled) {
return
super._instantTransferTokensToTokensReceiver(
tokenIn,
amountToken,
tokensDecimals
);
}

IAaveV3Pool pool = aavePools[tokenIn];
require(address(pool) != address(0), "DVA: no pool for token");

uint256 transferredAmount = _tokenTransferFromUser(
tokenIn,
address(this),
amountToken,
tokensDecimals
);

IERC20(tokenIn).safeIncreaseAllowance(address(pool), transferredAmount);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

safeIncreaseAllowance is deprecated in recent OpenZeppelin Contracts versions in favor of safeApprove. While its use here is not necessarily unsafe, switching to safeApprove would be more idiomatic and future-proof. To be absolutely safe against all token implementations, it's best practice to reset the allowance to 0 before setting a new value. This pattern should be applied across all new vault contracts in this PR that use safeIncreaseAllowance (DepositVaultWithMToken, DepositVaultWithMorpho, RedemptionVaultWithMToken).

        IERC20(tokenIn).safeApprove(address(pool), 0);
        IERC20(tokenIn).safeApprove(address(pool), transferredAmount);

pool.supply(tokenIn, transferredAmount, tokensReceiver, 0);
}
}
166 changes: 166 additions & 0 deletions contracts/DepositVaultWithMToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

import "./DepositVault.sol";
import "./interfaces/IDepositVault.sol";

/**
* @title DepositVaultWithMToken
* @notice Smart contract that handles mToken minting and invests
* proceeds into another mToken's DepositVault
* @dev If `mTokenDepositsEnabled` is false, regular deposit flow is used
* @author RedDuck Software
*/
contract DepositVaultWithMToken is DepositVault {
using DecimalsCorrectionLibrary for uint256;
using SafeERC20 for IERC20;

/**
* @notice Target mToken DepositVault for auto-invest
*/
IDepositVault public mTokenDepositVault;

/**
* @notice Whether mToken auto-invest deposits are enabled
* @dev if false, regular deposit flow will be used
*/
bool public mTokenDepositsEnabled;

/**
* @dev leaving a storage gap for futures updates
*/
uint256[50] private __gap;

/**
* @notice Emitted when the mToken DepositVault address is updated
* @param caller address of the caller
* @param newVault new mToken DepositVault address
*/
event SetMTokenDepositVault(
address indexed caller,
address indexed newVault
);

/**
* @notice Emitted when `mTokenDepositsEnabled` flag is updated
* @param enabled Whether mToken deposits are enabled
*/
event SetMTokenDepositsEnabled(bool indexed enabled);

/**
* @notice upgradeable pattern contract`s initializer
* @param _ac address of MidasAccessControll contract
* @param _mTokenInitParams init params for mToken
* @param _receiversInitParams init params for receivers
* @param _instantInitParams init params for instant operations
* @param _sanctionsList address of sanctionsList contract
* @param _variationTolerance percent of prices diviation 1% = 100
* @param _minAmount basic min amount for operations in mToken
* @param _minMTokenAmountForFirstDeposit min amount for first deposit in mToken
* @param _maxSupplyCap max supply cap for mToken
* @param _mTokenDepositVault target mToken DepositVault address
*/
function initialize(
address _ac,
MTokenInitParams calldata _mTokenInitParams,
ReceiversInitParams calldata _receiversInitParams,
InstantInitParams calldata _instantInitParams,
address _sanctionsList,
uint256 _variationTolerance,
uint256 _minAmount,
uint256 _minMTokenAmountForFirstDeposit,
uint256 _maxSupplyCap,
address _mTokenDepositVault
) external {
initialize(
_ac,
_mTokenInitParams,
_receiversInitParams,
_instantInitParams,
_sanctionsList,
_variationTolerance,
_minAmount,
_minMTokenAmountForFirstDeposit,
_maxSupplyCap
);

_validateAddress(_mTokenDepositVault, false);
mTokenDepositVault = IDepositVault(_mTokenDepositVault);
}

/**
* @notice Sets the target mToken DepositVault address
* @param _mTokenDepositVault new mToken DepositVault address
*/
function setMTokenDepositVault(address _mTokenDepositVault)
external
onlyVaultAdmin
{
require(
_mTokenDepositVault != address(mTokenDepositVault),
"DVMT: already set"
);
_validateAddress(_mTokenDepositVault, false);
mTokenDepositVault = IDepositVault(_mTokenDepositVault);
emit SetMTokenDepositVault(msg.sender, _mTokenDepositVault);
}

/**
* @notice Updates `mTokenDepositsEnabled` value
* @param enabled whether mToken auto-invest deposits are enabled
*/
function setMTokenDepositsEnabled(bool enabled) external onlyVaultAdmin {
mTokenDepositsEnabled = enabled;
emit SetMTokenDepositsEnabled(enabled);
}

/**
* @dev overrides original transfer to tokens receiver function
* in case of mToken deposits are disabled, it will act as the original transfer
* otherwise it will take payment tokens from user, deposit them into the target
* mToken DepositVault and forward received mTokens to tokens receiver
* @param tokenIn token address
* @param amountToken amount of tokens to transfer in base18
* @param tokensDecimals decimals of tokens
*/
function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
) internal override {
if (!mTokenDepositsEnabled) {
return
super._instantTransferTokensToTokensReceiver(
tokenIn,
amountToken,
tokensDecimals
);
}

uint256 transferredAmount = _tokenTransferFromUser(
tokenIn,
address(this),
amountToken,
tokensDecimals
);

IERC20(tokenIn).safeIncreaseAllowance(
address(mTokenDepositVault),
transferredAmount
);
Comment on lines +211 to +214
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using safeIncreaseAllowance can be problematic if a previous transaction that set an allowance failed, leaving a non-zero allowance. A more robust pattern is to reset the allowance to zero before setting the new value. This prevents unexpected behavior from leftover allowances.

        IERC20(tokenIn).safeApprove(address(mTokenDepositVault), 0);
        IERC20(tokenIn).safeApprove(address(mTokenDepositVault), transferredAmount);


IERC20 targetMToken = IERC20(address(mTokenDepositVault.mToken()));
uint256 balanceBefore = targetMToken.balanceOf(address(this));

mTokenDepositVault.depositInstant(tokenIn, amountToken, 0, bytes32(0));

uint256 mTokenReceived = targetMToken.balanceOf(address(this)) -
balanceBefore;
require(mTokenReceived > 0, "DVMT: zero mToken received");

targetMToken.safeTransfer(tokensReceiver, mTokenReceived);
}
}
Loading
Loading