Platform: Code4rena
Start Date: 06/01/2023
Pot Size: $210,500 USDC
Total HM: 27
Participants: 73
Period: 14 days
Judge: 0xean
Total Solo HM: 18
Id: 203
League: ETH
Rank: 37/73
Findings: 2
Award: $194.03
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: CodingNameKiki
Also found by: 0xA5DF, 0xAgro, 0xNazgul, 0xSmartContract, Aymen0909, BRONZEDISC, Bnke0x0, Breeje, Cyfrin, GalloDaSballo, HollaDieWaldfee, IceBear, IllIllI, MyFDsYours, RaymondFam, Ruhum, SaharDevep, Sathish9098, Soosh, Udsen, __141345__, brgltd, btk, carlitox477, chaduke, chrisdior4, cryptonue, delfin454000, descharre, hihen, joestakey, ladboy233, lukris02, luxartvinsec, peanuts, pedr02b2, rotcivegaf, shark, tnevler, yongskiws
121.587 USDC - $121.59
Issue | Description | Instances |
---|---|---|
1 | Use require rather than assert where appropriate | 11 |
Total | 11 |
Issue | Description | Instances |
---|---|---|
1 | Named return variable is not used | 5 |
2 | Constant value definitions including call to keccak256 should use immutable | 8 |
3 | nonReentrant modifier should appear before other modifiers | 1 |
4 | Unused override function arguments | 2 |
5 | Open items should be addressed | 3 |
6 | Event is missing indexed fields | 10 |
7 | Remove references to pragma experimental ABIEncoderV2 | 4 |
8 | pragma solidity version should be upgraded to latest version before finalization | 8 + multiple |
9 | Consider removing internal functions that are never used | 9 |
10 | Long single-line comments | 2 |
11 | Typos | 31 |
12 | Update sensitive terms | 3 |
Total | 86 + multiple |
No. | Explanation + cases |
---|---|
1 | Use require rather than assert where appropriate |
On failure, the assert function causes a Panic error and, unlike require , does not generate an error string. According to Solidity v0.8.17, "properly functioning code should never create a Panic." Therefore, an assert should be used only if, based on the relevant code, it is not expected to throw an exception. |
Below is an example of a valid use of assert
in Reserve Protocol:
// Optionally process settlement of the auction in Gnosis if (!isAuctionCleared()) { // By design, we don't rely on this return value at all, just the // "cleared" state of the auction, and the token balances this contract owns. // slither-disable-next-line unused-return gnosis.settleAuction(auctionId); assert(isAuctionCleared()); }
The following assert
functions appear not to meet the criteria and should be replaced by require
functions:
RecollateralizationLib.sol: L110
No. | Description |
---|---|
1 | Named return variable is not used |
function prepareTradeSell(TradeInfo memory trade, TradingRules memory rules) internal view returns (bool notDust, TradeRequest memory req) }
Here notDust
is never used, which is especially odd since it is discussed fairly extensively in the comments above the function.
Similarly for the function below, in which notDust
is also named:
function getActCalldata(RTokenP1 rToken) external returns (address to, bytes memory calldata_);
calldata_
is never used
function settleAuction(uint256 auctionId) external returns (bytes32 encodedOrder);
encodedOrder
is never used (it is used in a mock file)
function issue(address recipient, uint256 amount) external returns (uint256 mintedAmount);
mintedAmount
is not used (it is used in the out of scope contracts/p0/RToken.sol)
No. | Description |
---|---|
2 | Constant value definitions including call to keccak256 should use immutable |
Constant value definitions such as a call to keccak256 should use immutable instead of constant |
bytes32 private constant _PERMIT_TYPEHASH = keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" );
bytes32 private constant _DELEGATE_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
bytes32 constant UIntOutofBoundsHash = keccak256(abi.encodeWithSignature("UIntOutOfBounds()"));
bytes32 internal constant EIP712_DOMAIN = keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" );
bytes32 public constant PERMIT_TYPEHASH = keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" );
bytes32 public constant METADEPOSIT_TYPEHASH = keccak256( "Deposit(address depositor,address recipient,uint256 value,uint16 referralCode,bool fromUnderlying,uint256 nonce,uint256 deadline)" );
bytes32 public constant METAWITHDRAWAL_TYPEHASH = keccak256( "Withdraw(address owner,address recipient,uint256 staticAmount,uint256 dynamicAmount,bool toUnderlying,uint256 nonce,uint256 deadline)" );
ERC20PermitUpgradeable.sol: L39-42
bytes32 private constant _PERMIT_TYPEHASH = keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" );
No. | Description |
---|---|
3 | nonReentrant modifier should appear before other modifiers |
Adopting this rule protects against the possibility of reentrancy in other modifiers |
function settleTrade(IERC20 sell) external notPausedOrFrozen nonReentrant {
No. | Description |
---|---|
4 | Unused override function arguments |
If an override function argument is never used, the variable name should be removed or else commented out to avoid compiler warnings. Below, newImplementation is unused. |
function _authorizeUpgrade(address newImplementation) internal override onlyRole(OWNER) {}
Similarly for the following function:
No. | Description |
---|---|
5 | Open items should be addressed |
Open items should be resolved before deployment |
// In the future we'll have more sophisticated choice logic here, probably by trade size
* @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain.
/// @dev Don't actually execute this!
No. | Description |
---|---|
6 | Event is missing indexed fields |
Each event should use three indexed fields if there are three or more fields; if fewer, all should be indexed, assuming gas cost not too expensive |
event DistributionSet(address dest, uint16 rTokenDist, uint16 rsrDist);
event Redemption(address indexed redeemer, uint256 indexed amount, uint192 baskets);
event BasketsNeededChanged(uint192 oldBasketsNeeded, uint192 newBasketsNeeded);
event Melted(uint256 amount);
event Mint(address indexed from, uint256 value, uint256 index);
event Burn(address indexed from, address indexed target, uint256 value, uint256 index);
event BalanceTransfer(address indexed from, address indexed to, uint256 value, uint256 index);
IAaveIncentivesController.sol: L6
event RewardsAccrued(address indexed user, uint256 amount);
IAaveIncentivesController.sol: L8
event RewardsClaimed(address indexed user, address indexed to, uint256 amount);
event BasketSet(uint256 indexed nonce, IERC20[] erc20s, uint192[] refAmts, bool disabled);
In this case, the only change needed is to convert bool disabled
to bool indexed disabled
since IERC20[]
and uint192[]
are complex data types
No. | Description |
---|---|
7 | Remove references to pragma experimental ABIEncoderV2 |
Remove references to pragma experimental ABIEncoderV2 , which has been depreciated. ABI coder v2 is activated by default by the compiler as of Solidity v0.8.0. |
IAaveIncentivesController.sol: L3
pragma experimental ABIEncoderV2;
Similarly for the following:
No. | Description |
---|---|
8 | pragma solidity version should be upgraded to latest version before finalization |
The solidity version used in the contracts is 0.8.9 or earlier, compared to the latest version of 0.8.17 . | |
There are specific upgrades that have been applied since 0.8.9 . For example, a version of at least 0.8.10 is required to have external calls skip contract existence checks if the external call has a return value, 0.8.12 is necessary for string.concat to be used instead of abi.encodePacked , and 0.8.13 or later is needed to use using for with a list of free functions. In addition, only the latest version receives security fixes. |
The majority of the contracts use pragma solidity 0.8.9
. Below are the contracts with even earlier versions. All need to be upgraded before implementation.
pragma solidity ^0.6.0
pragma solidity >=0.6.0 <0.8.0
pragma solidity 0.6.12
IAaveIncentivesController.sol: L2
No. | Description |
---|---|
9 | Consider removing internal functions that are never used |
The following internal functions are not used by any contract and should be reviewed: |
ERC20PermitUpgradeable.sol: L67
No. | Description |
---|---|
10 | Long single-line comments |
Very long comments (over approximately 120 characters) can interfere with readability. Almost all of the very long comments in contest name wrap. However, below are two instances of extra-long comments whose readability could be improved by wrapping as shown. | |
Note that a small indentation is used in each continuation line (which flags for the reader that the comment is ongoing), along with a period at the close (to signal the end of the comment). |
IAaveIncentivesController.sol: L90-91
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards. The caller must * be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager
Suggestion:
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards — * the caller must be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager.
* @notice Compute the pending in RAY (rounded down). Pending is the amount to add (not yet unclaimed) rewards in RAY (rounded down).
Suggestion:
* @notice Compute the pending in RAY (rounded down). Pending is the amount * to add (not yet unclaimed) rewards in RAY (rounded down).
No. | Description |
---|---|
11 | Typos |
/// Mointain the overall backing policy; handout assets otherwise
Change Mointain
to Maintain
/// lowLow should be nonzero when the asset might be worth selling
Change lowLow
to lotLow
// As such, they're each interepreted as a map from target name -> target weight
Change interepreted
to interpreted
The same typo (permisionlessly
) occurs in both lines below:
* @notice A helper to melt RTokens slowly and permisionlessly.
Change permisionlessly
to permissionlessly
in both cases
// lastPayoutBal' = rToken.balanceOf'(this) (balance now == at end of pay leriod)
Change leriod
to period
// issuances, and so any particular issuance is actually the _difference_ between two adjaacent
Change adjaacent
to adjacent
// The way to keep an IssueQueue striaght in your head is to think of each TotalIssue item as a
Change striaght
to straight
* If this happens, users balances are zereod out and StRSR is re-issued at a 1:1 exchange rate
Change zereod
to zeroed
// r'.queue is r.queue with a new entry appeneded for (totalDrafts' - totalDraft) drafts
Change appeneded
to appended
/// Overriden in StRSRVotes to handle rebases
Change Overriden
to Overridden
The same typo (that it's
) occurs in both lines below:
RecollateralizationLib.sol: L306
RecollateralizationLib.sol: L311
// and sellAmount is the quantity of that token that it's in surplus (in qTok).
Change that it's
to that's
in both cases
/// @dev Indices are shared aross return values
Change aross
to across
// this functions will then panic when `uoaHeld.div(uoaNeeded)`
Change functions
to function
/// VERY IMPORTANT: In any valid implemntation, status() MUST become DISABLED in refresh() if
Change implemntation
to implementation
The same typo (repeated word the
) occurs in both lines below:
* vast majority of cases we expect the the O(n^2) function to be acceptable.
Change the the
to the
in both cases
/// @return backing The worst-case collaterazation % the protocol will have after done trading
Change collaterazation
to collateralization
* @notice A UX-friendly layer for interactin with the protocol
Change interactin
to interacting
/// @param redeemer The address of the account redeeeming RTokens
Change redeeeming
to redeeming
/// Set the fraction of the RToken supply that can be reedemed at once
Change reedemed
to redeemed
/// Emitted whenever RSR are paids out
Change paids
to paid
/// Emitted if all the RSR in the unstakin pool is seized, and all ongoing unstaking is voided.
Change unstakin
to unstaking
* Typically freezing thaws on its own in a predetemined number of blocks.
Change predetemined
to predetermined
* @return The amound of pending rewards in RAY
Change amound
to amount
uint48 delayUntilDefault; // {s} The number of seconds an oracle can mulfunction
Change mulfunction
to malfunction
// In this case, the asset may recover, reachiving _whenDefault == NEVER.
Change reachiving
to reaching
if that was what was intended
// from this trade's acution will all eventually go to origin.
Change acution
to auction
uint96 minBuyAmount = uint96(Math.max(1, req.minBuyAmount)); // Safe downcast; require'd
Change require'd
to required
// transfer all balancess of `buy` and `sell` at this address to `origin`
Change balancess
to balances
No. | Description |
---|---|
12 | Update sensitive terms |
Terms incorporating "black," "white," "slave" or "master" are potentially problematic. Substituting more neutral terminology is becoming common practice |
IAaveIncentivesController.sol: L41
* @dev Returns the whitelisted claimer for a certain address (0x0 if not set)
Suggestion: Change whitelisted
to allowlisted
Similarly for the following instances of whitelist
variants:
IAaveIncentivesController.sol: L34
IAaveIncentivesController.sol: L90-91
#0 - c4-judge
2023-01-25T00:02:55Z
0xean marked the issue as grade-b
🌟 Selected for report: IllIllI
Also found by: 0xA5DF, 0xSmartContract, 0xhacksmithh, AkshaySrivastav, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Budaghyan, Cyfrin, Madalad, NoamYakov, RHaO-sec, Rageur, RaymondFam, ReyAdmirado, Rolezn, SAAJ, SaharDevep, Sathish9098, __141345__, amshirif, arialblack14, c3phas, carlitox477, chaduke, delfin454000, descharre, nadin, oyc_109, pavankv, saneryee, shark
72.4433 USDC - $72.44
Issue | Description | Instances |
---|---|---|
1 | Avoid use of && within a require function | 15 |
2 | Inline a function that is used only once | 22 |
3 | Avoid use of default "checked" behavior in a for loop | 68 |
4 | Avoid storage of uints or ints smaller than 32 bytes, if possible | 16 |
Total | 121 |
No. | Description |
---|---|
1 | Avoid use of && within a require function |
Splitting into separate require() statements saves gas |
require( newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid auctionLength" );
Recommendation:
require(newAuctionLength > 0, "invalid auctionLength"); require(newAuctionLength <= MAX_AUCTION_LENGTH, "invalid auctionLength");
require( address(rsr_) != address(0) && address(gnosis_) != address(0) && address(rsrAsset_) != address(0) && address(implementations_.main) != address(0) && address(implementations_.trade) != address(0) && address(implementations_.components.assetRegistry) != address(0) && address(implementations_.components.backingManager) != address(0) && address(implementations_.components.basketHandler) != address(0) && address(implementations_.components.broker) != address(0) && address(implementations_.components.distributor) != address(0) && address(implementations_.components.furnace) != address(0) && address(implementations_.components.rsrTrader) != address(0) && address(implementations_.components.rTokenTrader) != address(0) && address(implementations_.components.rToken) != address(0) && address(implementations_.components.stRSR) != address(0), "invalid address" );
Recommendation:
require(address(rsr_) != address(0), "invalid address"); require(address(gnosis_) != address(0), "invalid address"); require(address(rsrAsset_) != address(0), "invalid address"); require(address(implementations_.main) != address(0), "invalid address"); require(address(implementations_.trade) != address(0), "invalid address"); require(address(implementations_.components.assetRegistry) != address(0), "invalid address"); require(address(implementations_.components.backingManager) != address(0), "invalid address"); require(address(implementations_.components.basketHandler) != address(0), "invalid address"); require(address(implementations_.components.broker) != address(0), "invalid address"); require(address(implementations_.components.distributor) != address(0), "invalid address"); require(address(implementations_.components.furnace) != address(0), "invalid address"); require(address(implementations_.components.furnace) != address(0), "invalid address"); require(address(implementations_.components.rsrTrader) != address(0), "invalid address"); require(address(implementations_.components.rTokenTrader) != address(0), "invalid address"); require(address(implementations_.components.rToken) != address(0), "invalid address"); require(address(implementations_.components.stRSR) != address(0), "invalid address");
require(owner != address(0) && owner != address(this), "invalid owner");
Recommendation:
require(owner != address(0), "invalid owner"); require(owner != address(this), "invalid owner");
require(period_ > 0 && period_ <= MAX_PERIOD, "invalid period");
Recommendation:
require(period_ > 0, "invalid period"); require(period_ <= MAX_PERIOD, "invalid period");
require(queue.left <= endId && endId <= queue.right, "out of range");
Recommendation:
require(queue.left <= endId, "out of range"); require(endId <= queue.right, "out of range");
Similary for the following require
statements incorporating &&
:
No. | Description |
---|---|
2 | Inline a function that is used only once |
It costs less gas to inline the code of functions that are only called once. Doing so saves the cost of allocating the function selector, and the cost of the jump when the function is called. |
function whenFinished(uint256 amtRToken) private returns (uint192 finished) {
function whenFinished
above is called only once, as shown below:
uint192 vestingEnd = whenFinished(amtRToken); // D18{block number}
The following functions
similarly are called only once and should be inlined:
BackingManager.sol
BasketHandler.sol
RToken.sol
StRSR.sol
RecollateralizationLib.sol
ComponentRegistry.sol
FiatCollateral.sol
No. | Description |
---|---|
3 | Avoid use of default "checked" behavior in a for loop |
Underflow/overflow checks are made every time ++i (or i++ or equivalent counter) is called. Such checks are unnecessary since i is already limited. Therefore, use unchecked {++i} /unchecked {i++} instead, as shown below: |
for (uint256 i = 0; i < length; ++i) { _register(assets_[i]); }
Suggestion:
for (uint256 i; i < length;) { _register(assets_[i]); unchecked { ++i; } }
Similarly, for the following for
loops:
AssetRegistry.sol
BackingManager.sol
BasketHandler.sol
Distributor.sol
RToken.sol
RecollateralizationLib.sol
RewardableLib.sol
FacadeAct.sol
FacadeRead.sol
FacadeWrite.sol
Array.sol
String.sol
No. | Description |
---|---|
4 | Avoid storage of uints or ints smaller than 32 bytes, if possible |
Storage of uints or ints smaller than 32 bytes incurs overhead. Instead, use size of at least 32, then downcast where needed |
function _ensureNonZeroDistribution(uint24 rTokenDist, uint24 rsrDist) internal pure {
function delegateBySig( address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s ) public {
function requireSignature( address owner, bytes32 hash, uint8 v, bytes32 r, bytes32 s ) external view {
struct RevenueTotals { uint24 rTokenTotal; // {revShare} uint24 rsrTotal; // {revShare} }
event DistributionSet(address dest, uint16 rTokenDist, uint16 rsrDist);
function distribution(address) external view returns (uint16 rTokenDist, uint16 rsrDist);
struct SignatureParams { uint8 v; bytes32 r; bytes32 s; }
function deposit( address recipient, uint256 amount, uint16 referralCode, bool fromUnderlying ) external returns (uint256);
function metaDeposit( address depositor, address recipient, uint256 value, uint16 referralCode, bool fromUnderlying, uint256 deadline, SignatureParams calldata sigParams ) external returns (uint256);
function deposit( address recipient, uint256 amount, uint16 referralCode, bool fromUnderlying ) external override nonReentrant returns (uint256) {
function metaDeposit( address depositor, address recipient, uint256 value, uint16 referralCode, bool fromUnderlying, uint256 deadline, SignatureParams calldata sigParams ) external override nonReentrant returns (uint256) {
function _deposit( address depositor, address recipient, uint256 amount, uint16 referralCode, bool fromUnderlying ) internal returns (uint256) {
#0 - c4-judge
2023-01-24T23:36:45Z
0xean marked the issue as grade-b