Ajna Protocol - ABAIKUNANBAEV's results

A peer to peer, oracleless, permissionless lending protocol with no governance, accepting both fungible and non fungible tokens as collateral.

General Information

Platform: Code4rena

Start Date: 03/05/2023

Pot Size: $60,500 USDC

Total HM: 25

Participants: 114

Period: 8 days

Judge: Picodes

Total Solo HM: 6

Id: 234

League: ETH

Ajna Protocol

Findings Distribution

Researcher Performance

Rank: 12/114

Findings: 3

Award: $889.58

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

7.7878 USDC - $7.79

Labels

bug
3 (High Risk)
partial-50
upgraded by judge
duplicate-251

External Links

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L811

Vulnerability details

Impact

When calling stake() or unstake() or moveStakedLiquidity(), eventually the functions will call interanl transferAjnaRewards() that will transfer user's rewards. The problem is that the function relies on the balanceOf() of the ajnaToken. And there is if statement that checks if rewardsEarned > ajnaBalance. If so, the rewards will be equal to the ajnaBalance. This logic can be violated and eventually lead to the user getting less rewards.

Proof of Concept

Imagine the following scenario:

  1. ajnaBalance = 1000.
  2. Alice decides to unstake() his tokens and get his rewards (900 tokens, for example)
  3. Bob with 700 reward tokens sees this transaction and decides to front-run. He unstakes his 700 tokens and now the ajnaBalance equals to 300 tokens. And when Alice's tx executes, she'll get rewardsEarned_ = ajnaBalance leaving her with less rewards than she earned.

Tools Used

Manual review.

Add some custom logic to hide the rewardsEarned_ by the user making it more difficult target for attackers. Also the statement: if (rewardsEarned_ > ajnaBalance) rewardsEarned_ = ajnaBalance can be changed to avoid situation where rewards earned bigger than the actual token balance (maybe minBalance can be introduced).

Assessed type

MEV

#0 - c4-judge

2023-05-18T10:44:02Z

Picodes marked the issue as duplicate of #361

#1 - c4-judge

2023-05-29T20:55:44Z

Picodes changed the severity to 3 (High Risk)

#2 - c4-judge

2023-05-29T20:59:30Z

Picodes marked the issue as partial-50

#3 - Picodes

2023-05-29T20:59:55Z

Unclear report and description of the impact. The main issue here isn't really MEV or front running.

Findings Information

🌟 Selected for report: Jorgect

Also found by: ABAIKUNANBAEV, shealtielanz

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-132

Awards

845.5499 USDC - $845.55

External Links

Lines of code

claimRewards(): https://github.com/ajna-finance/ajna-core/blob/main/src/RewardsManager.sol#L122 moveStakedLiquiidty(): https://github.com/ajna-finance/ajna-core/blob/main/src/RewardsManager.sol#L152 unstake(): https://github.com/ajna-finance/ajna-core/blob/main/src/RewardsManager.sol#L280

Vulnerability details

Impact

When we call external claimRewards(), we need to check whether msg.sender == stakeInfo.owner and whether the rewards have been claimed for this epoch and only after that call internal _claimRewards. However, when calling different functions such as moveStakedLiquidity() and unstake() we check only if msg.sender == stakeInfo.owner forgetting about isEpochClaimed mapping before calling internal _claimRewards. This allows the users to claim rewards multiple times in the same rewards by moving liquidity and staking / unstaking tokenId.

Proof of Concept

  1. Alice stakes her tokenId and starts earning the rewards.
  2. She claims her rewards by calling claimRewards().
  3. She then calls moveStakedLiquidity() and claims her rewards again in the same epoch.

Tools Used

Manual review.

The same check as in the claimRewards() for isEpochClaimed should be added to moveStakedLiquidity() and unstake().

Assessed type

Other

#0 - c4-judge

2023-05-18T17:36:11Z

Picodes marked the issue as duplicate of #316

#1 - c4-judge

2023-05-30T22:02:03Z

Picodes changed the severity to 3 (High Risk)

#2 - c4-judge

2023-05-31T14:06:45Z

Picodes marked the issue as satisfactory

Unchecked return value on add() function in PositionManager.sol:

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/PositionManager.sol#186

Although it doesn't seem to break the protocol and cause any unexpected behavior, it's better to handle the logic on the functions that doesn't revert on failure and return false silently. add() function from Enumerable.sol library will return false if the index is already in the set, but it doesn't revert and continue execution even if it's on the list.

#0 - c4-judge

2023-05-18T19:17:20Z

Picodes marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter