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: 141/147
Findings: 1
Award: $4.52
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
EthenaMinting.verifyRoutedoes
not verify that there are duplicate elements in route[]In the for loop does not verify that there are duplicate elements in route[], so the caller can pass in multiple routes of the same value, and the variable totalRatio can be added repeatedly.
uint256 totalRatio = 0; ..... for (uint256 i = 0; i < route.addresses.length; ++i) { if (!_custodianAddresses.contains(route.addresses[i]) || route.addresses[i] == address(0) || route.ratios[i] == 0) { return false; } @> totalRatio += route.ratios[i]; }
When the amount * ratios[i] < 10_000,amountToTransfer
is 0, so that the token amount received in addressesi is 0.
Since totalTransferred
is recorded in the function, the end will transfer the amount of tokens that have not been transferred to the last address in the addresses
, so there is no loss of funds.
But it may cause another problem: For example, a caller in mint wants to assign a small percentage of the collateral token to another route, the token is addressed in addresses[0], However, because the number is too small, division overflow occurs, the token amount received in addresses[0] is 0, and the tokens are all transferred to addresses[1], which is inconsistent with the result expected by the caller.
function _transferCollateral( uint256 amount, address asset, address benefactor, address[] calldata addresses, uint256[] calldata ratios ) internal { ..... for (uint256 i = 0; i < addresses.length; ++i) { @> uint256 amountToTransfer = (amount * ratios[i]) / 10_000; @> token.safeTransferFrom(benefactor, addresses[i], amountToTransfer); totalTransferred += amountToTransfer; } uint256 remainingBalance = amount - totalTransferred; if (remainingBalance > 0) { token.safeTransferFrom(benefactor, addresses[addresses.length - 1], remainingBalance); } }
There are no emit events in public methods such as unstake
cooldownAssets
in StakedUSDeV2, only setCooldownDuration
has an event log.
There is no such problem with the EthenaMinting contract.
function unstake(address receiver) external { UserCooldown storage userCooldown = cooldowns[msg.sender]; uint256 assets = userCooldown.underlyingAmount; if (block.timestamp >= userCooldown.cooldownEnd) { userCooldown.cooldownEnd = 0; userCooldown.underlyingAmount = 0; silo.withdraw(receiver, assets); } else { revert InvalidCooldown(); } }
SingleAdminAccessControl.grantRole has 2 Modifiers, onlyRole and notAdmin,
onlyRole like: Must be ADMIN,
notAdmin like: Cannot be ADMIN,
It looks like the opposite, it's confusing,
In fact, msg.sender
is restricted by onlyRole, while notAdmin restricts the permission of the passed parameter role
. It is recommended to add a description in the comment.
/// @notice grant a role /// @notice can only be executed by the current single admin /// @notice admin role cannot be granted externally /// @param role bytes32 /// @param account address function grantRole(bytes32 role, address account) public override onlyRole(DEFAULT_ADMIN_ROLE) notAdmin(role) { _grantRole(role, account); }
#0 - c4-pre-sort
2023-11-02T03:27:08Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-11-14T16:47:27Z
fatherGoose1 marked the issue as grade-b