Ajna Protocol - REACH'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: 46/114

Findings: 2

Award: $223.47

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Kenshin

Also found by: 0xRobocop, Dug, REACH, Ruhum, hyh, nobody2018, rbserver

Labels

bug
3 (High Risk)
satisfactory
duplicate-450

Awards

187.2333 USDC - $187.23

External Links

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#L236-L265

Vulnerability details

Impact

The treasury variable has accounting problems when the delegators claim their rewards of previous distribution(s) after a new distribution period has started, leading to a number of issues with the Ajna system.

The treasury is a critical component of the Ajna ecosystem, as it is used to fund grants, pay for development and operational expenses. The accounting problems with the treasury variable results in inaccurate reporting of funds available for grants or other expenses. This could lead to overcommitment of funds or insufficient funding for critical projects. Additionally, if there are discrepancies between the reported treasury balance and the actual balance of funds held in reserve, this could erode trust in the Ajna system and damage its reputation. The incorrect handling of the treasury variable has serious implications for the overall stability and functionality of the protocol.

The accurate management of the treasury balance is of utmost importance in the precise calculation of token distribution within each quarter, adhering to the guidelines stipulated by the Global Budgetary Constraint (GBC) outlined on page 33 of the whitepaper. Furthermore, it serves as a crucial factor in assessing whether a proposal meets the minimum withdrawal threshold, as described on page 35.

Given the vital role the treasury balance plays in key protocol functions, such as funds allocation, proposal thresholds, and extraordinary funding mechanisms, any vulnerability in this area poses a significant concern.

Proof of Concept

Gist link to the PoC test file: https://gist.github.com/0x3agle/cc2e90f498bedbbec632a2f460b5cd20

Note: Save this file to "2023-05-ajna/ajna-grants/test/unit". Run the PoC with "forge test --match-contract TreasuryAccounting -vvv"

The process of a distribution period in Ajna's grant coordination mechanism:

  1. Projects submit proposals for funding.
  2. The community screens and evaluates the proposals and selects 10 projects.
  3. Token holders vote on the remaining proposals using their voting power. Delegates who participate in both stages of the grant coordination mechanism are eligible for delegate rewards based on their pro rata share of total delegate votes in the Funding Stage.
  4. Winning proposals are decided through a one week challenge period.
  5. The Ajna DAO distributes funds from its treasury to fund approved projects according to a fixed schedule called the Project Funding Mechanism (PFM). Delegates who participated in both stages and received delegate rewards will receive them at this stage.
[+] Intended behaviour of the treasury funds

------------------
Distribution ID 0 started
Current Funds: 500000000000000000000000000
Distribution ID 0 ended
------------------
Distribution ID 1 started
Current Funds: 485000000000000000000000000
Distribution ID 1 ended
------------------
Distribution ID 2 started
Current Funds: 470450000000000000000000000

Previous voter[1] balance 431846446912702651
User claims delegate reward
New voter[1] balance 47706036425531786009850

Current Funds: 470402294395420915126692801 //Notice the change 47045 -> 47040 
Distribution ID 2 ended
------------------

But, here's what actually happens:

  • When a delegate doesn't claim their part of reward in current distribution, the reward amount is carried forward into the next distribution's treasury.
  • So, now when the delegates claims their reward of Distribution 1 in Distribution 2, the treasury variable is not being updated.
[+] Actual behaviour of the treasury funds

Distribution ID 1 started
Current Funds: 500000000000000000000000000
Distribution ID 1 ended
------------------
Treasury balance before distribution ID 2 started: 485000000000000000000000000
Distribution ID 2 started
Treasury balance after distribution ID 2 started: 481120000000000000000000000
------------------
Previous voter[1] balance 431846446912702651
User claims delegate reward
New voter[1] balance 47706036425531786009850
------------------
Treasury balance after user claims reward: 481120000000000000000000000 //No change
------------------

Conclusion: This issue persists for every delegate in every distribution, therefore it can be classified as a high severity bug in Ajna Grants due to incorrect accounting in treasury variable.

Tools Used

Manual Review and Foundry

  • Origin of the issue: /ajna-grants/src/grants/base/StandardFunding.sol#claimDelegateReward()
  • Mitigation:
    1. Option 1 - Update the treasury variable when the rewards are claimed by the delegates after the new distribution period has started
    2. Option 2 - Create a seperate accounting for rewards. When the delegates are set to recieve the rewards, the cumulative rewards are subtracted from the treasury and stored into a seperate mapping (address delegate => mapping(uint distributionId => uint reward)) rewards

Assessed type

Math

#0 - c4-judge

2023-05-18T09:57:10Z

Picodes marked the issue as duplicate of #263

#1 - c4-judge

2023-05-30T18:09:17Z

Picodes marked the issue as duplicate of #263

#2 - c4-judge

2023-05-30T18:14:06Z

Picodes marked the issue as satisfactory

[L-01] NFTs may be minted without a liquidity pool position

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

Within PositionManager.sol, the mint function does not check for liquidity pool positions. This could lead to future vulnerabilities if improvements are made without this in mind.

We recommend checking for liquidity prior to minting an NFT.

[L-02] Empty NFTs may be staked

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

Within RewardsManager.sol, an empty NFT can be staked. Rewards cannot be distributed, thus we are marking this as a low. However, similar to L-01, care must be taken here with future upgrades.

Mitigation from L-01 will suffice.

[L-03] Period lengths in block numbers are constant

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#L29-L46

The blocks/minute is variable within Ethereum, and may even change drastically with forks.

As it stands, the average block is mined in 12.11 seconds today, down from 13.56 a year ago.

If you want the block count to remain constant, then you should update the documentation to reflect that. If you want the time(days/seconds/etc.) to remain constant, then you should be able to change the block numbers in period lengths. Adding setter functions and removing the constant modifier would suffice.

E.g., At 15 seconds per block (4 per minute), within StandardFunding.sol, the FUNDING_PERIOD_LENGTH should be 57600 for ~10 days. At 12.11 seconds, it should be 70965 blocks. It is set to 72000.

[L-04] Global constant GLOBAL_BUDGET_CONSTRAINT does not match documentation

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#L27

From the docs pertaining to the Primary Funding Mechanism:

"Each quarter (90 days), up to a 2% of the treasury can be distributed to projects that win a competitive bidding process..."

However in PrimaryFundingMechanism.sol,

/** * @notice Maximum percentage of tokens that can be distributed by the treasury in a quarter. * @dev Stored as a Wad percentage. */ uint256 internal constant GLOBAL_BUDGET_CONSTRAINT = 0.03 * 1e18;

3% is set. We recommend changing this to 2%, or updating the documentation.

#0 - c4-judge

2023-05-18T18:35:11Z

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