Skip to content

Commit

Permalink
feat: refactor token wrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyonline committed Sep 23, 2024
1 parent 02260f0 commit cc5c202
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 65 deletions.
19 changes: 19 additions & 0 deletions src/interfaces/IWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: BSD 3-Clause License
pragma solidity ^0.8.24;

import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

interface IWrapper {

/// @notice Wraps the given amount of the given token.
/// @param _amount The amount to wrap.
/// @param _token The token to wrap.
/// @return The amount of wrapped tokens and the wrapped token.
function wrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20);

/// @notice Unwraps the given amount of the given token.
/// @param _amount The amount to unwrap.
/// @param _token The token to unwrap.
/// @return The amount of unwrapped tokens and the unwrapped token.
function unwrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20);
}
32 changes: 10 additions & 22 deletions src/ynEIGEN/EigenStrategyManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {IynEigen} from "src/interfaces/IynEigen.sol";
import {IwstETH} from "src/external/lido/IwstETH.sol";
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IWrapper} from "src/interfaces/IWrapper.sol";

interface IEigenStrategyManagerEvents {
event StrategyAdded(address indexed asset, address indexed strategy);
Expand Down Expand Up @@ -107,6 +108,7 @@ contract EigenStrategyManager is
IERC20 public stETH;

IRedemptionAssetsVaultExt public redemptionAssetsVault;
IWrapper public wrapper;

//--------------------------------------------------------------------------------------
//---------------------------------- INITIALIZATION ----------------------------------
Expand Down Expand Up @@ -166,11 +168,17 @@ contract EigenStrategyManager is

function initializeV2(
address _redemptionAssetsVault,
address _wrapper,
address _withdrawer
) external reinitializer(2) notZeroAddress(_redemptionAssetsVault) {
) external reinitializer(2) notZeroAddress(_redemptionAssetsVault) notZeroAddress(_wrapper) {
redemptionAssetsVault = IRedemptionAssetsVaultExt(_redemptionAssetsVault);
wrapper = IWrapper(_wrapper);

_grantRole(STAKING_NODES_WITHDRAWER_ROLE, _withdrawer);
_grantRole(WITHDRAWAL_MANAGER_ROLE, _withdrawer);

IERC20(address(wstETH)).forceApprove(address(_wrapper), type(uint256).max);
IERC20(address(woETH)).forceApprove(address(_wrapper), type(uint256).max);
}

//--------------------------------------------------------------------------------------
Expand Down Expand Up @@ -239,7 +247,7 @@ contract EigenStrategyManager is
uint256[] memory depositAmounts = new uint256[](amountsLength);

for (uint256 i = 0; i < assetsLength; i++) {
(IERC20 depositAsset, uint256 depositAmount) = toEigenLayerDeposit(assets[i], amounts[i]);
(uint256 depositAmount, IERC20 depositAsset) = wrapper.unwrap(amounts[i], assets[i]);
depositAssets[i] = depositAsset;
depositAmounts[i] = depositAmount;

Expand All @@ -254,26 +262,6 @@ contract EigenStrategyManager is
emit DepositedToEigenlayer(depositAssets, depositAmounts, strategiesForNode);
}

function toEigenLayerDeposit(
IERC20 asset,
uint256 amount
) internal returns (IERC20 depositAsset, uint256 depositAmount) {
if (address(asset) == address(wstETH)) {
// Adjust for wstETH
depositAsset = stETH;
depositAmount = wstETH.unwrap(amount);
} else if (address(asset) == address(woETH)) {
// Adjust for woeth
depositAsset = oETH;
// calling redeem with receiver and owner as address(this)
depositAmount = woETH.redeem(amount, address(this), address(this));
} else {
// No adjustment needed
depositAsset = asset;
depositAmount = amount;
}
}

//--------------------------------------------------------------------------------------
//---------------------------------- WITHDRAWALS -------------------------------------
//--------------------------------------------------------------------------------------
Expand Down
78 changes: 78 additions & 0 deletions src/ynEIGEN/LSDWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: BSD 3-Clause License
pragma solidity ^0.8.24;

import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import {IERC20, SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

import {IwstETH} from "src/external/lido/IwstETH.sol";
import {IWrapper} from "src/interfaces/IWrapper.sol";

contract LSDWrapper is IWrapper, Initializable {

using SafeERC20 for IERC20;

IERC20 public immutable wstETH;
IERC20 public immutable woETH;
IERC20 public immutable oETH;
IERC20 public immutable stETH;

// ============================================================================================
// Constructor
// ============================================================================================

constructor(address _wstETH, address _woETH, address _oETH, address _stETH) {
if (_wstETH == address(0) || _woETH == address(0) || _oETH == address(0) || _stETH == address(0)) {
revert ZeroAddress();
}

wstETH = IERC20(_wstETH);
woETH = IERC20(_woETH);
oETH = IERC20(_oETH);
stETH = IERC20(_stETH);
}

function initialize() external initializer {
stETH.forceApprove(address(wstETH), type(uint256).max);
oETH.forceApprove(address(woETH), type(uint256).max);
}

// ============================================================================================
// External functions
// ============================================================================================

/// @inheritdoc IWrapper
function wrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20) {
if (_token == stETH) {
stETH.safeTransferFrom(msg.sender, address(this), _amount);
_amount = IwstETH(address(wstETH)).wrap(_amount);
wstETH.safeTransfer(msg.sender, _amount);
return (_amount, wstETH);
} else if (_token == oETH) {
oETH.safeTransferFrom(msg.sender, address(this), _amount);
return (IERC4626(address(woETH)).deposit(_amount, msg.sender), woETH);
} else {
return (_amount, _token);
}
}

/// @inheritdoc IWrapper
function unwrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20) {
if (_token == wstETH) {
wstETH.safeTransferFrom(msg.sender, address(this), _amount);
_amount = IwstETH(address(wstETH)).unwrap(_amount);
stETH.safeTransfer(msg.sender, _amount);
return (_amount, stETH);
} else if (_token == woETH) {
return (IERC4626(address(woETH)).redeem(_amount, msg.sender, msg.sender), oETH);
} else {
return (_amount, _token);
}
}

// ============================================================================================
// Errors
// ============================================================================================

error ZeroAddress();
}
32 changes: 5 additions & 27 deletions src/ynEIGEN/TokenStakingNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfa
import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {ITokenStakingNode} from "src/interfaces/ITokenStakingNode.sol";
import {ITokenStakingNodesManager} from "src/interfaces/ITokenStakingNodesManager.sol";
import {IwstETH} from "src/external/lido/IwstETH.sol";
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import {IWrapper} from "src/interfaces/IWrapper.sol";

