Open code423n4 opened 1 year ago
hansfriese marked the issue as unsatisfactory: Invalid
hansfriese marked the issue as satisfactory
hansfriese marked the issue as primary issue
I don't think this is valid.
bonusPoints
are added to bothcitizenStatus.points
andpool.totalPoints
. So they must be expressed in base points.bonusPoints
= 100 = "one point", these numbers are all denominated in basis points.
bonusPoints = amount * 100 / (200 * 1e18)
-> amount
= 200 BYTES = 200 * 1e18
-> bonusPoints
= 100 = one logical point. Unless I got very unlucky with the selection of values for my test cases, the math in those behaves the way I want it to...
TimTinkers marked the issue as sponsor disputed
When we check other functions like _stakeLP()
, points are calculated like this. uint256 points = amount * 100 / 1e18 * timelockMultiplier / _DIVISOR;
It means the protocol considers 100 points as one logical point originally.
Downgrade to QA due to inaccurate documentation.
hansfriese changed the severity to QA (Quality Assurance)
hansfriese marked the issue as grade-b
TimTinkers marked the issue as sponsor confirmed
Lines of code
https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L203 https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L10617-L1080 https://github.com/code-423n4/2023-03-neotokyo/blob/main/contracts/staking/NeoTokyoStaker.sol#L1085-L1101
Vulnerability details
Impact
A magnitude of 100 times will completely break the balance on the rewards system calculation, as staking S1 Citizens, S2 Citizens and LP tokens will accrue 100 times less in comparison, and ultimately those users will only be able to claim a fraction of the rewards compared to the ones staking BYTES.
This will break the rewards system, and greatly discourage users from staking LP tokens, or from staking their Citizens without staking BYTES.
Proof of Concept
The
_stakeBytes()
function for staking BYTES with S1 and S2 Citizens has an error when calculating thebonusPoints
.As per the documentation: "Staking participants may also stake BYTES 2.0 tokens into their S1 or S2 Citizens in order to boost the points weight of those Citizens at a rate of 200 BYTES per point."
This means
200 BYTES per point
.The code defines it as:
bonusPoints = (amount * 100 / _BYTES_PER_POINT)
Which translates to:
bonusPoints = amount * 100 / (200 * 1e18)
This results in the miscalculation that it only needs:
2 BYTES per point
, which is 100 times lower than expected.This may be due to some confusion between "base points" and "basis points". 1 base point = 100 basis points.
bonusPoints
are added to bothcitizenStatus.points
andpool.totalPoints
. So they must be expressed in base points.Examples:
References:
The definition of
_BYTES_PER_POINT
:Link to Code
bonusPoints
are miscalculated in_stakeBytes()
and added to the user and the S1 and S2 pool:Link to Code
Link to Code
Tools Used
Manual Review
Recommended Mitigation Steps
Fix the calculation by removing the
* 100
:Check that the calculation for LP tokens is done correctly:
Link to code
Link to code
Fix the corresponding tests, and replace with exact calculations, as the use broad ranges make it difficult to spot these issues: