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: 36/99
Findings: 2
Award: $156.94
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: BowTiedWardens
Also found by: PwnedNoMore, TrungOre, hansfriese, hubble, minhquanym, shung
https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L100 https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L115-L130
In Yieldy contract, while calling _storeRebase() in function rebase(), updatedTotalSupply is passed instead of currentTotalSupply.
Filing this as medium risk , due to two impacts, in the way this parameter is used in _storeRebase() function.
The rebasePercent is under-calculated, and in the LogRebase Event, wrong (lesser than actual) value is emitted.
The rebases[] storage value will have wrong values, with totalStakedBefore & totalStakedAfter having same value.
So actual changes in rebase cant be reconstructed via historical info (via both events & storage).
Modify the line#100 to as below in rebase() function
_storeRebase(currentTotalSupply, _profit, _epoch);
#0 - toshiSat
2022-06-27T21:39:19Z
duplicate #221
🌟 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
84.4957 USDC - $84.50
https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L321 https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L372-L374
Refering to the lines#324 in Staking.sol contract, function _requestWithdrawalFromTokemak().
// the only way balance < _amount is when using unstakeAllFromTokemak uint256 amountToRequest = balance < _amount ? balance : _amount;
The check is being done for unstakeAllFromTokmak as mentioned in the comment. But in unstakeAllFromTokemak() the value of tokePoolBalance that is being passed is same as given below in _requestWithdrawalFromTokemak. uint256 balance = ITokePool(TOKE_POOL).balanceOf(address(this));
Hence the condition of balance < _amount will never hold good. Hence this line is unnecessary.
Modified function as below.
function _requestWithdrawalFromTokemak(uint256 _amount) internal { if (_amount > 0) tokePoolContract.requestWithdrawal(_amount); }
In LiquidityReserve.sol there is a fee paid by user on using instantUnstake() to get their stakingToken instantly. If the admin behaves maliciously, they can set the fee to BASIS_POINTS i.e., 100%, which will result in 0 payout to users using instantUnstake()
A reasonable max value of 5% or 10% can be used to check while setting the fee in setFee() function.
Contract Staking.sol
line#235 function setCoolDownPeriod(uint256 _vestingPeriod) line#217 function setEpochDuration(uint256 duration) line#167 function setAffiliateFee(uint256 _affiliateFee)
LiquidityReserve.sol
line#92 function setFee(uint256 _fee)
These functions that make critical changes should have a timelock, so that users can see the upcoming changes and have time to react to these changes.
Stable and trustworthy protocol.
https://consensys.net/diligence/audits/2020/12/1inch-liquidity-protocol/#unpredictable-behavior-for-users-due-to-admin-front-running-or-general-bad-timing https://github.com/code-423n4/2022-01-sandclock-findings/issues/178
Add timelock for critical changes which will effect user experience and decisions.
Because null check is missing in setEpochDuration(), its possible to set a value of 0 to epoch.duration In the eventuality of this set to 0, every external call like stake, unstake, etc., which calls rebase() will increment the epoch which will be detrimental and break functionality like warmup, cooldown, etc.,
Add null check along with a reasonable min value to be checked while setting the epoch.duration value.
In Staking.sol the Staking contract inherits OwnableUpgradeable, which has the function renounceOwnership() If by mistake or maliciously the renounceOwnership() is called, there is a possibility of losing control on the Staking contract.
Override this renounceOwnership() function in Staking.sol by making it a no-op.
In Staking.sol the Staking contract inherits OwnableUpgradeable, which has the function transferOwnership() If by mistake a wrong address is used in transferOwnership(), then there is a possibility of losing control on the Staking contract.
Best practice is to use two step process to transfer ownership 1st Step : setNewOwner << setting/proposing the address of the new Owner 2nd Step : acceptOwnership << the new Owner will execute this function to complete the ownership transfer