Skip to content

Commit

Permalink
feat: update tests for v3.1 (#142)
Browse files Browse the repository at this point in the history
* feat: pendle and mellow adapters for v3.1

* fix: prohibit deposits with disallowed underlyings, even for 0 amounts

* chore: bump pragmas

* feat: update LiveTestHelper for v3.1

* fix: fix test adapter deployment

* fix: add missing config params

* fix: fix missing name param

* fix: update test config

* feat: mellow erc4626 adapter

* chore: contract pragma and license year

* chore: update test config

* chore: update test config with adapters

* Update config
  • Loading branch information
Van0k authored Oct 2, 2024
1 parent d427a3a commit 27a9b7f
Show file tree
Hide file tree
Showing 38 changed files with 2,698 additions and 5,842 deletions.
9 changes: 9 additions & 0 deletions config_scripts/test_wethMainnetScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {
PoolV3CoreConfigurator,
testWethConfigMainnet,

Check failure on line 3 in config_scripts/test_wethMainnetScript.ts

View workflow job for this annotation

GitHub Actions / release

'"@gearbox-protocol/sdk-gov"' has no exported member named 'testWethConfigMainnet'. Did you mean 'wethConfigMainnet'?
} from "@gearbox-protocol/sdk-gov";

const poolCfg = PoolV3CoreConfigurator.new(testWethConfigMainnet);
console.error(poolCfg.toString());

console.log(poolCfg.deployConfig());
8 changes: 7 additions & 1 deletion contracts/adapters/erc4626/ERC4626Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import {IERC4626Adapter} from "../../interfaces/erc4626/IERC4626Adapter.sol";
/// @title ERC4626 Vault adapter
/// @notice Implements logic allowing CAs to interact with any standard-compliant ERC4626 vault
contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter {
bytes32 public constant override contractType = "AD_ERC4626_VAULT";
uint256 public constant override version = 3_10;

/// @notice Address of the underlying asset of the vault
address public immutable override asset;

function contractType() external pure virtual override returns (bytes32) {
return "AD_ERC4626_VAULT";
}

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _vault ERC4626 vault address
Expand Down Expand Up @@ -90,6 +93,7 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter {
/// @dev `receiver` and `owner` are ignored, since they are always set to the credit account address
function withdraw(uint256 assets, address, address)
external
virtual
override
creditFacadeOnly // U:[TV-2]
returns (bool)
Expand All @@ -104,6 +108,7 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter {
/// @dev `receiver` and `owner` are ignored, since they are always set to the credit account address
function redeem(uint256 shares, address, address)
external
virtual
override
creditFacadeOnly // U:[TV-2]
returns (bool)
Expand All @@ -117,6 +122,7 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter {
/// @param leftoverAmount Amount of vault token to keep on the account
function redeemDiff(uint256 leftoverAmount)
external
virtual
override
creditFacadeOnly // U:[TV-2]
returns (bool)
Expand Down
34 changes: 34 additions & 0 deletions contracts/adapters/mellow/Mellow4626VaultAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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 {ERC4626Adapter} from "../erc4626/ERC4626Adapter.sol";
import {NotImplementedException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol";

/// @title Mellow ERC4626 Vault adapter
/// @notice Implements logic allowing CAs to interact with a ERC4626 vaults, but with `withdraw` / `redeem` restricted, to avoid
/// CA's being exposed to Mellow's asynchronous withdrawals
contract Mellow4626VaultAdapter is ERC4626Adapter {
bytes32 public constant override contractType = "AD_MELLOW_ERC4626_VAULT";

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _vault ERC4626 vault address
constructor(address _creditManager, address _vault) ERC4626Adapter(_creditManager, _vault) {}

/// @dev For Mellow ERC4626 vaults all withdrawals revert to avoid CA's interacting with Mellow's delayed withdrawals
function withdraw(uint256, address, address) external view override creditFacadeOnly returns (bool) {
revert NotImplementedException();
}

/// @dev For Mellow ERC4626 vaults all withdrawals revert to avoid CA's interacting with Mellow's delayed withdrawals
function redeem(uint256, address, address) external view override creditFacadeOnly returns (bool) {
revert NotImplementedException();
}

/// @dev For Mellow ERC4626 vaults all withdrawals revert to avoid CA's interacting with Mellow's delayed withdrawals
function redeemDiff(uint256) external view override creditFacadeOnly returns (bool) {
revert NotImplementedException();
}
}
188 changes: 188 additions & 0 deletions contracts/adapters/mellow/MellowVaultAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// 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 {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {RAY} from "@gearbox-protocol/core-v3/contracts/libraries/Constants.sol";
import {AbstractAdapter} from "../AbstractAdapter.sol";

import {IMellowVaultAdapter, MellowUnderlyingStatus} from "../../interfaces/mellow/IMellowVaultAdapter.sol";
import {IMellowVault} from "../../integrations/mellow/IMellowVault.sol";

/// @title Mellow vault adapter
/// @notice Implements logic for interacting with the Mellow vault contract (deposits only)
contract MellowVaultAdapter is AbstractAdapter, IMellowVaultAdapter {
using EnumerableSet for EnumerableSet.AddressSet;

bytes32 public constant override contractType = "AD_MELLOW_LRT_VAULT";
uint256 public constant override version = 3_10;

/// @dev Set of allowed underlying addresses
EnumerableSet.AddressSet internal _allowedUnderlyings;

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _mellowVault Mellow vault address
constructor(address _creditManager, address _mellowVault) AbstractAdapter(_creditManager, _mellowVault) {
_getMaskOrRevert(_mellowVault); // U:[MEL-1]
}

/// @notice Deposits specified amounts of tokens into the vault in exchange for LP tokens.
/// @param amounts An array specifying the amounts for each underlying token.
/// @param minLpAmount The minimum amount of LP tokens to mint.
/// @param deadline The time before which the operation must be completed.
/// @notice `to` is ignored as the recipient is always the credit account
function deposit(address, uint256[] memory amounts, uint256 minLpAmount, uint256 deadline)
external
creditFacadeOnly // U:[MEL-2]
returns (bool)
{
address creditAccount = _creditAccount();

address[] memory underlyings = IMellowVault(targetContract).underlyingTokens();

uint256 len = underlyings.length;

if (amounts.length != len) revert IncorrectArrayLengthException(); // U:[MEL-3]

for (uint256 i = 0; i < len;) {
if (!_allowedUnderlyings.contains(underlyings[i])) {
revert UnderlyingNotAllowedException(underlyings[i]); // U:[MEL-3]
}

unchecked {
++i;
}
}

_approveAssets(underlyings, amounts, type(uint256).max);
_execute(abi.encodeCall(IMellowVault.deposit, (creditAccount, amounts, minLpAmount, deadline))); // U:[MEL-3]
_approveAssets(underlyings, amounts, 1);

return true;
}

/// @notice Deposits a specififed amount of one underlying into the vault in exchange for LP tokens.
/// @param asset The asset to deposit
/// @param amount Amount of underlying to deposit.
/// @param minLpAmount The minimum amount of LP tokens to mint.
/// @param deadline The time before which the operation must be completed.
function depositOneAsset(address asset, uint256 amount, uint256 minLpAmount, uint256 deadline)
external
creditFacadeOnly
returns (bool)
{
if (!_allowedUnderlyings.contains(asset)) revert UnderlyingNotAllowedException(asset); // U:[MEL-4]

address creditAccount = _creditAccount();

return _depositOneAsset(creditAccount, asset, amount, minLpAmount, deadline); // U:[MEL-4]
}

/// @notice Deposits the entire balance of one underlying, except the specified amount, into the vault in exchange for LP tokens.
/// @param asset The asset to deposit
/// @param leftoverAmount Amount of underlying to leave on the Credit Account.
/// @param rateMinRAY The minimum exchange rate between the deposited asset and LP, in 1e27 format.
/// @param deadline The time before which the operation must be completed.
function depositOneAssetDiff(address asset, uint256 leftoverAmount, uint256 rateMinRAY, uint256 deadline)
external
creditFacadeOnly
returns (bool)
{
if (!_allowedUnderlyings.contains(asset)) revert UnderlyingNotAllowedException(asset); // U:[MEL-5]

address creditAccount = _creditAccount();

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

if (amount <= leftoverAmount) return false;

unchecked {
amount = amount - leftoverAmount;
}

return _depositOneAsset(creditAccount, asset, amount, amount * rateMinRAY / RAY, deadline); // U:[MEL-5]
}

/// @dev Internal implementation for `depositOneAsset` and `depositOneAssetDiff`
function _depositOneAsset(
address creditAccount,
address asset,
uint256 amount,
uint256 minLpAmount,
uint256 deadline
) internal returns (bool) {
address[] memory underlyings = IMellowVault(targetContract).underlyingTokens();
uint256 len = underlyings.length;

uint256[] memory amounts = new uint256[](len);

for (uint256 i = 0; i < len;) {
if (underlyings[i] == asset) {
amounts[i] = amount;
break;
}

if (i == len - 1) revert UnderlyingNotFoundException(asset); // U:[MEL-4,5]

unchecked {
++i;
}
}

_approveToken(asset, type(uint256).max);
_execute(abi.encodeCall(IMellowVault.deposit, (creditAccount, amounts, minLpAmount, deadline))); // U:[MEL-4,5]
_approveToken(asset, 1);
return true;
}

/// @dev Internal function that changes approval for a batch of assets
function _approveAssets(address[] memory assets, uint256[] memory filter, uint256 amount) internal {
uint256 len = assets.length;

unchecked {
for (uint256 i = 0; i < len; ++i) {
if (filter[i] > 1) _approveToken(assets[i], amount);
}
}
}

// ---- //
// DATA //
// ---- //

/// @notice Returns the list of allowed underlyings
function allowedUnderlyings() public view returns (address[] memory) {
return _allowedUnderlyings.values();
}

/// @notice Serialized adapter parameters
function serialize() external view returns (bytes memory serializedData) {
serializedData = abi.encode(creditManager, targetContract, allowedUnderlyings());
}

// ------------- //
// CONFIGURATION //
// ------------- //

/// @notice Changes the allowed status of several underlyings
function setUnderlyingStatusBatch(MellowUnderlyingStatus[] calldata underlyings)
external
override
configuratorOnly // U:[MEL-6]
{
uint256 len = underlyings.length;
for (uint256 i; i < len; ++i) {
if (underlyings[i].allowed) {
_getMaskOrRevert(underlyings[i].underlying);
_allowedUnderlyings.add(underlyings[i].underlying);
} else {
_allowedUnderlyings.remove(underlyings[i].underlying);
}
emit SetUnderlyingStatus(underlyings[i].underlying, underlyings[i].allowed); // U:[MEL-6]
}
}
}
Loading

0 comments on commit 27a9b7f

Please sign in to comment.