Platform: Code4rena
Start Date: 28/09/2023
Pot Size: $36,500 USDC
Total HM: 5
Participants: 115
Period: 6 days
Judge: 0xDjango
Total Solo HM: 1
Id: 290
League: ETH
Rank: 83/115
Findings: 1
Award: $4.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Bauchibred
Also found by: 0x3b, 0xDetermination, 0xMosh, 0xScourgedev, 0xTheC0der, 0xTiwa, 0xWaitress, 0xdice91, 0xfusion, 0xpiken, 0xprinc, 0xweb3boy, ArmedGoose, Aymen0909, Breeje, Brenzee, Daniel526, DavidGiladi, DeFiHackLabs, Flora, Fulum, HChang26, Hama, IceBear, J4X, Krace, KrisApostolov, Maroutis, Mirror, MohammedRizwan, Norah, PwnStars, SPYBOY, TangYuanShen, Testerbot, ThreeSigma, Tricko, al88nsk, alexweb3, ast3ros, berlin-101, bin2chen, blutorque, btk, d3e4, deth, e0d1n, ether_sky, ge6a, gkrastenov, glcanvas, hals, imare, inzinko, jkoppel, jnforja, joaovwfreire, josephdara, kutugu, lotux, lsaudit, mahdirostami, merlin, n1punp, nadin, neumo, nisedo, nobody2018, oakcobalt, orion, peanuts, pep7siup, pina, ptsanev, rokinot, rvierdiiev, said, santipu_, sashik_eth, seerether, squeaky_cactus, terrancrypt, tonisives, twicek, vagrant, xAriextz, y4y
4.3669 USDC - $4.37
After a token burn happens even if the user has enough staked xvs
and has again waited the waiting period will not get a new token back.
The burn
operation will not restart the waiting period as it should.
The user must do a new deposit to the XVSVault
where Prime#xvsUpdated
is automatically called or call the Prime#xvsUpdated
method directly.
If a burn
happens to the user. Even if it has enough xvs
staked and waits a long time (longer then the needed waiting period) it will not get a new Prime
token.
The following POC is written in two parts. One part shows what happens if the user after token burn does call xvsUpdate
the other what happens if it does not.
it("M03 xvsUpdate will restart the waiting period after burn", async () => { const user = user1; // stake and wait some time to get the Prime token await xvs.connect(user).approve(xvsVault.address, bigNumber18.mul(10000)); await xvsVault.connect(user).deposit(xvs.address, 0, bigNumber18.mul(10000)); await mine(90 * 24 * 60 * 60); await prime.connect(user).claim(); let token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(true); // burn the prime token await prime.burn(user1.getAddress()); token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(false); let xvsBalance = await xvsVault.connect(user).getUserInfo (xvs.address, 0, user1.getAddress()); let stake = await prime.stakedAt(user.getAddress()); // !! without the following line the call to claim() will revert even if we have enough xvs staken !! // we need to call this line for waiting period time to start counting // this should happen automatically if user has enough xvs token staked after burn await prime.xvsUpdated(user.getAddress()); console.log("Amount staked:", xvsBalance.amount.toString(), "staked:",stake.toString()); await mine(91 * 24 * 60 * 60); await prime.connect(user1).claim(); token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(true); }); it("M03 without xvsUpdate will get a revert on claim even if we wait a long long time", async () => { const user = user1; // stake and wait some time to get the Prime token await xvs.connect(user).approve(xvsVault.address, bigNumber18.mul(10000)); await xvsVault.connect(user).deposit(xvs.address, 0, bigNumber18.mul(10000)); await mine(90 * 24 * 60 * 60); await prime.connect(user).claim(); let token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(true); // burn the prime token await prime.burn(user1.getAddress()); token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(false); let xvsBalance = await xvsVault.connect(user).getUserInfo (xvs.address, 0, user1.getAddress()); let stake = await prime.stakedAt(user.getAddress()); console.log("Amount staked:", xvsBalance.amount.toString(), "staked:",stake.toString()); // wait longer then needed await mine((100 + 91) * 24 * 60 * 60); await expect(prime.connect(user).claim()).to.be.revertedWithCustomError(prime, "IneligibleToClaim"); token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(false); });
run it with npx hardhat test tests/hardhat/Prime/Prime.ts --grep "M03"
and will get this output :
PrimeScenario Token mint and burn Amount staked: 10000000000000000000000 staked: 0 ✔ M03 xvsUpdate will restart the waiting period after burn (567ms) Amount staked: 10000000000000000000000 staked: 0 ✔ M03 without xvsUpdate will get a revert on claim even if we wait a long long time (395ms) 2 passing (8s)
Manual review
When burning the token with burn
restart the waiting period for the user by invoking xvsUpdate
and it will take care of all needed accounting.
function _burn(address user) internal { if (!tokens[user].exists) revert UserHasNoPrimeToken(); address[] storage _allMarkets = allMarkets; for (uint256 i = 0; i < _allMarkets.length; ) { _executeBoost(user, _allMarkets[i]); markets[_allMarkets[i]].sumOfMembersScore = markets[_allMarkets[i]].sumOfMembersScore - interests[_allMarkets[i]][user].score; interests[_allMarkets[i]][user].score = 0; interests[_allMarkets[i]][user].rewardIndex = 0; unchecked { i++; } } if (tokens[user].isIrrevocable) { totalIrrevocable--; } else { totalRevocable--; } tokens[user].exists = false; tokens[user].isIrrevocable = false; _updateRoundAfterTokenBurned(user); + xvsUpdated(user); + emit Burn(user); }
Invalid Validation
#0 - 0xRobocop
2023-10-06T22:01:26Z
Consider QA
#1 - c4-pre-sort
2023-10-06T22:01:30Z
0xRobocop marked the issue as low quality report
#2 - c4-judge
2023-11-01T20:22:19Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#3 - fatherGoose1
2023-11-01T20:22:32Z
QA. Helpful design feedback
#4 - c4-judge
2023-11-03T02:44:10Z
fatherGoose1 marked the issue as grade-b