Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7f80feb
feat: attach suite
lekhovitsky Jul 29, 2025
306f8d8
Merge remote-tracking branch 'origin/next' into attach-suite
lekhovitsky Jul 31, 2025
e5062be
chore: script to upload Mellow MultiVault and Kodiak bytecode
lekhovitsky Aug 2, 2025
e5b15f5
chore: fix commit in script
lekhovitsky Aug 4, 2025
cfdd4d0
Merge remote-tracking branch 'origin/main' into attach-suite
lekhovitsky Oct 13, 2025
bcf8e19
Merge remote-tracking branch 'origin/main' into attach-suite
lekhovitsky Oct 13, 2025
d2c3be2
chore: fix `.gitmodules`
lekhovitsky Oct 13, 2025
42ad36f
Merge remote-tracking branch 'origin/pendle-balancer-lp' into attach-…
lekhovitsky Oct 22, 2025
cfa1eaa
chore: script to upload Pendle and Balancer v3.1.1 integrations
lekhovitsky Oct 22, 2025
8d2dade
Merge remote-tracking branches 'origin' and 'origin/pendle-balancer-l…
lekhovitsky Oct 22, 2025
c70f7b0
fix: upload latest pendle router version
lekhovitsky Oct 22, 2025
77095f0
Merge remote-tracking branch 'origin/midas' into attach-suite
lekhovitsky Oct 31, 2025
315f822
chore: script to upload Midas contracts
lekhovitsky Oct 31, 2025
d7e86af
Merge remote-tracking branch 'origin/main' into attach-suite
lekhovitsky Nov 19, 2025
9823062
chore: script to upload Infinifi and Uniswap V4 contracts
lekhovitsky Nov 19, 2025
16ecdff
Merge pull request #176 from Gearbox-protocol/upshift-diff
lekhovitsky Dec 3, 2025
f8d8586
chore: script to upload updated Upshift adapter
lekhovitsky Dec 3, 2025
8d7151f
feat: Kelp LRT integration
lekhovitsky Dec 3, 2025
e8c5609
test: add Kelp unit tests
lekhovitsky Dec 3, 2025
c140467
fix: savant fixes
Van0k Nov 27, 2025
55dbc85
Merge pull request #178 from Gearbox-protocol/kelp-lrt
lekhovitsky Dec 3, 2025
bdc69ec
fix: upshift gateway correctly handles lagDuration == 0 ignoring time…
Van0k Dec 16, 2025
55ca01e
Merge pull request #185 from Gearbox-protocol/upshift-fix
lekhovitsky Dec 17, 2025
87bae4f
chore: script to upload updated Upshift gateway
lekhovitsky Dec 17, 2025
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
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ dist/
**/*.generated.*
**/generated/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
# Broadcast logs
/broadcast

# Docs
docs/
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "lib/@1inch/farming"]
path = lib/@1inch/farming
url = https://github.com/1inch/farming
[submodule "lib/@gearbox-protocol/permissionless"]
path = lib/@gearbox-protocol/permissionless
url = https://github.com/Gearbox-protocol/permissionless
124 changes: 124 additions & 0 deletions contracts/adapters/kelp/KelpLRTDepositPoolAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 {IKelpLRTDepositPoolAdapter} from "../../interfaces/kelp/IKelpLRTDepositPoolAdapter.sol";
import {IKelpLRTDepositPoolGateway} from "../../interfaces/kelp/IKelpLRTDepositPoolGateway.sol";

