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: 42/73
Findings: 1
Award: $121.59
š 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 | |
---|---|
L-01 | Wrong comment |
L-02 | Add a descriptive string for function likely to revert in some cases |
L-03 | Use require instead of assert |
L-04 | furnace and stRsr can be added twice in destinations |
L-05 | lastPayout description is incorrect |
L-06 | Avoid using draft dependencies |
L-07 | StRSR.stake() breaks a @custom:interaction requirement |
L-08 | Avoid division by 0 |
Issue | |
---|---|
N-01 | Use a more recent version of Solidity |
N-02 | Scientific notation can be used |
N-03 | Typos |
N-04 | Naming conventions |
N-05 | Block time assumption may be false |
N-06 | Native time should be used |
N-07 | Avoid floating pragmas |
N-08 | Avoid shadowing variables |
handoutExcessAssets()
forwards assets to the RSRTrader
, not to the StRSR pool
173: // Forward any RSR held to StRSR pool; RSR should never be sold for RToken yield //@audit L: wrong comment 174: if (rsr.balanceOf(address(this)) > 0) { 175: // For CEI, this is an interaction "within our system" even though RSR is already live 176: IERC20Upgradeable(address(rsr)).safeTransfer( 177: address(rsrTrader), 178: rsr.balanceOf(address(this)) 179: ); 180: }
If the Aave lending pool has a liquidity shortage at the time of withdrawal,full withdrawal of the requested underlying token amount will not be possible. Make sure an appropriate error is used to let the caller know why it reverted
345: assert(amt == amountToWithdraw);
require
instead of assert
As per Solidity's documentation:
The assert function creates an error of type Panic(uint256). The same error is created by the compiler in certain situations as listed below. Assert should only be used to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix
104: assert(low == 0);
furnace
and stRsr
can be added twice in destinations
There is a check in the add()
function of EnumerableSet
to prevent the same value to be added twice.
The issue is that is possible to bypass this check because of the FURNACE
and ST_RSR
values, meaning both can be added twice (for instance,FURNACE
and furnace
, by calling setDistribution()
with dest == furnace
)
170: } else { 171: destinations.add(dest); 172: require(destinations.length() <= MAX_DESTINATIONS_ALLOWED, "Too many destinations"); 173: }
Prevent it by adding an extra check in setDistribution
61: function setDistribution(address dest, RevenueShare memory share) external governance { + if((dest == furnace) || (dest == stRSR)) revert(); 62: _setDistribution(dest, share); 63: RevenueTotals memory revTotals = totals(); 64: _ensureNonZeroDistribution(revTotals.rTokenTotal, revTotals.rsrTotal); 65: }
lastPayout
description is incorrectlastPayout
is said to return the last payout time.
21: uint48 public lastPayout; // {seconds} The last time we did a payout
This is incorrect, as the rounding of numPeriods
in melt()
means most of the time lastPayout
will be less than the block.timestamp
at which the call was made
73: uint48 numPeriods = uint48((block.timestamp) - lastPayout) / period; 74: uint192 payoutRatio = FIX_ONE.minus(FIX_ONE.minus(ratio).powu(numPeriods)); 75: 76: uint256 amount = payoutRatio.mulu_toUint(lastPayoutBal); 77: 78: lastPayout += numPeriods * period;
StRSR
is using draft-EIP712Upgradeable.sol
, an OpenZeppelin contract.
This contract is still a draft and is not considered ready for mainnet use: it may not have received adequate security auditing and may be amended in the future.
8: import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
StRSR.stake()
breaks a @custom:interaction
requirementIt is available during paused/frozen state. This breaks a requirement of @custom:interaction
functions:
1.@custom:interaction - An action. Disallowed while paused. Per-contract reentrancy-safety is needed.
200: /// @dev Staking continues while paused/frozen, without reward handouts
Add this exception to the documentation, or add the modifier if the function should not be callable during paused/frozen state
basketPriceLow
can be 0. Add a check before performing the following computation
173: uint192 basketsHigh = components.bm.safeMulDivCeil(FIX_ONE, assetsHigh, basketPriceLow);
pragma solidity 0.8.9;
is used across the contracts. Consider upgrading to a more recent version: at least 0.8.12
to get string.concat()
to be used instead of abi.encodePacked()
15: uint192 constant MIN_BLOCK_ISSUANCE_LIMIT = 10_000 * FIX_ONE; //@audit 1e4
361: // qty = FIX_MAX iff p = 0 //@audit NC: typo iff -> if
Internal and private functions should have an underscore
68: function empty(Basket storage self) internal {
Avoid using fixed block time variables, as block time can be greater than expected if a slot is left empty https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/libraries/RedemptionBattery.sol#L11
11: uint48 constant BLOCKS_PER_HOUR = 300;
use Native time units instead of a number of seconds
34: uint48 public constant MAX_TRADING_DELAY = 31536000; //@audit 1 years
3: pragma solidity ^0.8.9;
27: ICToken erc20 = ICToken(address(config.erc20)); //@audit erc20 is shadowing
#0 - c4-judge
2023-01-24T23:57:59Z
0xean marked the issue as grade-b