Skip to content

Commit 55a265e

Browse files
authored
Merge pull request #43 from yieldnest/fix/inflation-bug-ynlsd
Fix/inflation bug ynlsd
2 parents b85a72b + 2c1d767 commit 55a265e

File tree

12 files changed

+149
-178
lines changed

12 files changed

+149
-178
lines changed

scripts/forge/BaseScript.s.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ abstract contract BaseScript is Script, Utils {
103103
PAUSE_ADMIN: vm.envAddress("PAUSER_ADDRESS"),
104104
LSD_RESTAKING_MANAGER: vm.envAddress("LSD_RESTAKING_MANAGER_ADDRESS"),
105105
STAKING_NODE_CREATOR: vm.envAddress("LSD_STAKING_NODE_CREATOR_ADDRESS"),
106-
ORACLE_MANAGER: vm.envAddress("YIELDNEST_ORACLE_MANAGER_ADDRESS")
106+
ORACLE_MANAGER: vm.envAddress("YIELDNEST_ORACLE_MANAGER_ADDRESS"),
107+
DEPOSIT_BOOTSTRAPER: vm.envAddress("DEPOSIT_BOOTSTRAPER_ADDRESS")
107108
});
108109
}
109110

scripts/forge/DeployYieldNest.s.sol

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ contract DeployYieldNest is BaseScript {
4444
IDepositContract public depositContract;
4545
IWETH public weth;
4646

47-
uint startingExchangeAdjustmentRate;
48-
4947
bytes ZERO_PUBLIC_KEY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
5048
bytes ONE_PUBLIC_KEY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001";
5149
bytes TWO_PUBLIC_KEY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002";
@@ -72,9 +70,6 @@ contract DeployYieldNest is BaseScript {
7270

7371
feeReceiver = payable(_broadcaster); // Casting the default signer address to payable
7472

75-
76-
startingExchangeAdjustmentRate = 4;
77-
7873
ContractAddresses contractAddresses = new ContractAddresses();
7974
ContractAddresses.ChainAddresses memory chainAddresses = contractAddresses.getChainAddresses(block.chainid);
8075
eigenPodManager = IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS);
@@ -120,7 +115,6 @@ contract DeployYieldNest is BaseScript {
120115
pauser: actors.PAUSE_ADMIN,
121116
stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)),
122117
rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)),
123-
exchangeAdjustmentRate: startingExchangeAdjustmentRate,
124118
pauseWhitelist: pauseWhitelist
125119
});
126120
yneth.initialize(ynethInit);

scripts/forge/DeployYnLSD.s.sol

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ contract DeployYnLSD is BaseScript {
3838
// solhint-disable-next-line no-console
3939
console.log("Current Chain ID:", block.chainid);
4040

41-
uint256 startingExchangeAdjustmentRate = 0;
42-
4341
ContractAddresses contractAddresses = new ContractAddresses();
4442
ContractAddresses.ChainAddresses memory chainAddresses = contractAddresses.getChainAddresses(block.chainid);
4543
eigenPodManager = IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS);
@@ -80,7 +78,6 @@ contract DeployYnLSD is BaseScript {
8078
strategyManager: strategyManager,
8179
delegationManager: delegationManager,
8280
oracle: yieldNestOracle,
83-
exchangeAdjustmentRate: startingExchangeAdjustmentRate,
8481
maxNodeCount: 10,
8582
admin: actors.ADMIN,
8683
pauser: actors.PAUSE_ADMIN,

src/interfaces/IStakingNode.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ interface IStakingEvents {
2323
event Staked(address indexed staker, uint256 ethAmount, uint256 ynETHAmount);
2424
event DepositETHPausedUpdated(bool isPaused);
2525
event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares);
26-
event ExchangeAdjustmentRateUpdated(uint256 newRate);
2726
}
2827

2928
interface IStakingNode {

src/ynETH.sol

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ contract ynETH is IynETH, ynBase, IStakingEvents {
2323
error Paused();
2424
error ValueOutOfBounds(uint256 value);
2525
error ZeroAddress();
26-
error ExchangeAdjustmentRateOutOfBounds(uint256 exchangeAdjustmentRate);
2726
error ZeroETH();
2827
error NoDirectETHDeposit();
2928
error CallerNotStakingNodeManager(address expected, address provided);
@@ -38,9 +37,6 @@ contract ynETH is IynETH, ynBase, IStakingEvents {
3837
IRewardsDistributor public rewardsDistributor;
3938
bool public depositsPaused;
4039

41-
/// @dev The value is in basis points (1/10000).
42-
uint256 public exchangeAdjustmentRate;
43-
4440
uint256 public totalDepositedInPool;
4541

4642
//--------------------------------------------------------------------------------------
@@ -53,7 +49,6 @@ contract ynETH is IynETH, ynBase, IStakingEvents {
5349
address pauser;
5450
IStakingNodesManager stakingNodesManager;
5551
IRewardsDistributor rewardsDistributor;
56-
uint256 exchangeAdjustmentRate;
5752
address[] pauseWhitelist;
5853
}
5954

@@ -79,11 +74,6 @@ contract ynETH is IynETH, ynBase, IStakingEvents {
7974
stakingNodesManager = init.stakingNodesManager;
8075
rewardsDistributor = init.rewardsDistributor;
8176

82-
if (init.exchangeAdjustmentRate > BASIS_POINTS_DENOMINATOR) {
83-
revert ExchangeAdjustmentRateOutOfBounds(init.exchangeAdjustmentRate);
84-
}
85-
exchangeAdjustmentRate = init.exchangeAdjustmentRate;
86-
8777
_setTransfersPaused(true); // transfers are initially paused
8878
_updatePauseWhitelist(init.pauseWhitelist, true);
8979
}
@@ -129,16 +119,13 @@ contract ynETH is IynETH, ynBase, IStakingEvents {
129119
if (totalSupply() == 0) {
130120
return ethAmount;
131121
}
132-
133-
// deltaynETH = (1 - exchangeAdjustmentRate) * (ynETHSupply / totalControlled) * ethAmount
134-
// If `(1 - exchangeAdjustmentRate) * ethAmount * ynETHSupply < totalControlled` this will be 0.
135122

136-
// Can only happen in bootstrap phase if `totalControlled` and `ynETHSupply` could be manipulated
137-
// independently. That should not be possible.
123+
124+
// deltaynETH = (ynETHSupply / totalControlled) * ethAmount
138125
return Math.mulDiv(
139126
ethAmount,
140-
totalSupply() * uint256(BASIS_POINTS_DENOMINATOR - exchangeAdjustmentRate),
141-
totalAssets() * uint256(BASIS_POINTS_DENOMINATOR),
127+
totalSupply(),
128+
totalAssets(),
142129
rounding
143130
);
144131
}
@@ -219,18 +206,6 @@ contract ynETH is IynETH, ynBase, IStakingEvents {
219206
emit DepositETHPausedUpdated(depositsPaused);
220207
}
221208

222-
/// @notice Sets the exchange adjustment rate.
223-
/// @dev Can only be called by the admin..
224-
/// Reverts if the new rate exceeds the basis points denominator.
225-
/// @param newRate The new exchange adjustment rate to be set.
226-
function setExchangeAdjustmentRate(uint256 newRate) external onlyRole(DEFAULT_ADMIN_ROLE) {
227-
if (newRate > BASIS_POINTS_DENOMINATOR) {
228-
revert ValueOutOfBounds(newRate);
229-
}
230-
exchangeAdjustmentRate = newRate;
231-
emit ExchangeAdjustmentRateUpdated(newRate);
232-
}
233-
234209
//--------------------------------------------------------------------------------------
235210
//---------------------------------- MODIFIERS ---------------------------------------
236211
//--------------------------------------------------------------------------------------

src/ynLSD.sol

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
77
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
88
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
99
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
10-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1110
import {IStrategy} from "./external/eigenlayer/v0.1.0/interfaces/IStrategy.sol";
1211
import {IStrategyManager} from "./external/eigenlayer/v0.1.0/interfaces/IStrategyManager.sol";
1312
import {IDelegationManager} from "./external/eigenlayer/v0.1.0/interfaces/IDelegationManager.sol";
@@ -33,7 +32,6 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
3332

3433
error UnsupportedAsset(IERC20 asset);
3534
error ZeroAmount();
36-
error ExchangeAdjustmentRateOutOfBounds(uint256 exchangeAdjustmentRate);
3735
error ZeroAddress();
3836
error BeaconImplementationAlreadyExists();
3937
error NoBeaconImplementationExists();
@@ -48,6 +46,8 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
4846
bytes32 public constant LSD_RESTAKING_MANAGER_ROLE = keccak256("LSD_RESTAKING_MANAGER_ROLE");
4947
bytes32 public constant LSD_STAKING_NODE_CREATOR_ROLE = keccak256("LSD_STAKING_NODE_CREATOR_ROLE");
5048

49+
uint256 public constant BOOTSTRAP_AMOUNT_UNITS = 10;
50+
5151
//--------------------------------------------------------------------------------------
5252
//---------------------------------- VARIABLES ---------------------------------------
5353
//--------------------------------------------------------------------------------------
@@ -63,8 +63,6 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
6363

6464
/// @notice List of supported ERC20 asset contracts.
6565
IERC20[] public assets;
66-
67-
uint256 public exchangeAdjustmentRate;
6866

6967
/**
7068
* @notice Array of LSD Staking Node contracts.
@@ -88,14 +86,14 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
8886
IStrategyManager strategyManager;
8987
IDelegationManager delegationManager;
9088
YieldNestOracle oracle;
91-
uint256 exchangeAdjustmentRate;
9289
uint256 maxNodeCount;
9390
address admin;
9491
address pauser;
9592
address stakingAdmin;
9693
address lsdRestakingManager;
9794
address lsdStakingNodeCreatorRole;
9895
address[] pauseWhitelist;
96+
address depositBootstrapper;
9997
}
10098

10199
function initialize(Init memory init)
@@ -128,15 +126,12 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
128126
strategyManager = init.strategyManager;
129127
delegationManager = init.delegationManager;
130128
oracle = init.oracle;
131-
132-
if (init.exchangeAdjustmentRate > BASIS_POINTS_DENOMINATOR) {
133-
revert ExchangeAdjustmentRateOutOfBounds(init.exchangeAdjustmentRate);
134-
}
135-
exchangeAdjustmentRate = init.exchangeAdjustmentRate;
136129
maxNodeCount = init.maxNodeCount;
137130

138131
_setTransfersPaused(true); // transfers are initially paused
139132
_updatePauseWhitelist(init.pauseWhitelist, true);
133+
134+
_deposit(assets[0], BOOTSTRAP_AMOUNT_UNITS * (10 ** (IERC20Metadata(address(assets[0])).decimals())), init.depositBootstrapper, init.depositBootstrapper);
140135
}
141136

142137
//--------------------------------------------------------------------------------------
@@ -157,7 +152,16 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
157152
IERC20 asset,
158153
uint256 amount,
159154
address receiver
160-
) external nonReentrant returns (uint256 shares) {
155+
) public nonReentrant returns (uint256 shares) {
156+
return _deposit(asset, amount, receiver, msg.sender);
157+
}
158+
159+
function _deposit(
160+
IERC20 asset,
161+
uint256 amount,
162+
address receiver,
163+
address sender
164+
) internal returns (uint256 shares) {
161165

162166
IStrategy strategy = strategies[asset];
163167
if(address(strategy) == address(0x0)){
@@ -178,15 +182,15 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
178182

179183
// Transfer assets in after shares are computed since _convertToShares relies on totalAssets
180184
// which inspects asset.balanceOf(address(this))
181-
asset.safeTransferFrom(msg.sender, address(this), amount);
185+
asset.safeTransferFrom(sender, address(this), amount);
182186

183-
emit Deposit(msg.sender, receiver, amount, shares);
187+
emit Deposit(sender, receiver, amount, shares);
184188
}
185189

186190
/**
187191
* @dev Converts an ETH amount to shares based on the current exchange rate and specified rounding method.
188192
* If it's the first stake (bootstrap phase), uses a 1:1 exchange rate. Otherwise, calculates shares based on
189-
* the formula: deltaynETH = (1 - exchangeAdjustmentRate) * (ynETHSupply / totalControlled) * ethAmount.
193+
* the formula: deltaynETH = (ynETHSupply / totalControlled) * ethAmount.
190194
* This calculation can result in 0 during the bootstrap phase if `totalControlled` and `ynETHSupply` could be
191195
* manipulated independently, which should not be possible.
192196
* @param ethAmount The amount of ETH to convert to shares.
@@ -200,15 +204,14 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
200204
return ethAmount;
201205
}
202206

203-
// deltaynETH = (1 - exchangeAdjustmentRate) * (ynETHSupply / totalControlled) * ethAmount
204-
// If `(1 - exchangeAdjustmentRate) * ethAmount * ynETHSupply < totalControlled` this will be 0.
207+
// deltaynETH = (ynETHSupply / totalControlled) * ethAmount
205208

206209
// Can only happen in bootstrap phase if `totalControlled` and `ynETHSupply` could be manipulated
207210
// independently. That should not be possible.
208211
return Math.mulDiv(
209212
ethAmount,
210-
totalSupply() * uint256(BASIS_POINTS_DENOMINATOR - exchangeAdjustmentRate),
211-
totalAssets() * uint256(BASIS_POINTS_DENOMINATOR),
213+
totalSupply(),
214+
totalAssets(),
212215
rounding
213216
);
214217
}
@@ -452,3 +455,4 @@ contract ynLSD is IynLSD, ynBase, ReentrancyGuardUpgradeable, IynLSDEvents {
452455
_;
453456
}
454457
}
458+

test/foundry/ActorAddresses.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ contract ActorAddresses {
1616
address LSD_RESTAKING_MANAGER;
1717
address STAKING_NODE_CREATOR;
1818
address ORACLE_MANAGER;
19+
address DEPOSIT_BOOTSTRAPER;
1920
}
2021

2122
mapping(uint256 => Actors) public actors;
@@ -33,7 +34,8 @@ contract ActorAddresses {
3334
PAUSE_ADMIN: 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f,
3435
LSD_RESTAKING_MANAGER: 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720,
3536
STAKING_NODE_CREATOR: 0xBcd4042DE499D14e55001CcbB24a551F3b954096,
36-
ORACLE_MANAGER: 0x71bE63f3384f5fb98995898A86B02Fb2426c5788
37+
ORACLE_MANAGER: 0x71bE63f3384f5fb98995898A86B02Fb2426c5788,
38+
DEPOSIT_BOOTSTRAPER: 0xFABB0ac9d68B0B445fB7357272Ff202C5651694a
3739
});
3840

3941
actors[5] = Actors({
@@ -48,7 +50,8 @@ contract ActorAddresses {
4850
PAUSE_ADMIN: 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f,
4951
LSD_RESTAKING_MANAGER: 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720,
5052
STAKING_NODE_CREATOR: 0xBcd4042DE499D14e55001CcbB24a551F3b954096,
51-
ORACLE_MANAGER: 0x71bE63f3384f5fb98995898A86B02Fb2426c5788
53+
ORACLE_MANAGER: 0x71bE63f3384f5fb98995898A86B02Fb2426c5788,
54+
DEPOSIT_BOOTSTRAPER: 0xFABB0ac9d68B0B445fB7357272Ff202C5651694a
5255
});
5356
}
5457

test/foundry/integration/IntegrationBaseTest.sol

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ contract IntegrationBaseTest is Test, Utils {
4141
bytes constant ZERO_SIGNATURE = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
4242
bytes32 constant ZERO_DEPOSIT_ROOT = bytes32(0);
4343

44-
uint256 startingExchangeAdjustmentRate = 4;
45-
4644
// Utils
4745
ContractAddresses public contractAddresses;
4846
ContractAddresses.ChainAddresses public chainAddresses;
@@ -178,7 +176,6 @@ contract IntegrationBaseTest is Test, Utils {
178176
pauser: actors.PAUSE_ADMIN,
179177
stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)),
180178
rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)),
181-
exchangeAdjustmentRate: startingExchangeAdjustmentRate,
182179
pauseWhitelist: pauseWhitelist
183180
});
184181

@@ -239,19 +236,20 @@ contract IntegrationBaseTest is Test, Utils {
239236
address[] memory pauseWhitelist = new address[](1);
240237
pauseWhitelist[0] = actors.TRANSFER_ENABLED_EOA;
241238

242-
// rETH
243-
assets[0] = IERC20(chainAddresses.lsd.RETH_ADDRESS);
244-
assetsAddresses[0] = chainAddresses.lsd.RETH_ADDRESS;
245-
strategies[0] = IStrategy(chainAddresses.lsd.RETH_STRATEGY_ADDRESS);
246-
priceFeeds[0] = chainAddresses.lsd.RETH_FEED_ADDRESS;
247-
maxAges[0] = uint256(86400);
248239

249240
// stETH
250-
assets[1] = IERC20(chainAddresses.lsd.STETH_ADDRESS);
251-
assetsAddresses[1] = chainAddresses.lsd.STETH_ADDRESS;
252-
strategies[1] = IStrategy(chainAddresses.lsd.STETH_STRATEGY_ADDRESS);
253-
priceFeeds[1] = chainAddresses.lsd.STETH_FEED_ADDRESS;
254-
maxAges[1] = uint256(86400); //one hour
241+
assets[0] = IERC20(chainAddresses.lsd.STETH_ADDRESS);
242+
assetsAddresses[0] = chainAddresses.lsd.STETH_ADDRESS;
243+
strategies[0] = IStrategy(chainAddresses.lsd.STETH_STRATEGY_ADDRESS);
244+
priceFeeds[0] = chainAddresses.lsd.STETH_FEED_ADDRESS;
245+
maxAges[0] = uint256(86400); //one hour
246+
247+
// rETH
248+
assets[1] = IERC20(chainAddresses.lsd.RETH_ADDRESS);
249+
assetsAddresses[1] = chainAddresses.lsd.RETH_ADDRESS;
250+
strategies[1] = IStrategy(chainAddresses.lsd.RETH_STRATEGY_ADDRESS);
251+
priceFeeds[1] = chainAddresses.lsd.RETH_FEED_ADDRESS;
252+
maxAges[1] = uint256(86400);
255253

256254
YieldNestOracle.Init memory oracleInit = YieldNestOracle.Init({
257255
assets: assetsAddresses,
@@ -262,25 +260,31 @@ contract IntegrationBaseTest is Test, Utils {
262260
});
263261
yieldNestOracle.initialize(oracleInit);
264262

265-
uint startingExchangeAdjustmentRateForYnLSD = 0;
266-
267263
LSDStakingNode lsdStakingNodeImplementation = new LSDStakingNode();
268264
ynLSD.Init memory init = ynLSD.Init({
269265
assets: assets,
270266
strategies: strategies,
271267
strategyManager: strategyManager,
272268
delegationManager: delegationManager,
273269
oracle: yieldNestOracle,
274-
exchangeAdjustmentRate: startingExchangeAdjustmentRateForYnLSD,
275270
maxNodeCount: 10,
276271
admin: actors.ADMIN,
277272
stakingAdmin: actors.STAKING_ADMIN,
278273
lsdRestakingManager: actors.LSD_RESTAKING_MANAGER,
279274
lsdStakingNodeCreatorRole: actors.STAKING_NODE_CREATOR,
280275
pauseWhitelist: pauseWhitelist,
281-
pauser: actors.PAUSE_ADMIN
276+
pauser: actors.PAUSE_ADMIN,
277+
depositBootstrapper: actors.DEPOSIT_BOOTSTRAPER
282278
});
283279

280+
vm.deal(actors.DEPOSIT_BOOTSTRAPER, 10000 ether);
281+
282+
vm.prank(actors.DEPOSIT_BOOTSTRAPER);
283+
(bool success, ) = chainAddresses.lsd.STETH_ADDRESS.call{value: 1000 ether}("");
284+
require(success, "ETH transfer failed");
285+
286+
vm.prank(actors.DEPOSIT_BOOTSTRAPER);
287+
IERC20(chainAddresses.lsd.STETH_ADDRESS).approve(address(ynlsd), type(uint256).max);
284288
ynlsd.initialize(init);
285289

286290
vm.prank(actors.STAKING_ADMIN);

0 commit comments

Comments
 (0)