/// @title Kelp LRTDepositPool adapter
/// @notice Implements logic for interacting with the Kelp LRTDepositPool contract
contract KelpLRTDepositPoolAdapter is AbstractAdapter, IKelpLRTDepositPoolAdapter {
using EnumerableSet for EnumerableSet.AddressSet;

bytes32 public constant override contractType = "ADAPTER::KELP_DEPOSIT_POOL";
uint256 public constant override version = 3_10;

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

/// @notice Referral ID for the adapter
string public override referralId;

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _depositPoolGateway Deposit pool gateway address
constructor(address _creditManager, address _depositPoolGateway, string memory _referralId)
AbstractAdapter(_creditManager, _depositPoolGateway)
{
address rsETH = IKelpLRTDepositPoolGateway(_depositPoolGateway).rsETH();
referralId = _referralId;
_getMaskOrRevert(rsETH);
}

/// @notice Deposits an asset into rsETH
/// @param asset Asset to deposit
/// @param amount Amount of asset to deposit
/// @param minRSETHAmountExpected Minimum amount of rsETH to receive
/// @dev `referralId` is ignored as it is hardcoded
function depositAsset(address asset, uint256 amount, uint256 minRSETHAmountExpected, string calldata)
external
creditFacadeOnly
returns (bool)
{
if (!_allowedAssets.contains(asset)) revert AssetNotAllowedException(asset);

_depositAsset(asset, amount, minRSETHAmountExpected);
return true;
}

/// @notice Deposits the entire balance of the asset into rsETH, except the specified amount
/// @param asset Asset to deposit
/// @param leftoverAmount Amount of asset to leave on the credit account
/// @param minRateRAY Minimum rate of rsETH to receive
function depositAssetDiff(address asset, uint256 leftoverAmount, uint256 minRateRAY)
external
creditFacadeOnly
returns (bool)
{
if (!_allowedAssets.contains(asset)) revert AssetNotAllowedException(asset);

address creditAccount = _creditAccount();

uint256 amount = IERC20(asset).balanceOf(creditAccount);
if (amount < leftoverAmount) return false;

unchecked {
amount -= leftoverAmount;
}

_depositAsset(asset, amount, amount * minRateRAY / RAY);
return true;
}

/// @notice Internal implementation of the `depositAsset` function
function _depositAsset(address asset, uint256 amount, uint256 minRSETHAmountExpected) internal {
_executeSwapSafeApprove(
asset,
abi.encodeCall(IKelpLRTDepositPoolGateway.depositAsset, (asset, amount, minRSETHAmountExpected, referralId))
);
}

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

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

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

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

/// @notice Changes the allowed status of several assets
function setAssetStatusBatch(address[] calldata assets, bool[] calldata allowed)
external
override
configuratorOnly // U:[MEL-6]
{
uint256 len = assets.length;
if (len != allowed.length) revert IncorrectArrayLengthException();
for (uint256 i; i < len; ++i) {
if (allowed[i]) {
_getMaskOrRevert(assets[i]);
_allowedAssets.add(assets[i]);
} else {
_allowedAssets.remove(assets[i]);
}
emit SetAssetStatus(assets[i], allowed[i]);
}
}
}
181 changes: 181 additions & 0 deletions contracts/adapters/kelp/KelpLRTWithdrawalManagerAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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 {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 {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
import {IPoolV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolV3.sol";

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

import {
IKelpLRTWithdrawalManagerAdapter,
TokenOutStatus
} from "../../interfaces/kelp/IKelpLRTWithdrawalManagerAdapter.sol";
import {IKelpLRTWithdrawalManagerGateway} from "../../interfaces/kelp/IKelpLRTWithdrawalManagerGateway.sol";
import {KelpLRTWithdrawalPhantomToken} from "../../helpers/kelp/KelpLRTWithdrawalPhantomToken.sol";

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

/// @title Kelp LRT Withdrawal Manager adapter
/// @notice Implements logic for interacting with the Kelp LRT Withdrawal Manager
contract KelpLRTWithdrawalManagerAdapter is AbstractAdapter, IKelpLRTWithdrawalManagerAdapter {
using EnumerableSet for EnumerableSet.AddressSet;

bytes32 public constant override contractType = "ADAPTER::KELP_WITHDRAWAL";
uint256 public constant override version = 3_10;

/// @notice The set of allowed tokens out
EnumerableSet.AddressSet private _allowedTokensOut;

/// @notice The withdrawal manager gateway
address public immutable withdrawalManagerGateway;

/// @notice The referral ID
string public referralId;

/// @notice The rsETH token
address public immutable rsETH;

/// @notice The mapping of token out to phantom token
mapping(address => address) public tokenOutToPhantomToken;

/// @notice The mapping of phantom token to token out
mapping(address => address) public phantomTokenToTokenOut;

/// @notice Constructor
constructor(address _creditManager, address _withdrawalManagerGateway, string memory _referralId)
AbstractAdapter(_creditManager, _withdrawalManagerGateway)
{
withdrawalManagerGateway = _withdrawalManagerGateway;
referralId = _referralId;
rsETH = IKelpLRTWithdrawalManagerGateway(withdrawalManagerGateway).rsETH();
_getMaskOrRevert(rsETH);
}

/// @notice Initiates a withdrawal for a specific amount of assets
/// @param asset The asset to withdraw
/// @param amount The amount of rsETH to redeem
function initiateWithdrawal(address asset, uint256 amount, string calldata)
external
override
creditFacadeOnly
returns (bool)
{
if (!_allowedTokensOut.contains(asset)) revert TokenNotAllowedException();
_initiateWithdrawal(asset, amount);
return true;
}

/// @notice Initiates a withdrawal for a specific amount of assets, except the specified amount
/// @param asset The asset to withdraw
/// @param leftoverAmount The amount of rsETH to leave on the credit account
function initiateWithdrawalDiff(address asset, uint256 leftoverAmount)
external
override
creditFacadeOnly
returns (bool)
{
if (!_allowedTokensOut.contains(asset)) revert TokenNotAllowedException();

address creditAccount = _creditAccount();

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

if (amount < leftoverAmount) return false;

unchecked {
amount -= leftoverAmount;
}

_initiateWithdrawal(asset, amount);
return true;
}

/// @notice Claims a specific amount of assets from completed withdrawals
/// @param asset The asset to withdraw
/// @param amount The amount of asset to claim
function completeWithdrawal(address asset, uint256 amount, string calldata)
external
override
creditFacadeOnly
returns (bool)
{
if (!_allowedTokensOut.contains(asset)) revert TokenNotAllowedException();
_completeWithdrawal(asset, amount);
return false;
}

/// @notice Internal implementation for `initiateWithdrawal`
function _initiateWithdrawal(address asset, uint256 amount) internal {
_executeSwapSafeApprove(
rsETH, abi.encodeCall(IKelpLRTWithdrawalManagerGateway.initiateWithdrawal, (asset, amount, referralId))
);
}

/// @notice Internal implementation for `completeWithdrawal`
function _completeWithdrawal(address asset, uint256 amount) internal {
_execute(abi.encodeCall(IKelpLRTWithdrawalManagerGateway.completeWithdrawal, (asset, amount, referralId)));
}

/// @notice Withdraws phantom token for its underlying
function withdrawPhantomToken(address token, uint256 amount) external override creditFacadeOnly returns (bool) {
address asset = phantomTokenToTokenOut[token];
if (!_allowedTokensOut.contains(asset)) revert IncorrectStakedPhantomTokenException();
_completeWithdrawal(asset, amount);
return false;
}

/// @dev It's not possible to deposit from underlying (the vault's asset) into the withdrawal phantom token,
/// hence the function is not implemented.
function depositPhantomToken(address, uint256) external view override creditFacadeOnly returns (bool) {
revert NotImplementedException();
}

/// @notice Returns the list of allowed withdrawable tokens
function getAllowedTokensOut() public view returns (address[] memory tokens) {
return _allowedTokensOut.values();
}

/// @notice Returns the list of phantom tokens associated with allowed withdrawable tokens
function getPhantomTokensForAllowedTokensOut() public view returns (address[] memory phantomTokens) {
address[] memory tokens = getAllowedTokensOut();
phantomTokens = new address[](tokens.length);
for (uint256 i; i < tokens.length; ++i) {
phantomTokens[i] = tokenOutToPhantomToken[tokens[i]];
}
return phantomTokens;
}

/// @notice Returns the serialized adapter parameters
function serialize() external view returns (bytes memory) {
return abi.encode(creditManager, targetContract, getAllowedTokensOut(), getPhantomTokensForAllowedTokensOut());
}

/// @notice Sets the status of a batch of output tokens
function setTokensOutBatchStatus(TokenOutStatus[] calldata tokensOut) external configuratorOnly {
uint256 len = tokensOut.length;
for (uint256 i; i < len; ++i) {
if (tokensOut[i].allowed) {
_getMaskOrRevert(tokensOut[i].tokenOut);
_getMaskOrRevert(tokensOut[i].phantomToken);
if (KelpLRTWithdrawalPhantomToken(tokensOut[i].phantomToken).tokenOut() != tokensOut[i].tokenOut) {
revert IncorrectStakedPhantomTokenException();
}
_allowedTokensOut.add(tokensOut[i].tokenOut);
tokenOutToPhantomToken[tokensOut[i].tokenOut] = tokensOut[i].phantomToken;
phantomTokenToTokenOut[tokensOut[i].phantomToken] = tokensOut[i].tokenOut;
} else {
_allowedTokensOut.remove(tokensOut[i].tokenOut);
address phantomToken = tokenOutToPhantomToken[tokensOut[i].tokenOut];
delete tokenOutToPhantomToken[tokensOut[i].tokenOut];
delete phantomTokenToTokenOut[phantomToken];
}
}
}
}
Loading