Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $50,000 USDC
Total HM: 31
Participants: 99
Period: 5 days
Judges: moose-code, JasoonS, denhampreen
Total Solo HM: 17
Id: 139
League: ETH
Rank: 23/99
Findings: 4
Award: $448.23
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: WatchPug
Also found by: BowTiedWardens, cccz, minhquanym, parashar, pashov, shung, zzzitron
Proof of Concept: the stake
function can be called for a different _recipient
than msg.sender
. Each time you stake tokens your warmUpInfo.expiry
or the time when you are able to claim rewards grows by warmUpPeriod
as is visible the last line here (not the line with the closing braces):
warmUpInfo[_recipient] = Claim({ amount: info.amount + _amount, credits: info.credits + IYieldy(YIELDY_TOKEN).creditsForTokenBalance(_amount), expiry: epoch.number + warmUpPeriod });
This means that a bad actor can always call the stake
function with an amount 1 wei of the staking token for a _recipient
that the bad actor wants to attack via DoS. he can basically stake a few minutes before the expiry
period ends which will again add warmUpPeriod
seconds that the staker has to wait until rewards can be claimed. This can be done for a very long time just by paying gas and staking 1 wei of staking token.
Impact: a perfectly good user of the protocol might be hit with a DoS attack and claiming rewards from the contract won’t be available for him
Recommended Mitigation Steps: replace _recipient
parameter with a msg.sender
usage in the code. This way only a given staker can stake tokens for himself, so no one else can push his expiry
time further away in time
#0 - toshiSat
2022-06-27T21:48:58Z
duplicate #245
Proof of Concept: transferFrom
method does not check if _to
argument is the zero address
Impact: this can lead to token burns without calling the burn
function, which has access control onlyRole(MINTER_BURNER_ROLE)
but here this can be bypassed by passing the zero address as the value of _to
Recommended Mitigation Steps: add a non-zero address check for _to
argument in transferFrom
#0 - toshiSat
2022-06-27T21:51:12Z
duplicate #206
🌟 Selected for report: IllIllI
Also found by: 0x1337, 0x1f8b, 0x29A, 0x52, 0xDjango, 0xNazgul, 0xNineDec, 0xc0ffEE, 0xf15ers, 0xmint, Bnke0x0, BowTiedWardens, Chom, ElKu, FudgyDRS, Funen, GalloDaSballo, GimelSec, JC, Kaiziron, Lambda, Limbooo, Metatron, MiloTruck, Noah3o6, Picodes, PumpkingWok, PwnedNoMore, Sm4rty, StErMi, TomJ, TrungOre, UnusualTurtle, Waze, _Adam, aga7hokakological, ak1, antonttc, berndartmueller, cccz, cryptphi, csanuragjain, defsec, delfin454000, dipp, elprofesor, exd0tpy, fatherOfBlocks, hake, hansfriese, hubble, joestakey, kenta, ladboy233, mics, oyc_109, pashov, pedr02b2, reassor, robee, samruna, scaraven, shung, sikorico, simon135, sseefried, tchkvsky, unforgiven, zzzitron
60.7879 USDC - $60.79
https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L674 https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L485
Proof of Concept: the unstake
and claimWithdraw
methods in Staking.sol
are not following the checks-effects-interactions pattern which can lead to reentrancy bugs. The contract does external calls and changes state after the calls.
Impact: if a reentrancy attack on unstaking/withdrawing is successfully executed this can drain the funds out of the smart contract
Recommended Mitigation Steps: always follow the checks-effects-interactions pattern and always change state before external calls. Another option is to add the OpenZeppelin’s ‘nonReentrant’ modifier to the vulnerable functions
#0 - 0xean
2022-06-27T22:11:53Z
Fair call out on the pattern, but the warden fails to show how this leads to an impact on user funds. The external calls are all to trusted contracts or tokens.
the modifier may be beneficial here as pre-caution still.
#1 - JasoonS
2022-07-26T14:20:59Z
I think I agree with this (where I didn't agree with the erc777 token examples) since it is true the checks-effects-interactions
isn't followed.
🌟 Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xmint, 8olidity, ACai, Bnke0x0, Chom, ElKu, Fabble, Fitraldys, FudgyDRS, Funen, GalloDaSballo, GimelSec, IllIllI, JC, Kaiziron, Lambda, Limbooo, MiloTruck, Noah3o6, Nyamcil, Picodes, PwnedNoMore, Randyyy, RedOneN, Sm4rty, StErMi, TomJ, Tomio, TrungOre, UnusualTurtle, Waze, _Adam, aga7hokakological, ajtra, antonttc, asutorufos, bardamu, c3phas, defsec, delfin454000, exd0tpy, fatherOfBlocks, hansfriese, ignacio, joestakey, kenta, ladboy233, m_Rassska, mics, minhquanym, oyc_109, pashov, reassor, robee, s3cunda, sach1r0, saian, sashik_eth, scaraven, sikorico, simon135, slywaters
26.7051 USDC - $26.71
Explanation: Solidity 0.8.x adds Custom Errors which are a lot more gas efficient than require statements with strings. Using them also helps with other smart contracts integrating with the codebase
Recommendation: replace all require statements with Custom Errors
x = x + y
is more gas efficient than x+=y
, same for x = x - y
and x-=y
Scope: Staking#310, Staking#309, Staking#694, Staking#494, Staking#538
Explanation: the code amountLeft -= warmUpBalance;
has worse gas efficiency than the code amountLeft = amountLeft - warmUpBalance;
and it is the same for the addition.
Recommendation: expand the expressions in scope as in the given example
++var;
is more gas efficient than var++;
Explanation: The code does epoch.number++;
while ++epoch.number;
would save gas
Recommendation: Change the code to ++epoch.number;
COW_SETTLEMENT
and COW_RELAYER
can be constantsExplanation: The COW_SETTLEMENT
and COW_RELAYER
variables are always set to the same addresses in the initialize
method so they can be turned into constants for gas savings
Recommendation: Change COW_SETTLEMENT
and COW_RELAYER
to be constants in StakingStorage.sol
and hardcode those addresses there
Epoch
structExplanation: The Epoch
struct uses only uint256
as the type for it’s members but most of them are either time-based or counters. Changing duration
, number
, timestamp
and endTime
to type uint128
will save a lot of gas when reading an Epoch
object.
Recommendation: Use variable packing by changing duration
, number
, timestamp
and endTime
to type uint128