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: 107/185
Findings: 2
Award: $7.42
🌟 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
The first depositor can break minting of rseth. The attack vector and impact is the same as TOB-YEARN-003, where users may not receive shares in exchange for their deposits if the total asset amount has been manipulated through a large “donation”.
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(); }
Manual Review
Uniswap V2 solved this problem by sending the first 1000 LP tokens to the zero address. The same can be done in this case i.e. when the totalSupply of rseth == 0, send some of the first tokens to the zero address. This will prevent this attack.
Error
#0 - c4-pre-sort
2023-11-16T02:52:22Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-16T02:52:56Z
raymondfam marked the issue as duplicate of #42
#2 - c4-judge
2023-12-01T16:59:39Z
fatherGoose1 marked the issue as satisfactory
🌟 Selected for report: m_Rassska
Also found by: 0x1337, 0xAadi, 0xHelium, 0xLeveler, 0xblackskull, 0xbrett8571, 0xepley, 0xffchain, 0xluckhu, 0xmystery, 0xrugpull_detector, 0xvj, ABAIKUNANBAEV, Aamir, AerialRaider, Amithuddar, Bauchibred, Bauer, CatsSecurity, Cryptor, Daniel526, Draiakoo, Eigenvectors, ElCid, GREY-HAWK-REACH, Inspecktor, Juntao, King_, LinKenji, Madalad, MaslarovK, Matin, MatricksDeCoder, McToady, Noro, PENGUN, Pechenite, Phantasmagoria, RaoulSchaffranek, SBSecurity, SandNallani, Shaheen, Soul22, Stormreckson, T1MOH, Tadev, TeamSS, TheSchnilch, Topmark, Tumelo_Crypto, Udsen, Yanchuan, ZanyBonzy, _thanos1, adeolu, adriro, alexfilippov314, almurhasan, amaechieth, anarcheuz, ayden, baice, bareli, boredpukar, bronze_pickaxe, btk, cartlex_, catellatech, chaduke, cheatc0d3, circlelooper, codynhat, crack-the-kelp, critical-or-high, debo, deepkin, desaperh, dipp, eeshenggoh, evmboi32, ge6a, gesha17, glcanvas, gumgumzum, hals, hihen, hunter_w3b, jasonxiale, joaovwfreire, ke1caM, leegh, lsaudit, marchev, merlinboii, niser93, osmanozdemir1, paritomarrr, passion, pep7siup, phoenixV110, pipidu83, poneta, ro1sharkm, rouhsamad, rvierdiiev, sakshamguruji, seerether, shealtielanz, soliditytaker, spark, squeaky_cactus, stackachu, supersizer0x, tallo, taner2344, turvy_fuzz, twcctop, ubl4nk, wisdomn_, xAriextz, zach, zhaojie, zhaojohnson, ziyou-
2.7592 USDC - $2.76
Users will be unable to deposit assets into the pool If the depsotiLimit is updated to zero, leading to a dos. The vulnerability lies in the fact that the function doesn't check to ensure that the deposit limit isn't being set to zero before updating it. This can lead to a denial of service (dos).
function updateAssetDepositLimit( address asset, uint256 depositLimit ) external onlyRole(LRTConstants.MANAGER) onlySupportedAsset(asset) { depositLimitByAsset[asset] = depositLimit; emit AssetDepositLimitUpdate(asset, depositLimit); }
A PoC to test the vulnerability. Run forge test to test the PoC. The test will show users unable to deposit due to the vulnerability.
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import { BaseTest } from "./BaseTest.t.sol"; import { LRTDepositPool } from "src/LRTDepositPool.sol"; import { RSETHTest, ILRTConfig, UtilLib, LRTConstants } from "./RSETHTest.t.sol"; import { ILRTDepositPool } from "src/interfaces/ILRTDepositPool.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; contract LRTOracleMock { function getAssetPrice(address) external pure returns (uint256) { return 1e18; } function getRSETHPrice() external pure returns (uint256) { return 1e18; } } contract MockNodeDelegator { function getAssetBalance(address) external pure returns (uint256) { return 1e18; } } contract LRTDepositPoolTest is BaseTest, RSETHTest { LRTDepositPool public lrtDepositPool; function setUp() public virtual override(RSETHTest, BaseTest) { super.setUp(); // deploy LRTDepositPool ProxyAdmin proxyAdmin = new ProxyAdmin(); LRTDepositPool contractImpl = new LRTDepositPool(); TransparentUpgradeableProxy contractProxy = new TransparentUpgradeableProxy( address(contractImpl), address(proxyAdmin), "" ); lrtDepositPool = LRTDepositPool(address(contractProxy)); // initialize RSETH. LRTCOnfig is already initialized in RSETHTest rseth.initialize(address(admin), address(lrtConfig)); vm.startPrank(admin); // add rsETH to LRT config lrtConfig.setRSETH(address(rseth)); // add oracle to LRT config lrtConfig.setContract(LRTConstants.LRT_ORACLE, address(new LRTOracleMock())); // add minter role for rseth to lrtDepositPool rseth.grantRole(rseth.MINTER_ROLE(), address(lrtDepositPool)); vm.stopPrank(); } } contract LRTDepositPoolGetAssetCurrentLimit is LRTDepositPoolTest { address public rETHAddress; function setUp() public override { super.setUp(); // initialize LRTDepositPool lrtDepositPool.initialize(address(lrtConfig)); rETHAddress = address(rETH); // add manager role within LRTConfig vm.startPrank(admin); lrtConfig.grantRole(LRTConstants.MANAGER, manager); vm.stopPrank(); } function test_DosDeposit() external { vm.startPrank(manager); lrtConfig.updateAssetDepositLimit(address(stETH), 0 ether); vm.stopPrank(); // deposit 1 ether stETH vm.startPrank(alice); stETH.approve(address(lrtDepositPool), 6 ether); lrtDepositPool.depositAsset(address(stETH), 6 ether); vm.stopPrank(); assertEq(lrtDepositPool.getAssetCurrentLimit(address(stETH)), 4 ether, "Asset current limit is not set"); } }
Manual Review and Foundry.
Check to ensure that the depositLimit of an asset isn't zero when updating the depositLimit
DoS
#0 - c4-pre-sort
2023-11-16T03:30:37Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-11-16T03:30:56Z
raymondfam marked the issue as duplicate of #69
#2 - c4-judge
2023-11-29T20:58:12Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#3 - c4-judge
2023-11-29T21:01:33Z
fatherGoose1 marked the issue as grade-b