interface ITokenStakingNodeEvents {
event DepositToEigenlayer(
Expand All @@ -30,10 +29,7 @@ interface ITokenStakingNodeEvents {
}

interface IYieldNestStrategyManager {
function wstETH() external view returns (IwstETH);
function stETH() external view returns (IERC20);
function woETH() external view returns (IERC4626);
function oETH() external view returns (IERC20);
function wrapper() external view returns (IWrapper);
function isStakingNodesWithdrawer(address _address) external view returns (bool);
}

Expand Down Expand Up @@ -206,7 +202,9 @@ contract TokenStakingNode is
_expectedAmountOut - _actualAmountOut;
if (_delta > 2) revert WithdrawalAmountMismatch(); // @todo - might be a footgun

(_actualAmountOut, _token) = _wrapIfNeeded(_actualAmountOut, _token);
IWrapper _wrapper = IYieldNestStrategyManager(tokenStakingNodesManager.yieldNestStrategyManager()).wrapper();
IERC20(_token).forceApprove(address(_wrapper), _actualAmountOut); // NOTE: approving also token that will not be transferred
(_actualAmountOut, _token) = _wrapper.wrap(_actualAmountOut, _token);

queuedShares[_strategy] -= _shares;
withdrawn[_token] += _actualAmountOut;
Expand All @@ -222,26 +220,6 @@ contract TokenStakingNode is
emit DeallocatedTokens(_amount, _token);
}

function _wrapIfNeeded(uint256 _amount, IERC20 _token) internal returns (uint256, IERC20) {
IYieldNestStrategyManager _strategyManager =
IYieldNestStrategyManager(ITokenStakingNodesManager(address(tokenStakingNodesManager)).yieldNestStrategyManager());
IERC20 _stETH = _strategyManager.stETH();
IERC20 _oETH = _strategyManager.oETH();
if (_token == _stETH) {
IwstETH _wstETH = _strategyManager.wstETH();
_stETH.forceApprove(address(_wstETH), _amount);
uint256 _wstETHAmount = _wstETH.wrap(_amount);
return (_wstETHAmount, IERC20(_wstETH));
} else if (_token == _oETH) {
IERC4626 _woETH = _strategyManager.woETH();
_oETH.forceApprove(address(_woETH), _amount);
uint256 _woETHShares = _woETH.deposit(_amount, address(this));
return (_woETHShares, IERC20(_woETH));
} else {
return (_amount, _token);
}
}

//--------------------------------------------------------------------------------------
//---------------------------------- DELEGATION --------------------------------------
//--------------------------------------------------------------------------------------
Expand Down
15 changes: 11 additions & 4 deletions src/ynEIGEN/ynEigenDepositAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/
import {AccessControlUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";

import {IWrapper} from "src/interfaces/IWrapper.sol";

interface IynEigenDepositAdapterEvents {
event ReferralDepositProcessed(
address sender,
Expand Down Expand Up @@ -54,6 +56,7 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A
IERC4626 public woETH;
IERC20 public stETH;
IERC20 public oETH;
IWrapper public wrapper;

//--------------------------------------------------------------------------------------
//---------------------------------- INITIALIZATION ----------------------------------
Expand Down Expand Up @@ -84,6 +87,10 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A
oETH = IERC20(woETH.asset());
}

function initializeV2(address _wrapper) external reinitializer(2) notZeroAddress(_wrapper) {
wrapper = IWrapper(_wrapper);
}

/**
* @notice Handles the deposit of assets into the ynEigen system.
It supports all assets supported by ynEigen
Expand Down Expand Up @@ -159,8 +166,8 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A

function depositStETH(uint256 amount, address receiver) internal returns (uint256 shares) {
stETH.safeTransferFrom(msg.sender, address(this), amount);
stETH.forceApprove(address(wstETH), amount);
uint256 wstETHAmount = wstETH.wrap(amount);
stETH.forceApprove(address(wrapper), amount);
(uint256 wstETHAmount,) = wrapper.wrap(amount, stETH);
wstETH.forceApprove(address(ynEigen), wstETHAmount);

shares = ynEigen.deposit(IERC20(address(wstETH)), wstETHAmount, receiver);
Expand All @@ -170,8 +177,8 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A

function depositOETH(uint256 amount, address receiver) internal returns (uint256 shares) {
oETH.safeTransferFrom(msg.sender, address(this), amount);
oETH.forceApprove(address(woETH), amount);
uint256 woETHShares = woETH.deposit(amount, address(this));
oETH.forceApprove(address(wrapper), amount);
(uint256 woETHShares,) = wrapper.wrap(amount, oETH);
woETH.forceApprove(address(ynEigen), woETHShares);

shares = ynEigen.deposit(IERC20(address(woETH)), woETHShares, receiver);
Expand Down
55 changes: 55 additions & 0 deletions test/scenarios/ynEIGEN/ynLSDeDepositAdapter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: BSD 3-Clause License
pragma solidity ^0.8.24;

import {TestAssetUtils} from "test/utils/TestAssetUtils.sol";

import "./ynLSDeWithdrawals.t.sol";

contract ynLSDeDepositAdapterTest is ynLSDeWithdrawalsTest {

TestAssetUtils public testAssetUtils;

function setUp() public override {
super.setUp();

// upgrade deposit adapter
{
_upgradeContract(
address(ynEigenDepositAdapter_),
address(new ynEigenDepositAdapter()),
abi.encodeWithSignature("initializeV2(address)", address(wrapper))
);
}

// deploy testAssetUtils
{
testAssetUtils = new TestAssetUtils();
}
}

// function testDepositSTETH(uint256 _amount) public {
// vm.assume(_amount > 0 && _amount <= 10 ether);

// testAssetUtils.get_stETH(user, _amount);

// vm.startPrank(user);
// IERC20(chainAddresses.lsd.STETH_ADDRESS).approve(address(ynEigenDepositAdapter_), _amount);
// uint256 _ynOut = ynEigenDepositAdapter_.deposit(IERC20(chainAddresses.lsd.STETH_ADDRESS), _amount, user);
// vm.stopPrank();

// assertEq(IERC20(yneigen).balanceOf(user), _ynOut, "testDepositSTETH");
// }

function testDepositOETH(uint256 _amount) public {
vm.assume(_amount > 0 && _amount <= 10 ether);

testAssetUtils.get_OETH(user, _amount);

vm.startPrank(user);
IERC20(chainAddresses.lsd.OETH_ADDRESS).approve(address(ynEigenDepositAdapter_), _amount);
uint256 _ynOut = ynEigenDepositAdapter_.deposit(IERC20(chainAddresses.lsd.OETH_ADDRESS), _amount, user);
vm.stopPrank();

assertEq(IERC20(yneigen).balanceOf(user), _ynOut, "testDepositOETH");
}
}
Loading

0 comments on commit cc5c202

Please sign in to comment.