Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
5 changes: 4 additions & 1 deletion config/constants/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export type RedemptionVaultType =
| 'redemptionVault'
| 'redemptionVaultBuidl'
| 'redemptionVaultSwapper'
| 'redemptionVaultUstb';
| 'redemptionVaultMToken'
| 'redemptionVaultUstb'
| 'redemptionVaultAave'
| 'redemptionVaultMorpho';

export type DepositVaultType = 'depositVault' | 'depositVaultUstb';

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

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

import "./RedemptionVault.sol";

import "./interfaces/aave/IAaveV3Pool.sol";
import "./libraries/DecimalsCorrectionLibrary.sol";

/**
* @title RedemptionVaultWithAave
* @notice Smart contract that handles redemptions using Aave V3 Pool withdrawals
* @dev When the vault has insufficient payment token balance, it withdraws from
* an Aave V3 Pool by burning its aTokens to obtain the underlying asset.
* @author RedDuck Software
*/
contract RedemptionVaultWithAave is RedemptionVault {
using DecimalsCorrectionLibrary for uint256;

/**
* @notice Aave V3 Pool contract used for withdrawals
*/
IAaveV3Pool public aavePool;

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

/**
* @notice Emitted when the Aave Pool address is updated
* @param caller address of the caller
* @param newPool new Aave Pool address
*/
event SetAavePool(address indexed caller, address indexed newPool);

/**
* @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
* @param _fiatRedemptionInitParams params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount
* @param _requestRedeemer address is designated for standard redemptions, allowing tokens to be pulled from this address
* @param _aavePool Aave V3 Pool contract address
*/
function initialize(
address _ac,
MTokenInitParams calldata _mTokenInitParams,
ReceiversInitParams calldata _receiversInitParams,
InstantInitParams calldata _instantInitParams,
address _sanctionsList,
uint256 _variationTolerance,
uint256 _minAmount,
FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
address _requestRedeemer,
address _aavePool
) external initializer {
__RedemptionVault_init(
_ac,
_mTokenInitParams,
_receiversInitParams,
_instantInitParams,
_sanctionsList,
_variationTolerance,
_minAmount,
_fiatRedemptionInitParams,
_requestRedeemer
);
_validateAddress(_aavePool, false);
aavePool = IAaveV3Pool(_aavePool);
}

/**
* @notice Sets the Aave V3 Pool address
* @param _aavePool new Aave V3 Pool address
*/
function setAavePool(address _aavePool) external onlyVaultAdmin {
_validateAddress(_aavePool, false);
aavePool = IAaveV3Pool(_aavePool);
emit SetAavePool(msg.sender, _aavePool);
}

/**
* @dev Redeem mToken to the selected payment token if daily limit and allowance are not exceeded.
* If the contract doesn't have enough payment token, the Aave V3 withdrawal flow will be
* triggered to withdraw the missing amount from the Aave Pool.
* Burns mToken from the user.
* Transfers fee in mToken to feeReceiver.
* Transfers tokenOut to user.
* @param tokenOut token out address
* @param amountMTokenIn amount of mToken to redeem
* @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
* @param recipient address that will receive the tokenOut
*/
function _redeemInstant(
address tokenOut,
uint256 amountMTokenIn,
uint256 minReceiveAmount,
address recipient
)
internal
override
returns (
CalcAndValidateRedeemResult memory calcResult,
uint256 amountTokenOutWithoutFee
)
{
address user = msg.sender;

calcResult = _calcAndValidateRedeem(
user,
tokenOut,
amountMTokenIn,
true,
false
);

_requireAndUpdateLimit(amountMTokenIn);

uint256 tokenDecimals = _tokenDecimals(tokenOut);

uint256 amountMTokenInCopy = amountMTokenIn;
address tokenOutCopy = tokenOut;
uint256 minReceiveAmountCopy = minReceiveAmount;
Comment on lines +118 to +120
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

The local variables amountMTokenInCopy, tokenOutCopy, and minReceiveAmountCopy are initialized with the values of the function parameters and then used in place of them. Since the original parameters are not modified, these copies are redundant. Removing them will improve code clarity and provide minor gas savings. The original parameters amountMTokenIn, tokenOut, and minReceiveAmount can be used directly throughout the function. This also applies to RedemptionVaultWithMToken and RedemptionVaultWithMorpho.


(uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
amountMTokenInCopy
);
(uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
amountMTokenInUsd,
tokenOutCopy
);

_requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);

mToken.burn(user, calcResult.amountMTokenWithoutFee);
if (calcResult.feeAmount > 0)
_tokenTransferFromUser(
address(mToken),
feeReceiver,
calcResult.feeAmount,
18
);

uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult
.amountMTokenWithoutFee * mTokenRate) / tokenOutRate)
.convertFromBase18(tokenDecimals);

amountTokenOutWithoutFee = amountTokenOutWithoutFeeFrom18
.convertToBase18(tokenDecimals);

require(
amountTokenOutWithoutFee >= minReceiveAmountCopy,
"RVA: minReceiveAmount > actual"
);

_checkAndRedeemAave(tokenOutCopy, amountTokenOutWithoutFeeFrom18);

_tokenTransferToUser(
tokenOutCopy,
recipient,
amountTokenOutWithoutFee,
tokenDecimals
);
}
Comment on lines +91 to +161
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

The _redeemInstant function contains a large block of code that is duplicated across RedemptionVaultWithAave, RedemptionVaultWithMorpho, and RedemptionVaultWithMToken. This duplication makes the code harder to maintain, as any change to the core redemption logic needs to be applied in multiple places. Consider refactoring this common logic into a shared internal function within the base RedemptionVault contract, perhaps using a hook that child contracts can override for their specific withdrawal logic. For example, an internal function like _prepareRedemption could handle the common calculations and checks, returning the necessary values to the caller.


/**
* @notice Check if contract has enough tokenOut balance for redeem;
* if not, withdraw the missing amount from the Aave V3 Pool
* @dev The Aave Pool burns the vault's aTokens and transfers the underlying
* asset directly to this contract. No approval is needed because the Pool
* burns aTokens from msg.sender (this contract) internally.
* @param tokenOut tokenOut address
* @param amountTokenOut amount of tokenOut needed
*/
function _checkAndRedeemAave(address tokenOut, uint256 amountTokenOut)
internal
{
uint256 contractBalanceTokenOut = IERC20(tokenOut).balanceOf(
address(this)
);
if (contractBalanceTokenOut >= amountTokenOut) return;

uint256 missingAmount = amountTokenOut - contractBalanceTokenOut;

address aToken = aavePool.getReserveAToken(tokenOut);
require(aToken != address(0), "RVA: token not in Aave pool");

uint256 aTokenBalance = IERC20(aToken).balanceOf(address(this));
require(
aTokenBalance >= missingAmount,
"RVA: insufficient aToken balance"
);

uint256 withdrawnAmount = aavePool.withdraw(
tokenOut,
missingAmount,
address(this)
);
require(
withdrawnAmount >= missingAmount,
"RVA: insufficient withdrawal amount"
);
}
}
Loading