Ajna Protocol - 7siech'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: 56/114

Findings: 1

Award: $171.22

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: bytes032

Also found by: 7siech, Ruhum, ktg

Labels

bug
2 (Med Risk)
satisfactory
sponsor disputed
edited-by-warden
duplicate-285

Awards

171.2239 USDC - $171.22

External Links

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L226 https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L164 https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/token/AjnaToken.sol#L11 https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/GrantFund.sol#L58 https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L105

Vulnerability details

Impact

It is possible to pass an extraordinary funding proposal without the required quorum at the snapshot.

Furthermore, it is possible to submit a proposal requesting zero tokens, exposing a potential DoS attack on extraordinary funding and locking up treasury funds.

Since there are only 9 extraordinary proposals possible during the lifetime of the protocol, this can jeopardize the longevity and long-term functioning of the project and therefore rate this as a medium risk.

Proof of Concept

Passing a proposal depends on _extraordinaryProposalSucceeded returning true. One of the conditions is to have votesReceived be greater or equal than the tokensRequested and the _getSliceOfNonTreasury.

    function _extraordinaryProposalSucceeded(
        uint256 proposalId_,
        uint256 tokensRequested_
    ) internal view returns (bool) {
        uint256 votesReceived          = uint256(_extraordinaryFundingProposals[proposalId_].votesReceived);
        uint256 minThresholdPercentage = _getMinimumThresholdPercentage();

        return
            // succeeded if proposal's votes received doesn't exceed the minimum threshold required
            (votesReceived >= tokensRequested_ + _getSliceOfNonTreasury(minThresholdPercentage))
            &&
            // succeeded if tokens requested are available for claiming from the treasury
            (tokensRequested_ <= _getSliceOfTreasury(Maths.WAD - minThresholdPercentage))
        ;
    }

This is supposed to get a percentage of the non treasury tokens to determine whether the proposal has received enough votes. This calculation relies on the current totalSupply and value of the treasury.

    /**
     * @notice Get the number of ajna tokens equivalent to a given percentage.
     * @param percentage_ The percentage of the Non treasury to retrieve, in WAD.
     * @return The number of tokens, in WAD.
     */
    function _getSliceOfNonTreasury(
        uint256 percentage_
    ) internal view returns (uint256) {
        uint256 totalAjnaSupply = IERC20(ajnaTokenAddress).totalSupply();
        return Maths.wmul(totalAjnaSupply - treasury, percentage_);
    }

Both totalSupply and treasury can be manipulated while a proposal is active to decrease the threshold needed to pass the proposal.

Furthermore in proposeExtraordinary, only an upper bound is checked for totalTokensRequested so it's possible to submit proposals asking for zero tokens.

  // check tokens requested are available for claiming from the treasury
        if (uint256(totalTokensRequested) > _getSliceOfTreasury(Maths.WAD - _getMinimumThresholdPercentage())) revert InvalidProposal();

TotalSupply

The AjnaToken implements OZ's ERC20Burnable and thus exposes the burn function. A sufficiently motivated actor can burn their own tokens or buy up tokens in the open market to reduce the total supply.

Let us assume a total supply of 1,000 tokens with the treasury holding 500 tokens and thus 500 non treasury tokens in circulation. Let's further assume this is the first proposal asking for zero tokens so requiring a quorum of 50% of non treasury tokens or 250 tokens.

At snapshot time, this would require 250 votes. Let's assume an actor holds 200 tokens and has already cast their vote. He can then go ahead and burn 100 tokens to reduce the supply of non treasury tokens from 500 down to 400. He now only requires 200 votes and can thus pass the proposal.

Treasury

The GrantFund contract exposes a fundTreasury function allowing anyone to transfer tokens to the treasury and thus increase the treasury. A sufficiently motivated actor can transfer tokens to the treasury decreasing the number of non-treasury tokens and thus reducing the number of votes required.

Let us again assume a total supply of 1,000 tokens with the treasury holding 500 tokens and thus 500 non treasury tokens in circulation. Let's also again assume this is the first proposal asking for zero tokens so requiring a quorum of 50% of non treasury tokens or 250 tokens.

At snapshot time, this would require 250 votes. Let's assume an actor holds 200 tokens and has already cast their vote. He can then go ahead and transfer 100 tokens to increase the treasury from 500 to 600 and thus the amount of non treasury tokens to 400. He now only requires 200 votes and can thus pass the proposal.

Tools Used

Manual review

Possible mitigation to avoid manipulation of totalSupply, treasury and totalTokensRequested -

  • use OZ's ERC20Votes.getPastTotalSupply function to query the token supply at a specific snapshot time
  • potentially use ERC20Votes.getPastVotes for the GrantFund contract to snapshot the balance of the treasury
  • implement some reasonable lower boundary for totalTokensRequested

Assessed type

Math

#0 - c4-sponsor

2023-05-19T18:58:37Z

MikeHathaway marked the issue as sponsor disputed

#1 - MikeHathaway

2023-05-19T18:59:35Z

It's valid behavior for token holders to decide to use up the extraordinary mechanism with 0 vote proposals. Likewise, there wouldn't be much harm to users from large scale burns or transfers of tokens to the treasury. Both of those cases represent a transfer of wealth from the "attacker" to other token holders.

#2 - c4-judge

2023-05-30T22:33:08Z

Picodes marked the issue as duplicate of #285

#3 - c4-judge

2023-05-30T22:33:12Z

Picodes marked the issue as satisfactory

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