Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 21 additions & 3 deletions contracts/DepositVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,8 @@ contract DepositVault is ManageableVault, IDepositVault {

calcResult = _calcAndValidateDeposit(user, tokenIn, amountToken, false);

_tokenTransferFromUser(
_requestTransferTokensToTokensReceiver(
tokenIn,
tokensReceiver,
calcResult.amountTokenWithoutFee,
calcResult.tokenDecimals
);
Expand Down Expand Up @@ -609,7 +608,7 @@ contract DepositVault is ManageableVault, IDepositVault {
}

/**
* @dev internal transfer tokens to tokens receiver
* @dev internal transfer tokens to tokens receiver (instant deposits)
* @param tokenIn tokenIn address
* @param amountToken amount of tokenIn (decimals 18)
* @param tokensDecimals tokens decimals
Expand All @@ -627,6 +626,25 @@ contract DepositVault is ManageableVault, IDepositVault {
);
}

/**
* @dev internal transfer tokens to tokens receiver (deposit requests)
* @param tokenIn tokenIn address
* @param amountToken amount of tokenIn (decimals 18)
* @param tokensDecimals tokens decimals
*/
function _requestTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
) internal virtual {
_tokenTransferFromUser(
tokenIn,
tokensReceiver,
amountToken,
tokensDecimals
);
}

/**
* @dev validate deposit and calculate mint amount
* @param user user address
Expand Down
201 changes: 201 additions & 0 deletions contracts/DepositVaultWithAave.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// 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;

/**
* @notice Whether to fall back to raw token transfer on auto-invest failure
* @dev if false, the transaction will revert when auto-invest fails
*/
bool public autoInvestFallbackEnabled;

/**
* @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 Emitted when `autoInvestFallbackEnabled` flag is updated
* @param enabled Whether fallback to raw transfer is enabled
*/
event SetAutoInvestFallbackEnabled(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, true);
_validateAddress(_aavePool, true);
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);
}

/**
* @notice Updates `autoInvestFallbackEnabled` value
* @param enabled whether fallback to raw transfer is enabled on auto-invest failure
*/
function setAutoInvestFallbackEnabled(bool enabled)
external
onlyVaultAdmin
{
autoInvestFallbackEnabled = enabled;
emit SetAutoInvestFallbackEnabled(enabled);
}

/**
* @dev overrides instant deposit transfer hook to auto-invest into Aave
*/
function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
) internal override {
IAaveV3Pool pool = aavePools[tokenIn];
if (!aaveDepositsEnabled || address(pool) == address(0)) {
return
super._instantTransferTokensToTokensReceiver(
tokenIn,
amountToken,
tokensDecimals
);
}

_autoInvest(tokenIn, amountToken, tokensDecimals, pool);
}

/**
* @dev overrides request deposit transfer hook to auto-invest into Aave
*/
function _requestTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
) internal override {
IAaveV3Pool pool = aavePools[tokenIn];
if (!aaveDepositsEnabled || address(pool) == address(0)) {
return
super._requestTransferTokensToTokensReceiver(
tokenIn,
amountToken,
tokensDecimals
);
}

_autoInvest(tokenIn, amountToken, tokensDecimals, pool);
}

/**
* @dev Transfers tokens from user to this contract and supplies them
* to the Aave V3 Pool. On failure, either falls back to raw transfer
* or reverts based on `autoInvestFallbackEnabled`.
* @param tokenIn token address
* @param amountToken amount of tokens to transfer in base18
* @param tokensDecimals decimals of tokens
* @param pool Aave V3 Pool
*/
function _autoInvest(
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals,
IAaveV3Pool pool
) private {
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);


try
pool.supply(tokenIn, transferredAmount, tokensReceiver, 0)
{} catch {
if (autoInvestFallbackEnabled) {
IERC20(tokenIn).safeApprove(address(pool), 0);
IERC20(tokenIn).safeTransfer(tokensReceiver, transferredAmount);
} else {
revert("DVA: auto-invest failed");
}
}
}
}
Loading
Loading