Platform: Code4rena
Start Date: 24/10/2023
Pot Size: $36,500 USDC
Total HM: 4
Participants: 147
Period: 6 days
Judge: 0xDjango
Id: 299
League: ETH
Rank: 7/147
Findings: 2
Award: $1,436.70
π Selected for report: 0
π Solo Findings: 0
π Selected for report: adeolu
Also found by: Eeyore, Madalad, Mike_Bello90, Shubham, jasonxiale, josephdara, peanuts
1432.1788 USDC - $1,432.18
When the owner changes the Cooldown Duration from 90 to 0 days, all users should be able to withdraw immediately.
The contract functionality for withdrawing assets from the staking system is that when the cooldown duration is set to 0 all users should be able to withdraw tokens without the need to wait for the cooldown duration to end, but this is not possible in all cases.
Let's imagine the next scenario:
When the StakedUSDeV2 contract is deployed the Cooldown Duration is set to 90 days, while this cooldown is set, a bunch of users ask for a withdrawal using the cooldownAssets or the cooldownShares functions, so, the User Cooldown
struct is filled and the user has to wait for 90 days to unstake their tokens, but if the owner changes the Cooldown Duration to 0, while these users are waiting for the 90 days to be finished, they should be able to withdraw their tokens right away like any other user, cause now the Cooldown Duration is 0, this is not possible right now.
this does not affect the balances of the users, is just a functionality improvement that can be made in the unstake function so any user can withdraw right away when the Cooldown Duration is set to 0.
share you a test that validate this scenario where users are unable to withdraw immediately when Cooldown Duration is set to 0.
You can add this test to the file StakedUSDeV2.cooldownEnabled.t.sol in the staking tests to validate the scenario:
function test_UserUnable_ToUnstake_ImmediatelyAfter_CooldownDuration_ResetTo_0() public { uint256 amount = 100 ether; // Alice stake some tokens in the Contract. _mintApproveDeposit(alice, amount); // Checking Balances are correct. assertEq(usdeToken.balanceOf(alice), 0); assertEq(usdeToken.balanceOf(address(stakedUSDe)), amount); assertEq(stakedUSDe.balanceOf(alice), amount); // Alice ask for a reedem while the cooldown is active. vm.prank(alice); stakedUSDe.cooldownShares(amount, alice); (uint104 cooldownEnd,) = stakedUSDe.cooldowns(alice); // 20 days have passed, and before Alice's cooldown finish the owner sets the cooldownDuration = 0 vm.warp(cooldownEnd - 70 days); vm.prank(owner); stakedUSDe.setCooldownDuration(0); // Alice and other users see the change in cooldown duration and try to unstake cause they need their money right away, // but they're unable to unstake immediately and they have to wait for the 90-day period to end. vm.expectRevert(IStakedUSDeCooldown.InvalidCooldown.selector); vm.prank(alice); stakedUSDe.unstake(alice); }
To solve this problem you can check in the unstake function if the cooldown duration is set to 0, if true allows the users to withdraw their tokens without checking if the user's cooldownEnd
time has passed, if not follow the current workflow the unstake function has.
to see an example check the recommended Mitigation section above.
Manual Review.
To solve the scenario mentioned before you can change the unstake function to check if the cooldown duration is equal to 0 or not and act according to this in the unstake function, allowing the users to withdraw right away or making them to wait for the cooldown period to end.
this can be a way to implement this solution in the unstake function:
function unstake(address receiver) external { UserCooldown storage userCooldown = cooldowns[msg.sender]; uint256 assets = userCooldown.underlyingAmount; if (cooldownDuration != 0) { // checking if the cooldownDuration is > 0 if (block.timestamp >= userCooldown.cooldownEnd) { userCooldown.cooldownEnd = 0; userCooldown.underlyingAmount = 0; silo.withdraw(receiver, assets); } else { revert InvalidCooldown(); } } else { // if cooldownDuration = 0 allow the withdrawal without checking userCooldown.cooldownEnd userCooldown.cooldownEnd = 0; userCooldown.underlyingAmount = 0; silo.withdraw(receiver, assets); } }
Other
#0 - c4-pre-sort
2023-11-01T01:48:59Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-01T01:49:09Z
raymondfam marked the issue as duplicate of #29
#2 - c4-judge
2023-11-13T19:05:41Z
fatherGoose1 marked the issue as satisfactory
#3 - c4-judge
2023-11-17T02:45:06Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#4 - c4-judge
2023-11-17T16:47:08Z
This previously downgraded issue has been upgraded by fatherGoose1
#5 - c4-judge
2023-11-27T19:58:25Z
fatherGoose1 marked the issue as not a duplicate
#6 - c4-judge
2023-11-27T19:58:34Z
fatherGoose1 marked the issue as duplicate of #198
π Selected for report: 0xmystery
Also found by: 0x11singh99, 0xAadi, 0xAlix2, 0xG0P1, 0xStalin, 0xWaitress, 0x_Scar, 0xhacksmithh, 0xhunter, 0xpiken, Al-Qa-qa, Arz, Avci, Bauchibred, BeliSesir, Breeje, Bughunter101, DarkTower, Eeyore, Fitro, HChang26, Imlazy0ne, J4X, JCK, Kaysoft, Kral01, Madalad, Mike_Bello90, Noro, PASCAL, PENGUN, Proxy, Rickard, Shubham, SovaSlava, Strausses, Team_Rocket, ThreeSigma, Topmark, Udsen, Walter, Yanchuan, Zach_166, ZanyBonzy, adam-idarrha, adeolu, almurhasan, arjun16, ast3ros, asui, ayden, btk, cartlex_, castle_chain, cccz, chainsnake, codynhat, critical-or-high, cryptonue, csanuragjain, deepkin, degensec, dirk_y, erebus, foxb868, ge6a, hunter_w3b, jasonxiale, kkkmmmsk, lanrebayode77, lsaudit, marchev, matrix_0wl, max10afternoon, nuthan2x, oakcobalt, oxchsyston, pavankv, peanuts, pep7siup, pipidu83, pontifex, ptsanev, qpzm, radev_sw, rokinot, rotcivegaf, rvierdiiev, sorrynotsorry, squeaky_cactus, supersizer0x, tnquanghuy0512, twcctop, twicek, young, zhaojie, ziyou-
4.5226 USDC - $4.52
ID | Title |
---|---|
1. | Use Named Parameters in Mapping Types to Improve Code Readability. |
2. | Use the Same Compiler Version in the Whole CodeBase. |
3. | Follow the Recommended Solidity Layout in Contracts to Improve Code Readability. |
4. | Instead of Hashing in the Contract Calculate the Hash off-chain. |
Since solidity 0.8.18 named parameters in mappings types are allowed, this feature was enabled to improve code readability, this is why it's recommended as a good practice to use named parameters in mapping to improve code readability for your team members or other developers like auditors.
you can implement this feature in all mappings, for example in the mintedPerBlock mapping in the EthenaMining contract:
/// @notice USDe minted per block mapping(uint256 => uint256) public mintedPerBlock;
you can improve the readability of the mapping declaration like this:
/// @notice USDe minted per block mapping(uint256 blockNumber => uint256 amountMinted) public mintedPerBlock;
These are the Github links to the mappings in the codebase on which you can implement this improvement:
looking at the files in the repository I found that almost all contracts are using the fixed solidity version 0.8.19, except for the USDeSilo contract which uses the floating pragma ^0.8.0.
For security, itβs recommended to use the same fixed compiler pragma version in all your contracts, in this way, you can know with certainty the compiler version all your contracts use, so you know which compiler bugs can affect your contracts, and you only have to worry for the bug of one compiler version for all your contracts.
itβs recommended to use the fixed pragma solidity version in USDeSilo.sol. Change the current pragma from this:
pragma solidity ^0.8.0;
To this:
pragma solidity 0.8.19;
This is the GitHub link to the code section: https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/USDeSilo.sol#L2
Solidity documentation recommends following a specified layout in contract declarations to improve code readability, this is a good practice recommended for writing clean, and easy-to-maintain code.
The USDeSilo contract doesnβt follow the recommended solidity layout, which says the following: https://docs.soliditylang.org/en/latest/style-guide.html#order-of-layout
Inside each contract, library or interface, use the following order: Type declarations State variables Events Errors Modifiers Functions Just as a reminder, solidity also recommends a way to order the functions in the contracts:
https://docs.soliditylang.org/en/latest/style-guide.html#order-of-functions
Ordering helps readers identify which functions they can call and find the constructor and fallback definitions more easily.
Functions should be grouped according to their visibility and ordered: constructor receive function (if exists) fallback function (if exists) external public internal private Within a grouping, place the view and pure functions last. example:
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; contract A { constructor() { // ... } receive() external payable { // ... } fallback() external { // ... } // External functions // ... // External functions that are view // ... // External functions that are pure // ... // Public functions // ... // Internal functions // ... // Private functions // ... }
The contracts EthenaMinting and StakeUSDe are using the keccak256 function to calculate the hash for roles and functions signatures assigned to constant values during deployment, this increases the deployment gas cost.
In order to improve gas efficiency and reduce gas costs during the contracts deployment, you can hash the values off-chain in a tool like https://emn178.github.io/online-tools/keccak_256.html and just assign the calculated hash of the values to the constant declared in the contract.
for example, in the StakeUSDe you can hash the value of the BLACKLIST_MANAGER_ROLE in the tool mentioned before and assign that value in the contract.
You can change this:
/// @notice The role that is allowed to blacklist and un-blacklist addresses bytes32 private constant BLACKLIST_MANAGER_ROLE = keccak256("BLACKLIST_MANAGER_ROLE");
To this:
/// @notice The role that is allowed to blacklist and un-blacklist addresses bytes32 private constant BLACKLIST_MANAGER_ROLE = 0xf988e4fb62b8e14f4820fed03192306ddf4d7dbfa215595ba1c6ba4b76b369ee;
and you can do something similar with the rest of the constant values that use keccak256.
#0 - raymondfam
2023-11-02T02:23:53Z
1 - 3 have been found by the bot.
#1 - c4-pre-sort
2023-11-02T02:24:04Z
raymondfam marked the issue as low quality report
#2 - c4-judge
2023-11-14T16:48:12Z
fatherGoose1 marked the issue as grade-b