Skip to content

Commit cc5c202

Browse files
committed
feat: refactor token wrapping
1 parent 02260f0 commit cc5c202

File tree

7 files changed

+209
-65
lines changed

7 files changed

+209
-65
lines changed

src/interfaces/IWrapper.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: BSD 3-Clause License
2+
pragma solidity ^0.8.24;
3+
4+
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
5+
6+
interface IWrapper {
7+
8+
/// @notice Wraps the given amount of the given token.
9+
/// @param _amount The amount to wrap.
10+
/// @param _token The token to wrap.
11+
/// @return The amount of wrapped tokens and the wrapped token.
12+
function wrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20);
13+
14+
/// @notice Unwraps the given amount of the given token.
15+
/// @param _amount The amount to unwrap.
16+
/// @param _token The token to unwrap.
17+
/// @return The amount of unwrapped tokens and the unwrapped token.
18+
function unwrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20);
19+
}

src/ynEIGEN/EigenStrategyManager.sol

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {IynEigen} from "src/interfaces/IynEigen.sol";
1515
import {IwstETH} from "src/external/lido/IwstETH.sol";
1616
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
1717
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
18+
import {IWrapper} from "src/interfaces/IWrapper.sol";
1819

1920
interface IEigenStrategyManagerEvents {
2021
event StrategyAdded(address indexed asset, address indexed strategy);
@@ -107,6 +108,7 @@ contract EigenStrategyManager is
107108
IERC20 public stETH;
108109

109110
IRedemptionAssetsVaultExt public redemptionAssetsVault;
111+
IWrapper public wrapper;
110112

111113
//--------------------------------------------------------------------------------------
112114
//---------------------------------- INITIALIZATION ----------------------------------
@@ -166,11 +168,17 @@ contract EigenStrategyManager is
166168

167169
function initializeV2(
168170
address _redemptionAssetsVault,
171+
address _wrapper,
169172
address _withdrawer
170-
) external reinitializer(2) notZeroAddress(_redemptionAssetsVault) {
173+
) external reinitializer(2) notZeroAddress(_redemptionAssetsVault) notZeroAddress(_wrapper) {
171174
redemptionAssetsVault = IRedemptionAssetsVaultExt(_redemptionAssetsVault);
175+
wrapper = IWrapper(_wrapper);
176+
172177
_grantRole(STAKING_NODES_WITHDRAWER_ROLE, _withdrawer);
173178
_grantRole(WITHDRAWAL_MANAGER_ROLE, _withdrawer);
179+
180+
IERC20(address(wstETH)).forceApprove(address(_wrapper), type(uint256).max);
181+
IERC20(address(woETH)).forceApprove(address(_wrapper), type(uint256).max);
174182
}
175183

176184
//--------------------------------------------------------------------------------------
@@ -239,7 +247,7 @@ contract EigenStrategyManager is
239247
uint256[] memory depositAmounts = new uint256[](amountsLength);
240248

241249
for (uint256 i = 0; i < assetsLength; i++) {
242-
(IERC20 depositAsset, uint256 depositAmount) = toEigenLayerDeposit(assets[i], amounts[i]);
250+
(uint256 depositAmount, IERC20 depositAsset) = wrapper.unwrap(amounts[i], assets[i]);
243251
depositAssets[i] = depositAsset;
244252
depositAmounts[i] = depositAmount;
245253

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

257-
function toEigenLayerDeposit(
258-
IERC20 asset,
259-
uint256 amount
260-
) internal returns (IERC20 depositAsset, uint256 depositAmount) {
261-
if (address(asset) == address(wstETH)) {
262-
// Adjust for wstETH
263-
depositAsset = stETH;
264-
depositAmount = wstETH.unwrap(amount);
265-
} else if (address(asset) == address(woETH)) {
266-
// Adjust for woeth
267-
depositAsset = oETH;
268-
// calling redeem with receiver and owner as address(this)
269-
depositAmount = woETH.redeem(amount, address(this), address(this));
270-
} else {
271-
// No adjustment needed
272-
depositAsset = asset;
273-
depositAmount = amount;
274-
}
275-
}
276-
277265
//--------------------------------------------------------------------------------------
278266
//---------------------------------- WITHDRAWALS -------------------------------------
279267
//--------------------------------------------------------------------------------------

src/ynEIGEN/LSDWrapper.sol

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: BSD 3-Clause License
2+
pragma solidity ^0.8.24;
3+
4+
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
5+
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
6+
import {IERC20, SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
7+
8+
import {IwstETH} from "src/external/lido/IwstETH.sol";
9+
import {IWrapper} from "src/interfaces/IWrapper.sol";
10+
11+
contract LSDWrapper is IWrapper, Initializable {
12+
13+
using SafeERC20 for IERC20;
14+
15+
IERC20 public immutable wstETH;
16+
IERC20 public immutable woETH;
17+
IERC20 public immutable oETH;
18+
IERC20 public immutable stETH;
19+
20+
// ============================================================================================
21+
// Constructor
22+
// ============================================================================================
23+
24+
constructor(address _wstETH, address _woETH, address _oETH, address _stETH) {
25+
if (_wstETH == address(0) || _woETH == address(0) || _oETH == address(0) || _stETH == address(0)) {
26+
revert ZeroAddress();
27+
}
28+
29+
wstETH = IERC20(_wstETH);
30+
woETH = IERC20(_woETH);
31+
oETH = IERC20(_oETH);
32+
stETH = IERC20(_stETH);
33+
}
34+
35+
function initialize() external initializer {
36+
stETH.forceApprove(address(wstETH), type(uint256).max);
37+
oETH.forceApprove(address(woETH), type(uint256).max);
38+
}
39+
40+
// ============================================================================================
41+
// External functions
42+
// ============================================================================================
43+
44+
/// @inheritdoc IWrapper
45+
function wrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20) {
46+
if (_token == stETH) {
47+
stETH.safeTransferFrom(msg.sender, address(this), _amount);
48+
_amount = IwstETH(address(wstETH)).wrap(_amount);
49+
wstETH.safeTransfer(msg.sender, _amount);
50+
return (_amount, wstETH);
51+
} else if (_token == oETH) {
52+
oETH.safeTransferFrom(msg.sender, address(this), _amount);
53+
return (IERC4626(address(woETH)).deposit(_amount, msg.sender), woETH);
54+
} else {
55+
return (_amount, _token);
56+
}
57+
}
58+
59+
/// @inheritdoc IWrapper
60+
function unwrap(uint256 _amount, IERC20 _token) external returns (uint256, IERC20) {
61+
if (_token == wstETH) {
62+
wstETH.safeTransferFrom(msg.sender, address(this), _amount);
63+
_amount = IwstETH(address(wstETH)).unwrap(_amount);
64+
stETH.safeTransfer(msg.sender, _amount);
65+
return (_amount, stETH);
66+
} else if (_token == woETH) {
67+
return (IERC4626(address(woETH)).redeem(_amount, msg.sender, msg.sender), oETH);
68+
} else {
69+
return (_amount, _token);
70+
}
71+
}
72+
73+
// ============================================================================================
74+
// Errors
75+
// ============================================================================================
76+
77+
error ZeroAddress();
78+
}

src/ynEIGEN/TokenStakingNode.sol

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfa
1212
import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
1313
import {ITokenStakingNode} from "src/interfaces/ITokenStakingNode.sol";
1414
import {ITokenStakingNodesManager} from "src/interfaces/ITokenStakingNodesManager.sol";
15-
import {IwstETH} from "src/external/lido/IwstETH.sol";
16-
import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
15+
import {IWrapper} from "src/interfaces/IWrapper.sol";
1716

1817
interface ITokenStakingNodeEvents {
1918
event DepositToEigenlayer(
@@ -30,10 +29,7 @@ interface ITokenStakingNodeEvents {
3029
}
3130

3231
interface IYieldNestStrategyManager {
33-
function wstETH() external view returns (IwstETH);
34-
function stETH() external view returns (IERC20);
35-
function woETH() external view returns (IERC4626);
36-
function oETH() external view returns (IERC20);
32+
function wrapper() external view returns (IWrapper);
3733
function isStakingNodesWithdrawer(address _address) external view returns (bool);
3834
}
3935

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

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

211209
queuedShares[_strategy] -= _shares;
212210
withdrawn[_token] += _actualAmountOut;
@@ -222,26 +220,6 @@ contract TokenStakingNode is
222220
emit DeallocatedTokens(_amount, _token);
223221
}
224222

225-
function _wrapIfNeeded(uint256 _amount, IERC20 _token) internal returns (uint256, IERC20) {
226-
IYieldNestStrategyManager _strategyManager =
227-
IYieldNestStrategyManager(ITokenStakingNodesManager(address(tokenStakingNodesManager)).yieldNestStrategyManager());
228-
IERC20 _stETH = _strategyManager.stETH();
229-
IERC20 _oETH = _strategyManager.oETH();
230-
if (_token == _stETH) {
231-
IwstETH _wstETH = _strategyManager.wstETH();
232-
_stETH.forceApprove(address(_wstETH), _amount);
233-
uint256 _wstETHAmount = _wstETH.wrap(_amount);
234-
return (_wstETHAmount, IERC20(_wstETH));
235-
} else if (_token == _oETH) {
236-
IERC4626 _woETH = _strategyManager.woETH();
237-
_oETH.forceApprove(address(_woETH), _amount);
238-
uint256 _woETHShares = _woETH.deposit(_amount, address(this));
239-
return (_woETHShares, IERC20(_woETH));
240-
} else {
241-
return (_amount, _token);
242-
}
243-
}
244-
245223
//--------------------------------------------------------------------------------------
246224
//---------------------------------- DELEGATION --------------------------------------
247225
//--------------------------------------------------------------------------------------

src/ynEIGEN/ynEigenDepositAdapter.sol

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/
1010
import {AccessControlUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";
1111
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
1212

13+
import {IWrapper} from "src/interfaces/IWrapper.sol";
14+
1315
interface IynEigenDepositAdapterEvents {
1416
event ReferralDepositProcessed(
1517
address sender,
@@ -54,6 +56,7 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A
5456
IERC4626 public woETH;
5557
IERC20 public stETH;
5658
IERC20 public oETH;
59+
IWrapper public wrapper;
5760

5861
//--------------------------------------------------------------------------------------
5962
//---------------------------------- INITIALIZATION ----------------------------------
@@ -84,6 +87,10 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A
8487
oETH = IERC20(woETH.asset());
8588
}
8689

90+
function initializeV2(address _wrapper) external reinitializer(2) notZeroAddress(_wrapper) {
91+
wrapper = IWrapper(_wrapper);
92+
}
93+
8794
/**
8895
* @notice Handles the deposit of assets into the ynEigen system.
8996
It supports all assets supported by ynEigen
@@ -159,8 +166,8 @@ contract ynEigenDepositAdapter is IynEigenDepositAdapterEvents, Initializable, A
159166

160167
function depositStETH(uint256 amount, address receiver) internal returns (uint256 shares) {
161168
stETH.safeTransferFrom(msg.sender, address(this), amount);
162-
stETH.forceApprove(address(wstETH), amount);
163-
uint256 wstETHAmount = wstETH.wrap(amount);
169+
stETH.forceApprove(address(wrapper), amount);
170+
(uint256 wstETHAmount,) = wrapper.wrap(amount, stETH);
164171
wstETH.forceApprove(address(ynEigen), wstETHAmount);
165172

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

171178
function depositOETH(uint256 amount, address receiver) internal returns (uint256 shares) {
172179
oETH.safeTransferFrom(msg.sender, address(this), amount);
173-
oETH.forceApprove(address(woETH), amount);
174-
uint256 woETHShares = woETH.deposit(amount, address(this));
180+
oETH.forceApprove(address(wrapper), amount);
181+
(uint256 woETHShares,) = wrapper.wrap(amount, oETH);
175182
woETH.forceApprove(address(ynEigen), woETHShares);
176183

177184
shares = ynEigen.deposit(IERC20(address(woETH)), woETHShares, receiver);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// SPDX-License-Identifier: BSD 3-Clause License
2+
pragma solidity ^0.8.24;
3+
4+
import {TestAssetUtils} from "test/utils/TestAssetUtils.sol";
5+
6+
import "./ynLSDeWithdrawals.t.sol";
7+
8+
contract ynLSDeDepositAdapterTest is ynLSDeWithdrawalsTest {
9+
10+
TestAssetUtils public testAssetUtils;
11+
12+
function setUp() public override {
13+
super.setUp();
14+
15+
// upgrade deposit adapter
16+
{
17+
_upgradeContract(
18+
address(ynEigenDepositAdapter_),
19+
address(new ynEigenDepositAdapter()),
20+
abi.encodeWithSignature("initializeV2(address)", address(wrapper))
21+
);
22+
}
23+
24+
// deploy testAssetUtils
25+
{
26+
testAssetUtils = new TestAssetUtils();
27+
}
28+
}
29+
30+
// function testDepositSTETH(uint256 _amount) public {
31+
// vm.assume(_amount > 0 && _amount <= 10 ether);
32+
33+
// testAssetUtils.get_stETH(user, _amount);
34+
35+
// vm.startPrank(user);
36+
// IERC20(chainAddresses.lsd.STETH_ADDRESS).approve(address(ynEigenDepositAdapter_), _amount);
37+
// uint256 _ynOut = ynEigenDepositAdapter_.deposit(IERC20(chainAddresses.lsd.STETH_ADDRESS), _amount, user);
38+
// vm.stopPrank();
39+
40+
// assertEq(IERC20(yneigen).balanceOf(user), _ynOut, "testDepositSTETH");
41+
// }
42+
43+
function testDepositOETH(uint256 _amount) public {
44+
vm.assume(_amount > 0 && _amount <= 10 ether);
45+
46+
testAssetUtils.get_OETH(user, _amount);
47+
48+
vm.startPrank(user);
49+
IERC20(chainAddresses.lsd.OETH_ADDRESS).approve(address(ynEigenDepositAdapter_), _amount);
50+
uint256 _ynOut = ynEigenDepositAdapter_.deposit(IERC20(chainAddresses.lsd.OETH_ADDRESS), _amount, user);
51+
vm.stopPrank();
52+
53+
assertEq(IERC20(yneigen).balanceOf(user), _ynOut, "testDepositOETH");
54+
}
55+
}

0 commit comments

Comments
 (0)