Platform: Code4rena
Start Date: 24/03/2023
Pot Size: $49,200 USDC
Total HM: 20
Participants: 246
Period: 6 days
Judge: Picodes
Total Solo HM: 1
Id: 226
League: ETH
Rank: 109/246
Findings: 2
Award: $37.07
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: silviaxyz
Also found by: 0xMirce, 0xbepresent, CodingNameKiki, Franfran, HollaDieWaldfee, MiloTruck, Tricko, adriro, codeislight, cryptonue, d3e4, ladboy233, rbserver, shaka, volodya
28.8013 USDC - $28.80
When rocketpool node deposits are not enabled, users will not be able to stake.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Whenever user stake system check whether rocketpool can be deposited to - poolCanDeposit
function ethPerDerivative(uint256 _amount) public view returns (uint256) { if (poolCanDeposit(_amount)) return RocketTokenRETHInterface(rethAddress()).getEthValue(10 ** 18); else return (poolPrice() * 10 ** 18) / (10 ** 18); }
SafEth/derivatives/Reth.sol#L212
Here is the function poolCanDeposit
function poolCanDeposit(uint256 _amount) private view returns (bool) { address rocketDepositPoolAddress = RocketStorageInterface( ROCKET_STORAGE_ADDRESS ).getAddress( keccak256( abi.encodePacked("contract.address", "rocketDepositPool") ) ); RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface( rocketDepositPoolAddress ); address rocketProtocolSettingsAddress = RocketStorageInterface( ROCKET_STORAGE_ADDRESS ).getAddress( keccak256( abi.encodePacked( "contract.address", "rocketDAOProtocolSettingsDeposit" ) ) ); RocketDAOProtocolSettingsDepositInterface rocketDAOProtocolSettingsDeposit = RocketDAOProtocolSettingsDepositInterface( rocketProtocolSettingsAddress ); return rocketDepositPool.getBalance() + _amount <= rocketDAOProtocolSettingsDeposit.getMaximumDepositPoolSize() && _amount >= rocketDAOProtocolSettingsDeposit.getMinimumDeposit(); }
SafEth/derivatives/Reth.sol#L120
I think the whole system was designed to work even when user cannot deposit to rocketpool. Here you can see code from rocketpool, require from line 77 is not implemented, all the other requires are in the place
function deposit() override external payable onlyThisLatestContract { // Check deposit settings RocketDAOProtocolSettingsDepositInterface rocketDAOProtocolSettingsDeposit = RocketDAOProtocolSettingsDepositInterface(getContractAddress("rocketDAOProtocolSettingsDeposit")); 77: require(rocketDAOProtocolSettingsDeposit.getDepositEnabled(), "Deposits into Rocket Pool are currently disabled"); require(msg.value >= rocketDAOProtocolSettingsDeposit.getMinimumDeposit(), "The deposited amount is less than the minimum deposit size"); RocketVaultInterface rocketVault = RocketVaultInterface(getContractAddress("rocketVault")); require(rocketVault.balanceOf("rocketDepositPool").add(msg.value) <= rocketDAOProtocolSettingsDeposit.getMaximumDepositPoolSize(), "The deposit pool size after depositing exceeds the maximum size"); // Calculate deposit fee uint256 depositFee = msg.value.mul(rocketDAOProtocolSettingsDeposit.getDepositFee()).div(calcBase); uint256 depositNet = msg.value.sub(depositFee); // Mint rETH to user account RocketTokenRETHInterface rocketTokenRETH = RocketTokenRETHInterface(getContractAddress("rocketTokenRETH")); rocketTokenRETH.mint(depositNet, msg.sender); // Emit deposit received event emit DepositReceived(msg.sender, msg.value, block.timestamp); // Process deposit processDeposit(rocketVault, rocketDAOProtocolSettingsDeposit); }
deposit/RocketDepositPool.sol#L73-L91
Manual review
Add missing check, so user`s transactions will not be reverted when rocketpool node deposits are not enabled
function poolCanDeposit(uint256 _amount) private view returns (bool) { address rocketDepositPoolAddress = RocketStorageInterface( ROCKET_STORAGE_ADDRESS ).getAddress( keccak256( abi.encodePacked("contract.address", "rocketDepositPool") ) ); RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface( rocketDepositPoolAddress ); address rocketProtocolSettingsAddress = RocketStorageInterface( ROCKET_STORAGE_ADDRESS ).getAddress( keccak256( abi.encodePacked( "contract.address", "rocketDAOProtocolSettingsDeposit" ) ) ); RocketDAOProtocolSettingsDepositInterface rocketDAOProtocolSettingsDeposit = RocketDAOProtocolSettingsDepositInterface( rocketProtocolSettingsAddress ); return + rocketDAOProtocolSettingsDeposit.getDepositEnabled() && rocketDepositPool.getBalance() + _amount <= rocketDAOProtocolSettingsDeposit.getMaximumDepositPoolSize() && _amount >= rocketDAOProtocolSettingsDeposit.getMinimumDeposit(); }
#0 - c4-pre-sort
2023-04-04T19:01:39Z
0xSorryNotSorry marked the issue as duplicate of #812
#1 - c4-judge
2023-04-24T19:43:55Z
Picodes marked the issue as satisfactory
🌟 Selected for report: __141345__
Also found by: 0xWaitress, 0xbepresent, AkshaySrivastav, Bauer, Haipls, HollaDieWaldfee, Lirios, MiloTruck, SaeedAlipoor01988, UdarTeam, adriro, bytes032, ck, d3e4, hihen, hl_, kaden, ladboy233, lopotras, m_Rassska, peanuts, reassor, volodya
8.2654 USDC - $8.27
If WstEth and SfrxEth cannot be deposited, the stake
function will be reverted.
There is a check on the Reth
derivative to determine if it is possible to deposit into it; otherwise, the Uniswap router will be used to exchange ETH for Reth
.
function poolCanDeposit(uint256 _amount) private view returns (bool) { address rocketDepositPoolAddress = RocketStorageInterface(
SafEth/derivatives/Reth.sol#L120
Since there are no checks for WstEth and SfrxEth, the stake function will be reverted if either of them cannot be deposited. require check
*/ function _submit(address _referral) internal returns (uint256) { require(msg.value != 0, "ZERO_DEPOSIT"); StakeLimitState.Data memory stakeLimitData = STAKING_STATE_POSITION.getStorageStakeLimitStruct(); require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED");// require check if (stakeLimitData.isStakingLimitSet()) { uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT");// require check STAKING_STATE_POSITION.setStorageStakeLimitStruct( stakeLimitData.updatePrevStakeLimit(currentStakeLimit - msg.value) ); } uint256 sharesAmount = getSharesByPooledEth(msg.value); if (sharesAmount == 0) { // totalControlledEther is 0: either the first-ever deposit or complete slashing // assume that shares correspond to Ether 1-to-1 sharesAmount = msg.value; } _mintShares(msg.sender, sharesAmount); BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(msg.value)); emit Submitted(msg.sender, msg.value, _referral); _emitTransferAfterMintingShares(msg.sender, sharesAmount); return sharesAmount; }
0x47ebab13b806773ec2a2d16873e2df770d130b50#code#F1#L675
function _submit(address recipient) internal nonReentrant { // Initial pause and value checks require(!submitPaused, "Submit is paused");// require check require(msg.value != 0, "Cannot submit 0"); // Give the sender frxETH frxETHToken.minter_mint(recipient, msg.value); // Track the amount of ETH that we are keeping uint256 withheld_amt = 0; if (withholdRatio != 0) { withheld_amt = (msg.value * withholdRatio) / RATIO_PRECISION; currentWithheldETH += withheld_amt; } emit ETHSubmitted(msg.sender, recipient, msg.value, withheld_amt); }
0xbAFA44EFE7901E04E39Dad13167D089C559c1138#code#F1#L85
You should consider adding checks for WstEth and SfrxEth and swapping ETH to those tokens on Uniswap, similar to what you have done with Reth. It might be helpful to create a function called poolCanDeposit
inside IDerivative.
For WstEth
it will look like this
function poolCanDeposit(){ uint256 currentStakeLimit = ISTETH_TOKEN(STETH_TOKEN).getCurrentStakeLimit(); return msg.value <= currentStakeLimit && !ISTETH_TOKEN(STETH_TOKEN).isStakingPaused(); }
for SfrxEth like this
function poolCanDeposit(){ IFrxETHMinter frxETHMinterContract = IFrxETHMinter( FRX_ETH_MINTER_ADDRESS ); return !frxETHMinterContract.submitPaused(); }
#0 - c4-pre-sort
2023-04-04T22:02:13Z
0xSorryNotSorry marked the issue as duplicate of #328
#1 - c4-judge
2023-04-21T10:47:05Z
Picodes marked the issue as duplicate of #770
#2 - c4-judge
2023-04-24T18:30:27Z
Picodes marked the issue as satisfactory