Platform: Code4rena
Start Date: 29/03/2022
Pot Size: $50,000 USDC
Total HM: 16
Participants: 42
Period: 5 days
Judge: 0xean
Total Solo HM: 9
Id: 105
League: ETH
Rank: 11/42
Findings: 2
Award: $2,139.21
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: jah
Also found by: securerodd
2075.8711 USDC - $2,075.87
March 31, 2022
@securerodd
The bonus multipliers userCurrentBonusRatio[user]
and userBonusRatioDecrease[user]
are zeroed out in the _kick(address user, address kicker)
and _unlock(address user)
functions (where an empty lock is set). These bonus multipliers are not zeroed out in the emergencyWithdraw(uint256 amount, address receiver)
function even though an empty lock is set there as well. The logic for this is seen below:
// Remove the bonus multiplier userCurrentBonusRatio[user] = 0; userBonusRatioDecrease[user] = 0;
Recommendation:
Remove the redundant lines in _kick(address user, address kicker)
and _unlock(address user)
. Currently, before a user is able to claim rewards or perform any action that hits the pre-transfer hook, the _updateUserRewards(address user)
function is called. This function will only factor in the locking rewards if the current lock's amount is > 0 and its duration is not equal to 0. Thus, zeroing out the bonus multipliers is unnecessary as long as an empty lock is set. This is already done in all 3 of the aforementioned functions.
#0 - Kogaroshi
2022-04-01T08:18:19Z
This logic of reset of userCurrentBonusRatio[user
] and userBonusRatioDecrease[user]
is implemented in case other smart contracts are built on top of hPAL, and might use that system, so we never want to return them a bonusRatio if the Lock was removed.
Update in the PR: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/5
#1 - 0xean
2022-04-09T22:43:15Z
duplicate of #27
#2 - 0xean
2022-05-10T11:12:59Z
closing as dupe
🌟 Selected for report: TerrierLover
Also found by: 0v3rf10w, 0xDjango, 0xNazgul, 0xkatana, 0xmint, Cityscape, Czar102, Dravee, Funen, IllIllI, Tomio, antonttc, defsec, gzeon, hake, kenta, minhquanym, pmerkleplant, rayn, rfa, robee, saian, securerodd, teryanarmen
63.3419 USDC - $63.34
March 31, 2022
@securerodd
This finding applies to the following five functions:
increaseLockDuration(uint256 duration)
increaseLock(uint256 amount)
stakeAndIncreaseLock(uint256 amount, uint256 duration)
_kick(address user, address kicker
_unlock(address user)
Each function implemented almost the exact same logic to check that a lock exists and then to get the current lock. The logic (taken from increaseLockDuration(uint256 duration)
) can be seen below:
require(userLocks[msg.sender].length != 0, "hPAL: No Lock"); // Find the current Lock uint256 currentUserLockIndex = userLocks[msg.sender].length - 1;
Significant gas can be saved by first caching the length of userlocks[msg.sender] into a local variable, then checking that it is not 0, and using the unchecked directive to decrement it by 1. The suggested replacement logic is seen below:
uint256 currentUserLockIndex = userLocks[msg.sender].length; require(currentUserLockIndex != 0, "hPAL: No Lock"); // Find the current Lock unchecked {--currentUserLockIndex;}
Through testing on Remix, this saved 71 gas on increaseLockDuration(uint256 duration)
. This same logic could be ported over to all 5 functions for ~355 gas savings.
#0 - Kogaroshi
2022-04-01T13:43:05Z
QA & gas optimizations changes are done in the PR: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/6 (some changes/tips were implemented, others are noted but won't be applied)