Platform: Code4rena
Start Date: 14/09/2022
Pot Size: $50,000 USDC
Total HM: 25
Participants: 110
Period: 5 days
Judge: hickuphh3
Total Solo HM: 9
Id: 162
League: ETH
Rank: 23/110
Findings: 5
Award: $470.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: cccz
Also found by: Respx, Saintcode_, csanuragjain, fatherOfBlocks, pashov, unforgiven
90.0028 USDC - $90.00
The recoverERC20()
method can’t be used to rug the staked tokens because it has the check
require( tokenAddress != address(stakingToken), "Cannot withdraw the staking token" );
but it is missing the same check for the reward tokens.
Users can lose all of their accrued rewards to the malicious/compromised owner
Change the require statement to
require( tokenAddress != address(stakingToken) && tokenAddress != address(rewardsToken), "Cannot withdraw the staking or rewards token" );
#0 - MiguelBits
2022-09-30T00:31:49Z
working as intended not changing Synthetix well tested code, as we forked it
#1 - HickupHH3
2022-10-29T13:53:03Z
dup #49
🌟 Selected for report: cccz
Also found by: csanuragjain, pashov
320.0832 USDC - $320.08
The method only checks if the reward tokens balance is enough to distribute the newly added rewards for their duration
uint256 balance = rewardsToken.balanceOf(address(this)); require( rewardRate <= balance.div(rewardsDuration), "Provided reward too high" );
But since part of this reward tokens balance was from unclaimed rewards, this means the current check is not sufficient to prove there are enough rewards for the new rewards duration. This means that if the admin calls notifyRewardAmount()
with a bigger reward amount that it has loaded in, it is possible that there won’t be enough rewards for all users to claim, because the calculations were made based on a balance that is partly from already accrued but unclaimed rewards.
Example:
Given:
1,000
stakingToken;rewardsDistribution
sends 100
rewardsToken to the contract;rewardsDistribution
calls notifyRewardAmount()
with amount
= 100
;earned()
and it returns 100
rewardsToken, but Alice choose not to getReward()
for now;rewardsDistribution
calls notifyRewardAmount()
with amount
= 100
without send any fund to contract, the tx will success;earned()
200
rewardsToken, when Alice tries to call getReward()
, the transaction will fail due to insufficient balance of rewardsToken.Expected Results:
The tx in step 5 should revert.
This vulnerability can result in inability for a staker to claim his rewards because there is not enough reward token balance in the contract.
A possible solution is changing the notifyRewardAmount()
function to an addReward()
function that does the token transfer of reward tokens itself. Here is an example implementation:
#0 - MiguelBits
2022-09-30T00:30:16Z
not changing this code as it was forked from synthetix
#1 - HickupHH3
2022-10-29T14:44:59Z
dup of #50. not selected for report because recommendation is incomplete.
8.0071 USDC - $8.01
https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/Controller.sol#L297 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L63 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L96 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L119
The code in Controller.sol
and PegOracle.sol
makes use of Chainlink’s latestRoundData()
method to get prices of assets. The problem is that the checks implemented are insufficient to ensure the price returned from the oracle is not stale. Also for the latestRoundData()
function in the PegOracle.sol
smart contract, the call to priceFeed1.latestRoundData()
is not validated at all.
If for some reason Chainlink data feed stops updating (it has happened before when Chainlink paused its $LUNA price feed for example) then the protocol will start working with a stale price.
Change the latestRoundData
logic to the following (you can use custom errors instead require statements as well):
(roundId, rawPrice,, updatedAt, answeredInRound) = feedVariable.latestRoundData() require(rawPrice > 0, "Chainlink price <= 0"); require(answeredInRound >= roundId, "Stale price"); require(block.timestamp - updatedAt < MAX_STALENESS_THRESHOLD_SECOND, "Stale price"); // You should decide the value of MAX_STALENESS_THRESHOLD_SECOND, for most cases 60 minutes is good enough
The code is currently missing the last check I suggested and for PegOracle.sol
's latestRoundData()
method it is missing all of the validations for priceFeed1.latestRoundData()
.
Change the code exactly the same way for all latestRoundData
calls.
#0 - MiguelBits
2022-10-03T20:58:18Z
🌟 Selected for report: Respx
Also found by: 0x1f8b, 0xDecorativePineapple, 0xNazgul, 0xPanas, 0xSmartContract, 0xc0ffEE, 0xmuxyz, Aymen0909, Bahurum, Bnke0x0, CodingNameKiki, Deivitto, Jeiwan, Lambda, Picodes, PwnPatrol, R2, RaymondFam, Rolezn, Ruhum, Saintcode_, SooYa, Tointer, V_B, ajtra, ak1, async, auditor0517, brgltd, c3phas, carrotsmuggler, cccz, csanuragjain, datapunk, djxploit, durianSausage, eierina, erictee, gogo, imare, joestakey, jonatascm, kv, ladboy233, leosathya, lukris02, oyc_109, pashov, pauliax, rbserver, robee, rokinot, rvierdiiev, scaraven, simon135, unforgiven, wagmi, zzzitron
36.6223 USDC - $36.62
Wrong NatSpec comment for setClaimTVL()
in Vault.sol
The NatSpec is saying “Function to be called after endEpoch” but it is called when Controller gets a triggerDepeg()
call as well.
🌟 Selected for report: pfapostol
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xc0ffEE, 0xkatana, Aymen0909, Bnke0x0, Deivitto, Diana, JAGADESH, KIntern_NA, Lambda, MiloTruck, R2, RaymondFam, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, Saintcode_, Samatak, Sm4rty, SnowMan, Tomio, Tomo, WilliamAmbrozic, _Adam, __141345__, ajtra, ak1, async, c3phas, ch0bu, cryptostellar5, d3e4, delfin454000, dharma09, djxploit, durianSausage, eierina, erictee, fatherOfBlocks, gianganhnguyen, gogo, ignacio, imare, jag, jonatascm, leosathya, lukris02, malinariy, oyc_109, pashov, pauliax, peanuts, peiw, prasantgupta52, robee, rokinot, rotcivegaf, rvierdiiev, seyni, simon135, slowmoses, sryysryy, tnevler, zishansami
16.1756 USDC - $16.18
No need to use OpenZeppelin’s SafeMath
library if using Solidity >0.8
StakingRewards.sol
uses OpenZeppelin’s SafeMath
library but it doesn’t need it as after solc 0.8 there is compiler built-in over/underflow checks.