Platform: Code4rena
Start Date: 24/10/2023
Pot Size: $149,725 USDC
Total HM: 7
Participants: 52
Period: 21 days
Judge: ronnyx2017
Total Solo HM: 2
Id: 300
League: ETH
Rank: 52/52
Findings: 1
Award: $19.71
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SpicyMeatball
Also found by: 0xBeirao, 7ashraf, LokiThe5th, OMEN, TrungOre, alexzoid, alpha, bdmcbri, ether_sky, fatherOfBlocks, ge6a, hihen, hunter_w3b, jasonxiale, ladboy233, lsaudit, niroh, nobody2018, nonseodion, peanuts, prapandey031, shaka, twcctop, twicek, wangxx2026
19.712 USDC - $19.71
_descendList
and _ascendList
contain incorrect termination conditionThe first loop condition when ascending and descending the sorted cdp list is unnecessary or incorrect, but it is unclear that it causes any problems
I believe this is the correct logic:
diff --git a/packages/contracts/contracts/SortedCdps.sol b/packages/contracts/contracts/SortedCdps.sol index 0fd34e7..f37cdc4 100644 --- a/packages/contracts/contracts/SortedCdps.sol +++ b/packages/contracts/contracts/SortedCdps.sol @@ -626,7 +626,7 @@ contract SortedCdps is ISortedCdps { bytes32 nextId = data.nodes[prevId].nextId; // Descend the list until we reach the end or until we find a valid insert position - while (prevId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) { + while (nextId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) { prevId = data.nodes[prevId].nextId; nextId = data.nodes[prevId].nextId; } @@ -649,7 +649,7 @@ contract SortedCdps is ISortedCdps { bytes32 prevId = data.nodes[nextId].prevId; // Ascend the list until we reach the end or until we find a valid insertion point - while (nextId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) { + while (prevId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) { nextId = data.nodes[nextId].prevId; prevId = data.nodes[nextId].prevId; }
However, removing the first condition also is perfectly valid as the necessary logic for loop termination is redundantly captured in _validInsertPosition
. The following logic also works:
diff --git a/packages/contracts/contracts/SortedCdps.sol b/packages/contracts/contracts/SortedCdps.sol index 0fd34e7..7da9745 100644 --- a/packages/contracts/contracts/SortedCdps.sol +++ b/packages/contracts/contracts/SortedCdps.sol @@ -626,7 +626,7 @@ contract SortedCdps is ISortedCdps { bytes32 nextId = data.nodes[prevId].nextId; // Descend the list until we reach the end or until we find a valid insert position - while (prevId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) { + while (!_validInsertPosition(_NICR, prevId, nextId)) { prevId = data.nodes[prevId].nextId; nextId = data.nodes[prevId].nextId; } @@ -649,7 +649,7 @@ contract SortedCdps is ISortedCdps { bytes32 prevId = data.nodes[nextId].prevId; // Ascend the list until we reach the end or until we find a valid insertion point - while (nextId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) { + while (!_validInsertPosition(_NICR, prevId, nextId)) { nextId = data.nodes[nextId].prevId; prevId = data.nodes[nextId].prevId; }
If this is not correct, the test cases do not reflect the need for this condition, as all three instances pass the tests.
In RolesAuthority.sol
there are a couple checks to see if an EnumerableSet contains an item before adding it to the set. This is unnecessary as add
in EnumerableSet will also check for containment.
L116 in setRoleCapability
:
if (!targets.contains(target)) {//@audit This is a redundant check as targets is an EnumerableSet and targets.add will check containment targets.add(target); }
L151 in setUserRole
:
if (!users.contains(user)) {//@audit This is a redundant check as users is an EnumerableSet and users.add will check containment users.add(user); }
L65 in EnumerableSet
shows that add
checks for the value in the set:
/** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value] = set._values.length; return true; } else { return false; } }
diff --git a/packages/contracts/contracts/Dependencies/RolesAuthority.sol b/packages/contracts/contracts/Dependencies/RolesAuthority.sol index 14fbbf3..49cdba5 100644 --- a/packages/contracts/contracts/Dependencies/RolesAuthority.sol +++ b/packages/contracts/contracts/Dependencies/RolesAuthority.sol @@ -112,10 +112,7 @@ contract RolesAuthority is IRolesAuthority, Auth, Authority { if (enabled) { getRolesWithCapability[target][functionSig] |= bytes32(1 << role); enabledFunctionSigsByTarget[target].add(bytes32(functionSig)); - - if (!targets.contains(target)) { - targets.add(target); - } + targets.add(target); } else { getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role); enabledFunctionSigsByTarget[target].remove(bytes32(functionSig)); @@ -147,10 +144,7 @@ contract RolesAuthority is IRolesAuthority, Auth, Authority { function setUserRole(address user, uint8 role, bool enabled) public virtual requiresAuth { if (enabled) { getUserRoles[user] |= bytes32(1 << role); - - if (!users.contains(user)) { - users.add(user); - } + users.add(user); } else { getUserRoles[user] &= ~bytes32(1 << role);
_openCdp
The following check in L463 of _openCdp
is unneeded:
require(vars.netStEthBalance > 0, "BorrowerOperations: zero collateral for openCdp()!");
As the check in L454, _requireAtLeastMinNetStEthBalance(vars.netStEthBalance)
covers it already (defined on L939):
function _requireAtLeastMinNetStEthBalance(uint256 _stEthBalance) internal pure { require( _stEthBalance >= MIN_NET_STETH_BALANCE, "BorrowerOperations: Cdp's net stEth balance must not fall below minimum" ); }
MIN_NET_STETH_BALANCE
is defined in EbtcBase.sol
, L31:
uint256 public constant MIN_NET_STETH_BALANCE = 2e18;
diff --git a/packages/contracts/contracts/BorrowerOperations.sol b/packages/contracts/contracts/BorrowerOperations.sol index db7a32e..ed822cd 100644 --- a/packages/contracts/contracts/BorrowerOperations.sol +++ b/packages/contracts/contracts/BorrowerOperations.sol @@ -459,9 +459,6 @@ contract BorrowerOperations is vars.price = priceFeed.fetchPrice(); vars.debt = _debt; - // Sanity check - require(vars.netStEthBalance > 0, "BorrowerOperations: zero collateral for openCdp()!"); - uint256 _netCollAsShares = collateral.getSharesByPooledEth(vars.netStEthBalance); uint256 _liquidatorRewardShares = collateral.getSharesByPooledEth(LIQUIDATOR_REWARD);
#0 - c4-pre-sort
2023-11-17T13:21:32Z
bytes032 marked the issue as sufficient quality report
#1 - c4-judge
2023-11-27T10:24:31Z
jhsagd76 marked the issue as grade-b