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: 38/115
Findings: 2
Award: $129.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: deth
Also found by: 0xDetermination, 0xpiken, 3agle, Brenzee, Flora, HChang26, KrisApostolov, Satyam_Sharma, Testerbot, aycozynfada, berlin-101, gkrastenov, mahdirostami, merlin, rokinot, rvierdiiev, said, santipu_, sl1, tapir, twicek
124.9633 USDC - $124.96
Here's the step-by-step description of this issue:
XVSVault.deposit
) 10,000 XVS tokens for a certain period (e.g., 90 days).Prime.issue
) an irrevocable prime token to the user.XVSVault.requestWithdrawal
) 9,500 XVS tokens.Prime.burn
) the irrevocable prime token from the user.Prime.claim
) a revocable prime token , without waiting for the full 90 days.Include and execute a test in the tests/hardhat/Prime/Prime.ts
file:
it("mint recovable prime token to user faster than 90 days", async () => { const user = user1; await xvs.connect(user).approve(xvsVault.address, bigNumber18.mul(10000)); await xvsVault.connect(user).deposit(xvs.address, 0, bigNumber18.mul(10000)); await mine(89 * 24 * 60 * 60); //pass 89 days await expect(prime.connect(user).claim()).to.be.revertedWithCustomError(prime, "WaitMoreTime"); await prime.issue(true, [user1.getAddress()]); expect(await prime.totalIrrevocable()).to.be.equal(1); let token = await prime.tokens(user.getAddress()); expect(token.exists).to.be.equal(true); expect(token.isIrrevocable).to.be.equal(true); // user withdraw almost all XVS tokens await xvsVault.connect(user).requestWithdrawal(xvs.address, 0, bigNumber18.mul(9500)); // ACM burn irrecovable prime token for user await prime.burn(user.getAddress()); token = await prime.tokens(user.getAddress()); expect(token.exists).to.be.equal(false); expect(token.isIrrevocable).to.be.equal(false); // user claim recovable prime token after 2 days instead of 90 await mine(2 * 24 * 60 * 60); //pass 2 days await prime.connect(user).claim(); token = await prime.tokens(user.getAddress()); expect(token.exists).to.be.equal(true); expect(token.isIrrevocable).to.be.equal(false); });
After ACM burns the irrevocable prime token from the user, the user can instantly claim revocable token, regardless of having less than 1,000 XVS tokens and without the requirement of staking for the full 90 days once more.
This issue arises only when irrevocable prime tokens are directly given to the user. When ACM upgrades revocable to irrevocable prime tokens, the user has already claimed the revocable prime token, and their stakedAt
value was set to 0.
Manual review
Consider including the following line within the issue
function:
function issue(bool isIrrevocable, address[] calldata users) external { _checkAccessAllowed("issue(bool,address[])"); if (isIrrevocable) { for (uint256 i = 0; i < users.length; ) { Token storage userToken = tokens[users[i]]; // if (userToken.exists && !userToken.isIrrevocable) { _upgrade(users[i]); } else { _mint(true, users[i]); _initializeMarkets(users[i]); + delete stakedAt[users[i]]; } unchecked { i++; } } } else { // code }
Invalid Validation
#0 - c4-pre-sort
2023-10-04T20:45:40Z
0xRobocop marked the issue as duplicate of #633
#1 - c4-pre-sort
2023-10-04T20:46:02Z
0xRobocop marked the issue as sufficient quality report
#2 - c4-judge
2023-11-02T02:21:19Z
fatherGoose1 marked the issue as satisfactory
#3 - c4-judge
2023-11-05T00:50:34Z
fatherGoose1 changed the severity to 3 (High Risk)
🌟 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
As stated in the Arbitrum Docs, the block.number function returns the most recently synchronized L1 block number. This synchronization between the block number in the Sequencer and the actual L1 block number occurs once every minute.
L2 block.number: updated to sync with L1 block.number ~ every minute (source): L1 block.number 1000 1001 1002 1003 1004 1005 L2 block.number 1000 1000 1000 1000 1004 1004
The PrimeLiquidityProvider.accrueTokens
function will utilize an L1 block.number for the Arbitrum chain.
The calculation of tokenAmountAccrued will be done using the Ethereum L1 block.number, not the L2 Arbitrum block.number. Additionally, the actual L1 block number occurs once every minute.
Manual review
Consider utilizing the L2 block.number for computing tokenAmountAccrued:
+uint256 constant public ARBITRUM_CHAIN_ID = 42161; ArbSys constant public arbSys = +ArbSys(address(100)); function getBlockNumber() public view virtual returns (uint256) { +if(block.chainid == ARBITRUM_CHAIN_ID) +{ + return arbSys.arbBlockNumber(); +} return block.number; }
Timing
#0 - c4-pre-sort
2023-10-05T02:01:22Z
0xRobocop marked the issue as duplicate of #132
#1 - c4-judge
2023-10-31T19:34:39Z
fatherGoose1 changed the severity to QA (Quality Assurance)