Users who increase their holdings after profit distributions are made will receive a larger share of future profits, despite not contributing proportionally to past distributions.
Let's assume userA and userB gets 100 tokens with tokenId=0
through the mint() when joining a DAO:
Assuming totalsupply is 0, Let's now go step by step:
userA
mints 100 tokens with tokenId = 0
(weight = 64).
Increase in totalSupply
: 100 × 64 = 6400`
userB
also does the same thing, which will make
totalSupply equal to 12800
sendProfit(50)
is called, distributing 50 tokens as profit.
totalProfit = 50 × 10^30 / 12800 = 0.00390625 × 10^30
. - so
totalProfitis now
0.00390625 × 10^30`.
userA
claims their profit by calling claimProfit() method, which makes a chain of calls. Let's focus on the last function getUnsaved():
lastProfit[userA]
is initially 0 (first claim).
The unsaved profit for userA
is:unsaved = (0.00390625 × 10^30 - 0) × (100 × 64) / 10^30 = 25 tokens
userA
receives 25 tokens.
lastProfit[userA]
is updated to totalProfit
(in saveProfit() method), which is 0.00390625 × 10^30
.
userA
mints another 100 tokens with tokenId = 0
(weight = 64).
Increase in totalSupply
:12800 + 100 × 64 = 19200
userA
now holds a total of 200 tokens, while userB
still holds 100 tokens. totalSupply
is now 19,200.
The platform calls sendProfit(50)
again to distribute another 50 tokens.Here we are assuming no one joined the DAO for simplicity
The new profit per token is:
profitPerToken = 50 × 10^30 / 19200 = 0.002604167 × 10^30
totalProfit
is updated:
new totalProfit = 0.00390625 × 10^30 + 0.002604167 × 10^30 = 0.006510417 × 10^30
userA
claims profit based on the difference between totalProfit
and lastProfit[userA]
.
The unsaved profit for userA
is:
unsaved = (0.006510417 × 10^30 - 0.00390625 × 10^30) × (200 × 64) / 10^30 = 33.33 tokens
userA
receives 33.33 tokens in this second claim.
As it can be seen from the steps above, each user’s profit entitlement is based on the difference between totalProfit
and lastProfit[user]
.When a user claims their profit, lastProfit[user]
is set to the current totalProfit
value, recording a "snapshot" of the profit they’ve received up to that point. However this is not sound mechanism, because the system doesn’t take into account the user's token balance changes between claims . If a user mints additional tokens after some profit has been distributed, the lastProfit mechanism doesn’t adjust to reflect the increased token balance.
This means that when the user next claims profit, they will calculate their share based on their new, larger balance—even though they did not hold this larger balance at the time of the previous distributions.
Users who increase their holdings after distributions end up receiving a larger share of future profits without contributing proportionally to past distributions.
Users who are aware of this can game the system.
The share of profits for Long-term holders can be diluted over time by users who “game” the system through minting.
Manual inspection, VsCode
I do not have an easy solution.However tracking each user's balance changes separately and calculating profit shares based on the exact balance at each distribution point should fix the issue.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.