Platform: Code4rena
Start Date: 10/11/2023
Pot Size: $28,000 USDC
Total HM: 5
Participants: 185
Period: 5 days
Judge: 0xDjango
Id: 305
League: ETH
Rank: 27/185
Findings: 2
Award: $144.91
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Krace
Also found by: 0xDING99YA, 0xrugpull_detector, Aamir, AlexCzm, Aymen0909, Banditx0x, Bauer, CatsSecurity, GREY-HAWK-REACH, Madalad, Phantasmagoria, QiuhaoLi, Ruhum, SBSecurity, SandNallani, SpicyMeatball, T1MOH, TheSchnilch, adam-idarrha, adriro, almurhasan, ast3ros, ayden, bronze_pickaxe, btk, chaduke, ck, crack-the-kelp, critical-or-high, deth, gumgumzum, jasonxiale, joaovwfreire, ke1caM, m_Rassska, mahdirostami, mahyar, max10afternoon, osmanozdemir1, peanuts, pep7siup, peter, ptsanev, qpzm, rouhsamad, rvierdiiev, spark, twcctop, ubl4nk, wisdomn_, zach, zhaojie
4.6614 USDC - $4.66
An attacker can take advantage and be the first depositor therefore stealing value from subsequent depositors.
When the supply of rsETH
is 0
, the price of rsETH
is set to 1
ETH
:
function getRSETHPrice() external view returns (uint256 rsETHPrice) { address rsETHTokenAddress = lrtConfig.rsETH(); uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply(); if (rsEthSupply == 0) { return 1 ether; }
The first depositor can therefore donate a very small value of assets (1 wei
) and then send a large amount of assets to the pool.
This would highly inflate the value of their rsETH
holding and lead to loss of value for subsequent depositors.
1 wei
of assets using the depositAsset
function.1 wei
of rsETH
in return.1 wei
of rsETH
they hold is now very high.rsETH
when they deposits assets.Manual review.
Seed the pool with initial rsETH
liquidity. Alternatively set a minimum value of the amount of rsETH
that can be minted by the first depositor.
MEV
#0 - c4-pre-sort
2023-11-16T18:57:45Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T18:57:53Z
raymondfam marked the issue as duplicate of #42
#2 - c4-judge
2023-12-01T17:04:14Z
fatherGoose1 marked the issue as satisfactory
🌟 Selected for report: max10afternoon
Also found by: 0xMilenov, 0xbrett8571, 0xhacksmithh, 0xmystery, Aymen0909, Bauer, Daniel526, PENGUN, Pechenite, Shaheen, adriro, anarcheuz, btk, ck, ge6a, glcanvas, hals, turvy_fuzz
140.2525 USDC - $140.25
The amount of rsETH
to be minted is dependent on lrtOracle.getRSETHPrice()
.
// calculate rseth amount to mint based on asset amount and asset exchange rate rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
The function getRSETHPrice()
is reliant on the totalETHInPool
value:
function getRSETHPrice() external view returns (uint256 rsETHPrice) { address rsETHTokenAddress = lrtConfig.rsETH(); uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply(); if (rsEthSupply == 0) { return 1 ether; } uint256 totalETHInPool; address lrtDepositPoolAddr = lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL); address[] memory supportedAssets = lrtConfig.getSupportedAssetList(); uint256 supportedAssetCount = supportedAssets.length; for (uint16 asset_idx; asset_idx < supportedAssetCount;) { address asset = supportedAssets[asset_idx]; uint256 assetER = getAssetPrice(asset); uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset); totalETHInPool += totalAssetAmt * assetER; unchecked { ++asset_idx; } } return totalETHInPool / rsEthSupply; }
As can be seen the totalETHInPool
is determined by totalAssetAmt
:
uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset); totalETHInPool += totalAssetAmt * assetER;
A component of this value is the assets in the deposit pool:
function getTotalAssetDeposits(address asset) public view override returns (uint256 totalAssetDeposit) { (uint256 assetLyingInDepositPool, uint256 assetLyingInNDCs, uint256 assetStakedInEigenLayer) = getAssetDistributionData(asset); return (assetLyingInDepositPool + assetLyingInNDCs + assetStakedInEigenLayer); }
Anyone can deposit assets to the pool causing this value to be inflated e.g through a flashloan.
By manipulating the deposits in the pool, the attacker would be able to frontrun other users and reduce the amount of rsETH
they get during a mint.
assetLyingInDepositPool
increases.totalAssetAmt
and totalETHInPool
values dramatically increasing.getRSETHPrice()
would therefore also increase due to the return value of return totalETHInPool / rsEthSupply;
rsETH
price would lead to a decrease in the amount of rsETH
that can be minted for a given asset amount: rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
rsETH
making them suffer losses.Manual Review.
A possible mitigation would be to let depositors specify a minimum amountof rsETH
they expect to receive during a mint.
Oracle
#0 - c4-pre-sort
2023-11-16T07:50:33Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T07:50:47Z
raymondfam marked the issue as duplicate of #39
#2 - c4-pre-sort
2023-11-17T06:43:12Z
raymondfam marked the issue as duplicate of #148
#3 - c4-judge
2023-11-29T19:10:27Z
fatherGoose1 marked the issue as satisfactory
🌟 Selected for report: max10afternoon
Also found by: 0xMilenov, 0xbrett8571, 0xhacksmithh, 0xmystery, Aymen0909, Bauer, Daniel526, PENGUN, Pechenite, Shaheen, adriro, anarcheuz, btk, ck, ge6a, glcanvas, hals, turvy_fuzz
140.2525 USDC - $140.25
When a deposit of assets is being made, the depositor specifies the depositAmount
.
function depositAsset( address asset, uint256 depositAmount ) external whenNotPaused nonReentrant onlySupportedAsset(asset) { // checks if (depositAmount == 0) { revert InvalidAmount(); } if (depositAmount > getAssetCurrentLimit(asset)) { revert MaximumDepositLimitReached(); } if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) { revert TokenTransferFailed(); } // interactions uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount); emit AssetDeposit(asset, depositAmount, rsethAmountMinted); }
The rsethAmountMinted
is calculated using the _mintRsETH
function which in turn relies on the getRsETHAmountToMint
function:
function _mintRsETH(address _asset, uint256 _amount) private returns (uint256 rsethAmountToMint) { (rsethAmountToMint) = getRsETHAmountToMint(_asset, _amount); address rsethToken = lrtConfig.rsETH(); // mint rseth for user IRSETH(rsethToken).mint(msg.sender, rsethAmountToMint); }
function getRsETHAmountToMint( address asset, uint256 amount ) public view override returns (uint256 rsethAmountToMint) { // setup oracle contract address lrtOracleAddress = lrtConfig.getContract(LRTConstants.LRT_ORACLE); ILRTOracle lrtOracle = ILRTOracle(lrtOracleAddress); // calculate rseth amount to mint based on asset amount and asset exchange rate rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice(); }
This value is likely to change as it relies on various factors such as the calculated getRSETHPrice
.
If this values fluctuates highly, there are no protections for the user on what amount of rsETH
they should receive.
The depositor is therefore very likely to suffer slippage loss.
rsETH
than they would have expected suffering losses.Manual review.
Allow the depositor to specify an expected minimum amount of rsETH
they should receive during deposits.
MEV
#0 - c4-pre-sort
2023-11-16T18:40:51Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T18:41:01Z
raymondfam marked the issue as duplicate of #39
#2 - c4-pre-sort
2023-11-17T06:43:14Z
raymondfam marked the issue as duplicate of #148
#3 - c4-judge
2023-11-29T19:10:35Z
fatherGoose1 marked the issue as satisfactory