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: 70/147
Findings: 2
Award: $20.70
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0x11singh99, 0xAadi, 0xAnah, 0xgrbr, 0xhacksmithh, 0xhex, 0xpiken, 0xta, J4X, JCK, K42, Raihan, Rolezn, SAQ, SM3_SS, Sathish9098, SovaSlava, ThreeSigma, Udsen, arjun16, aslanbek, brakelessak, castle_chain, evmboi32, hunter_w3b, lsaudit, naman1778, niser93, nuthan2x, oakcobalt, pavankv, petrichor, phenom80, radev_sw, shamsulhaq123, tabriz, thekmj, unique, yashgoel72, ybansal2403
6.4563 USDC - $6.46
Possible Optimization 1 =
keccak256
for role management (e.g., MINTER_ROLE, REDEEMER_ROLE, etc.). Bitwise operations is a more gas-efficient way to manage roles.After Optimization:
// Before bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE"); // After uint256 private constant MINTER_ROLE = 1 << 1;
SSTORE
and SLOAD
operations related to role management. The bitwise operation uses fewer opcodes compared to keccak256.Possible Optimization 2 =
After Optimization:
// Before if (order.order_type != OrderType.MINT) revert InvalidOrder(); verifyOrder(order, signature); // After if (order.order_type != OrderType.MINT || !verifyOrder(order, signature)) revert InvalidOrder();
Possible Optimization 3 =
Here is the optimized code snippet:
// Before uint256 totalTransferred = 0; for (uint256 i = 0; i < addresses.length; ++i) { uint256 amountToTransfer = (amount * ratios[i]) / 10_000; // ... } // After for (uint256 i = 0; i < addresses.length; ++i) { uint256 amountToTransfer = (amount * ratios[i]) / 10_000; uint256 totalTransferred = 0; // ... }
Possible Optimization 1 =
require
directly in the function can be more gas-efficient.After Optimization:
// Before modifier notZero(uint256 amount) { if (amount == 0) revert InvalidAmount(); _; } // After // Use require directly in the function require(amount != 0, "InvalidAmount");
Possible Optimization 2 =
calldata
for function parameters that are not modified within the function to save gas.After Optimization:
// Before function addToBlacklist(address target, bool isFullBlacklisting) external ... // After function addToBlacklist(address calldata target, bool isFullBlacklisting) external ...
calldata
is cheaper than memory or storage.Possible Optimization 3 =
Optimized Code Snippet:
// Before bytes32 private constant FULL_RESTRICTED_STAKER_ROLE = keccak256("FULL_RESTRICTED_STAKER_ROLE"); bytes32 private constant SOFT_RESTRICTED_STAKER_ROLE = keccak256("SOFT_RESTRICTED_STAKER_ROLE"); // After uint256 private constant FULL_RESTRICTED_STAKER_ROLE = 1 << 1; uint256 private constant SOFT_RESTRICTED_STAKER_ROLE = 1 << 2;
SSTORE
and SLOAD
operations related to role management.Possible Optimization 1 =
After Optimization:
function cooldown(uint256 amount, address owner, bool isAsset) external ensureCooldownOn returns (uint256) { uint256 limit = isAsset ? maxWithdraw(owner) : maxRedeem(owner); if (amount > limit) revert ExcessiveAmount(); uint256 otherAmount = isAsset ? previewWithdraw(amount) : previewRedeem(amount); cooldowns[owner].cooldownEnd = uint104(block.timestamp) + cooldownDuration; cooldowns[owner].underlyingAmount += amount; _withdraw(_msgSender(), address(silo), owner, amount, otherAmount); return otherAmount; }
isAsset
parameter to distinguish between assets and shares.Possible Optimization 2 =
revert
statements, emit events
for errors. This can save gas because revert consumes all remaining gas, while emitting an event will not.After Optimization:
event Error(string reason); modifier ensureCooldownOff() { if (cooldownDuration != 0) emit Error("OperationNotAllowed"); else _; } modifier ensureCooldownOn() { if (cooldownDuration == 0) emit Error("OperationNotAllowed"); else _; }
Possible Optimization 1 =
After Optimization:
event RoleChanged(address indexed account, bytes32 role, bool isGranted); function _grantRole(bytes32 role, address account) internal override { // ... existing code emit RoleChanged(account, role, true); } function _revokeRole(bytes32 role, address account) internal override { // ... existing code emit RoleChanged(account, role, false); }
Possible Optimization 2 =
onlyRole(DEFAULT_ADMIN_ROLE)
and notAdmin(role)
, use bitfields to combine multiple checks into a single modifier.After Optimization:
modifier roleChecks(bytes32 role, uint8 checks) { if ((checks & 0x01) == 0x01 && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert InvalidAdminChange(); if ((checks & 0x02) == 0x02 && role == DEFAULT_ADMIN_ROLE) revert InvalidAdminChange(); _; } function grantRole(bytes32 role, address account) public override roleChecks(role, 0x03) { _grantRole(role, account); }
JUMP
and JUMPI
opcodes.#0 - c4-pre-sort
2023-11-01T15:40:16Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-11-10T20:09:56Z
fatherGoose1 marked the issue as grade-b
🌟 Selected for report: radev_sw
Also found by: 0xSmartContract, 0xweb3boy, Al-Qa-qa, Bauchibred, Bulletprime, D_Auditor, J4X, JCK, K42, Kral01, Sathish9098, ZanyBonzy, albahaca, catellatech, clara, digitizeworx, fouzantanveer, hunter_w3b, invitedtea, jauvany, oakcobalt, pavankv, peanuts, xiao
14.2357 USDC - $14.24
1. Structures and Libraries
2. Modifiers and Access Control
ensureCooldownOff
and ensureCooldownOn
could be more descriptive. Consider renaming to requireCooldownInactive
and requireCooldownActive
.3. State Variables
cooldownDuration
in StakedUSDeV2.sol should be better encapsulated to prevent unauthorized changes.cooldowns
in StakedUSDeV2.sol should be justified in inline comments.4. Events and Logging
5. Function Complexity
mint()
and redeem()
in EthenaMinting.sol perform multiple tasks, making them more complex and harder to audit.transferInRewards()
in StakedUSDe.sol could be optimized for gas by reducing state changes.6. Upgradability
7. Error Handling
revert()
statements with error messages, which is a good practice for debugging.mint()
nonReentrant
but still performs multiple state changes after external calls.redeem()
setMaxMintPerBlock()
and setMaxRedeemPerBlock()
transferInRewards()
addToBlacklist()
and removeFromBlacklist()
rescueTokens()
withdraw()
and redeem()
unstake()
cooldownAssets()
and cooldownShares()
Function interaction graphs I made for each contract for better visualization of function interactions:
Link to Graph for EthenaMinting.sol.
Link to Graph for StakedUSDe.sol.
Link to Graph for StakedUSDeV2.sol.
Link to Graph for USDeSilo.sol.
Link to Graph for SingleAdminAccessControl.sol.
15 hours
#0 - c4-pre-sort
2023-11-01T14:24:25Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-11-10T19:35:57Z
fatherGoose1 marked the issue as grade-b