Skip to content

Instantly share code, notes, and snippets.

@CloudEllie
Created May 4, 2023 09:39
Show Gist options
  • Save CloudEllie/a4655b833548ed9a86a63eb7292bcc0f to your computer and use it in GitHub Desktop.
Save CloudEllie/a4655b833548ed9a86a63eb7292bcc0f to your computer and use it in GitHub Desktop.

Automated findings report: Ajna Finance | Code4rena audit

Report generated by IllIllI-bot (bot crew: IllIllI)

Summary

Medium Risk Issues

Issue Instances
[M‑01] Use of transferFrom() rather than safeTransferFrom() for NFTs in will lead to the loss of NFTs 1
[M‑02] _safeMint() should be used rather than _mint() wherever possible 1

Total: 2 instances over 2 issues

Low Risk Issues

Issue Instances
[L‑01] Loss of precision 1
[L‑02] Array lengths not checked 5

Total: 6 instances over 2 issues

Non-critical Issues

Issue Instances
[N‑01] The nonReentrant modifier should occur before all other modifiers 1
[N‑02] require()/revert() statements should have descriptive reason strings 1
[N‑03] External calls in an un-bounded for-loop may result in a DOS 1
[N‑04] constants should be defined rather than using magic numbers 15
[N‑05] Numeric values having to do with time should use time units for readability 3
[N‑06] Events that mark critical parameter changes should contain both the old and the new value 1
[N‑07] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant 2
[N‑08] Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18) 5
[N‑09] Use @inheritdoc rather than using a non-standard annotation 1
[N‑10] Inconsistent spacing in comments 4
[N‑11] Lines are too long 70
[N‑12] Typos 16
[N‑13] NatSpec @param is missing 5
[N‑14] NatSpec @return argument is missing 1
[N‑15] Event is not properly indexed 1
[N‑16] Consider using delete rather than assigning zero to clear values 1
[N‑17] Contracts should have full test coverage 1
[N‑18] Large or complicated code bases should implement invariant tests 1
[N‑19] Function ordering does not follow the Solidity style guide 7
[N‑20] Contract does not follow the Solidity style guide's suggested layout ordering 1

Total: 138 instances over 20 issues

Gas Optimizations

Issue Instances Total Gas Saved
[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 2 -
[G‑02] Using storage instead of memory for structs/arrays saves gas 4 16800
[G‑03] State variables should be cached in stack variables rather than re-reading them from storage 1 97
[G‑04] Multiple accesses of a mapping/array should use a local variable cache 24 1008
[G‑05] internal functions only called once can be inlined to save gas 7 140
[G‑06] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement 1 85
[G‑07] <array>.length should not be looked up in every loop of a for-loop 8 24
[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops 1 60
[G‑09] Optimize names to save gas 3 66
[G‑10] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 4 -
[G‑11] Using private rather than public for constants, saves gas 1 -
[G‑12] Division by two should use bit shifting 4 80
[G‑13] Constructors can be marked payable 2 42

Total: 62 instances over 13 issues with 18402 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Medium Risk Issues

[M‑01] Use of transferFrom() rather than safeTransferFrom() for NFTs in will lead to the loss of NFTs

The EIP-721 standard says the following about transferFrom():

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

https://github.com/ethereum/EIPs/blob/78e2c297611f5e92b6a5112819ab71f74041ff25/EIPS/eip-721.md?plain=1#L103-L113 Code must use the safeTransferFrom() flavor if it hasn't otherwise verified that the receiving address can handle it

There is one instance of this issue:

File: ajna-core/src/RewardsManager.sol

302:          IERC721(address(positionManager)).transferFrom(address(this), msg.sender, tokenId_);

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L302

[M‑02] _safeMint() should be used rather than _mint() wherever possible

_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function

There is one instance of this issue:

File: ajna-core/src/PositionManager.sol

238:          _mint(params_.recipient, tokenId_);

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L238

Low Risk Issues

[L‑01] Loss of precision

Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator

There is one instance of this issue:

File: ajna-grants/src/grants/libraries/Maths.sol

38:           return (x * 10**18 + y / 2) / y;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L38

[L‑02] Array lengths not checked

If the length of the arrays are not required to be of the same length, user operations may not be fully executed due to a mismatch in the number of items iterated over, versus the number of items provided in the second array

There are 5 instances of this issue:

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

56        function executeExtraordinary(
57            address[] memory targets_,
58            uint256[] memory values_,
59            bytes[] memory calldatas_,
60            bytes32 descriptionHash_
61:       ) external nonReentrant override returns (uint256 proposalId_) {

85        function proposeExtraordinary(
86            uint256 endBlock_,
87            address[] memory targets_,
88            uint256[] memory values_,
89            bytes[] memory calldatas_,
90:           string memory description_) external override returns (uint256 proposalId_) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L56-L61

File: ajna-grants/src/grants/base/StandardFunding.sol

343       function executeStandard(
344           address[] memory targets_,
345           uint256[] memory values_,
346           bytes[] memory calldatas_,
347           bytes32 descriptionHash_
348:      ) external nonReentrant override returns (uint256 proposalId_) {

366       function proposeStandard(
367           address[] memory targets_,
368           uint256[] memory values_,
369           bytes[] memory calldatas_,
370           string memory description_
371:      ) external override returns (uint256 proposalId_) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/StandardFunding.sol#L343-L348

File: ajna-grants/src/grants/GrantFund.sol

22        function hashProposal(
23            address[] memory targets_,
24            uint256[] memory values_,
25            bytes[] memory calldatas_,
26            bytes32 descriptionHash_
27:       ) external pure override returns (uint256 proposalId_) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/GrantFund.sol#L22-L27

Non-critical Issues

[N‑01] The nonReentrant modifier should occur before all other modifiers

This is a best-practice to protect against reentrancy in other modifiers

There is one instance of this issue:

File: ajna-core/src/PositionManager.sol

264:      ) external override mayInteract(params_.pool, params_.tokenId) nonReentrant {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L264

[N‑02] require()/revert() statements should have descriptive reason strings

There is one instance of this issue:

File: ajna-core/src/PositionManager.sol

520:          require(_exists(tokenId_));

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L520

[N‑03] External calls in an un-bounded for-loop may result in a DOS

Consider limiting the number of iterations in for-loops that make external calls

There is one instance of this issue:

File: ajna-core/src/RewardsManager.sol

/// @audit bucketExchangeRate
229:          for (uint256 i = 0; i < positionIndexes.length; ) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L229

[N‑04] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 15 instances of this issue:

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

/// @audit 0.5
/// @audit 1e18
209:              return 0.5 * 1e18;

/// @audit 0.5
/// @audit 1e18
/// @audit 0.05
213:              return 0.5 * 1e18 + (_fundedExtraordinaryProposals.length * (0.05 * 1e18));

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L209

File: ajna-grants/src/grants/base/Funding.sol

/// @audit 0x20
123:                  selector := mload(add(selDataWithSig, 0x20))

/// @audit 0xa9059cbb
125:              if (selector != bytes4(0xa9059cbb)) revert InvalidProposal();

/// @audit 68
133:                  tokensRequested := mload(add(tokenDataWithSig, 68))

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L123

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit 9
391:          if (newProposal.tokensRequested > (currentDistribution.fundsAvailable * 9 / 10)) revert InvalidProposal();

/// @audit 9
448:              if (totalTokensRequested > (gbc * 9 / 10)) {

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

File: ajna-grants/src/grants/libraries/Maths.sol

/// @audit 3
19:           if (y > 3) {

/// @audit 9
30:           z = z * 10**9;

/// @audit 18
34:           return (x * y + 10**18 / 2) / 10**18;

/// @audit 18
38:           return (x * 10**18 + y / 2) / y;

/// @audit 18
47:           z = n % 2 != 0 ? x : 10**18;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L19

[N‑05] Numeric values having to do with time should use time units for readability

There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used

There are 3 instances of this issue:

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit 50400
29        /**
30         * @notice Length of the challengephase of the distribution period in blocks.
31         * @dev    Roughly equivalent to the number of blocks in 7 days.
32         * @dev    The period in which funded proposal slates can be checked in updateSlate.
33         */
34:       uint256 internal constant CHALLENGE_PERIOD_LENGTH = 50400;

/// @audit 648000
36        /**
37         * @notice Length of the distribution period in blocks.
38         * @dev    Roughly equivalent to the number of blocks in 90 days.
39         */
40:       uint48 internal constant DISTRIBUTION_PERIOD_LENGTH = 648000;

/// @audit 72000
42        /**
43         * @notice Length of the funding phase of the distribution period in blocks.
44         * @dev    Roughly equivalent to the number of blocks in 10 days.
45         */
46:       uint256 internal constant FUNDING_PERIOD_LENGTH = 72000;

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

[N‑06] Events that mark critical parameter changes should contain both the old and the new value

This should especially be done if the new value is not required to be different from the old value

There is one instance of this issue:

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit updateSlate()
335               emit FundedSlateUpdated(
336                   distributionId_,
337                   newSlateHash
338:              );

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/StandardFunding.sol#L335-L338

[N‑07] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts. constants should be used for literal values written into the code, and immutable variables should be used for expressions, or values calculated in, or passed into the constructor.

There are 2 instances of this issue:

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

28:       bytes32 internal constant DESCRIPTION_PREFIX_HASH_EXTRAORDINARY = keccak256(bytes("Extraordinary Funding: "));

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L28

File: ajna-grants/src/grants/base/StandardFunding.sol

51:       bytes32 internal constant DESCRIPTION_PREFIX_HASH_STANDARD = keccak256(bytes("Standard Funding: "));

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

[N‑08] Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18)

While the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist

There are 5 instances of this issue:

File: ajna-grants/src/grants/libraries/Maths.sol

6:        uint256 public constant WAD = 10**18;

30:           z = z * 10**9;

34:           return (x * y + 10**18 / 2) / 10**18;

38:           return (x * 10**18 + y / 2) / y;

47:           z = n % 2 != 0 ? x : 10**18;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L6

[N‑09] Use @inheritdoc rather than using a non-standard annotation

There is one instance of this issue:

File: ajna-core/src/PositionManager.sol

514       /**
515        * @dev See {IERC721Metadata-tokenURI}.
516        */
517       function tokenURI(
518           uint256 tokenId_
519:      ) public view override(ERC721) returns (string memory) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L514-L519

[N‑10] Inconsistent spacing in comments

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file

There are 4 instances of this issue:

File: ajna-grants/src/grants/base/Funding.sol

121:              //slither-disable-next-line assembly

131:              //slither-disable-next-line assembly

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L121

File: ajna-grants/src/grants/base/StandardFunding.sol

771:              //slither-disable-next-line incorrect-equality

798:              //slither-disable-next-line incorrect-equality

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

[N‑11] Lines are too long

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. The solidity style guide recommends a maximumum line length of 120 characters, so the lines below should be split when they reach that length.

There are 70 instances of this issue:

File: ajna-core/src/PositionManager.sol

423:          address erc20DeployedPoolAddress  = erc20PoolFactory.deployedPools(subsetHash_, collateralAddress, quoteAddress);

424:          address erc721DeployedPoolAddress = erc721PoolFactory.deployedPools(subsetHash_, collateralAddress, quoteAddress);

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L423

File: ajna-core/src/RewardsManager.sol

53:        * @dev ensures that rewards issued to staked lenders in a given pool are less than the `Ajna` tokens burned in that pool.

76:       /// @dev Mapping of per pool bucket exchange rates at a given burn event `poolAddress => bucketIndex => epoch => bucket exchange rate`.

168:              IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams(

379:       *  @dev    Rewards are calculated as the difference in exchange rates between the last interaction burn event and the current burn event.

381:       *  @param  epochToClaim_ The burn epoch to claim rewards for (rewards calculation starts from the last claimed epoch).

418:       *  @dev    Rewards are calculated as the difference in exchange rates between the last interaction burn event and the current burn event.

557:       *  @param  epochToClaim_  The burn epoch to claim rewards for (rewards calculation starts from the last claimed epoch)

558:       *  @param  validateEpoch_ True if the epoch is received as a parameter and needs to be validated (lower or equal with latest epoch).

653:          uint256 totalBurned   = totalBurnedLatest   != 0 ? totalBurnedLatest   - totalBurnedAtBlock   : totalBurnedAtBlock;

654:          uint256 totalInterest = totalInterestLatest != 0 ? totalInterestLatest - totalInterestAtBlock : totalInterestAtBlock;

665:       *  @dev    Called as part of `stake`, `unstake`, and `claimRewards`, as well as `updateBucketExchangeRatesAndClaim`.

666:       *  @dev    Caller can claim `5%` of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated.

787:              // skip reward calculation if update at the previous epoch was missed and if exchange rate decreased due to bad debt

788:              // prevents excess rewards from being provided from using a 0 value as an input to the interestFactor calculation below.

808:       *  @dev   It is used to ensure that rewards claimers will be able to claim some portion of the remaining tokens if a claim would exceed the remaining contract balance.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L53

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

62:           proposalId_ = _hashProposal(targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_EXTRAORDINARY, descriptionHash_)));

70:           if (proposal.executed || !_extraordinaryProposalSucceeded(proposalId_, tokensRequested)) revert ExecuteExtraordinaryProposalInvalid();

92:           proposalId_ = _hashProposal(targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_EXTRAORDINARY, keccak256(bytes(description_)))));

105:          if (uint256(totalTokensRequested) > _getSliceOfTreasury(Maths.WAD - _getMinimumThresholdPercentage())) revert InvalidProposal();

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L62

File: ajna-grants/src/grants/base/Funding.sol

48:        * @param targets_   The list of smart contract targets for the calldata execution. Should be the Ajna token address.

70:        * @dev    Voting power is the minimum of the amount of votes available at a snapshot block 33 blocks prior to voting start, and at the vote starting block.

72:        * @param snapshot_       One of the block numbers to retrieve the voting power at. 33 blocks prior to the block at which a proposal is available for voting.

110:          if (targets_.length == 0 || targets_.length != values_.length || targets_.length != calldatas_.length) revert InvalidProposal();

144:       * @notice Create a proposalId from a hash of proposal's targets, values, and calldatas arrays, and a description hash.

149:       * @param descriptionHash_ The hash of the proposal's description string. Generated by keccak256(bytes(description))).

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L48

File: ajna-grants/src/grants/base/StandardFunding.sol

269:       * @dev    Voter must have voted in both the screening and funding stages, and is proportional to their share of votes across the stages.

310:          uint256 sum = _validateSlate(distributionId_, currentDistribution.endBlock, currentDistribution.fundsAvailable, proposalIds_, numProposalsInSlate);

349:          proposalId_ = _hashProposal(targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_STANDARD, descriptionHash_)));

354:          // check that the distribution period has ended, and one week has passed to enable competing slates to be checked

355:          if (block.number <= _getChallengeStageEndBlock(_distributions[distributionId].endBlock)) revert ExecuteProposalInvalid();

372:          proposalId_ = _hashProposal(targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_STANDARD, keccak256(bytes(description_)))));

388:          newProposal.tokensRequested = _validateCallDatas(targets_, values_, calldatas_); // check proposal parameters are valid and update tokensRequested

411:       * @notice Check the validity of a potential slate of proposals to execute, and sum the slate's fundingVotesReceived.

419:       * @return sum_                              The total funding votes received by all proposals in the proposed slate.

421:      function _validateSlate(uint24 distributionId_, uint256 endBlock, uint256 distributionPeriodFundsAvailable_, uint256[] calldata proposalIds_, uint256 numProposalsInSlate_) internal view returns (uint256 sum_) {

438:              if (_findProposalIndex(proposalIds_[i], _topTenProposals[distributionId_]) == -1) revert InvalidProposalSlate();

444:              sum_ += uint128(proposal.fundingVotesReceived); // since we are converting from int128 to uint128, we can safely assume that the value will not overflow

541:              uint128 newVotingPower = SafeCast.toUint128(_getVotesFunding(msg.sender, votingPower, voter.remainingVotingPower, screeningStageEndBlock));

556:              if (_findProposalIndex(voteParams_[i].proposalId, _topTenProposals[currentDistributionId]) == -1) revert InvalidVote();

578:          if (block.number < currentDistribution.startBlock || block.number > _getScreeningStageEndBlock(currentDistribution.endBlock)) revert InvalidVote();

609:       * @param  voteParams_           The amount of votes being allocated to the proposal. Not squared. If less than 0, vote is against.

668:          // calculate the change in voting power used by the voter in this vote in order to accurately track the total voting power used in the funding stage

682:          // emits the amount of incremental votes cast for the proposal, not the voting power cost or total votes on a proposal

706:          if (screeningVotesCast[distributionId][account_] + votes_ > _getVotesScreening(distributionId, account_)) revert InsufficientVotingPower();

732:              else if(_standardFundingProposals[currentTopTenProposals[screenedProposalsLength - 1]].votesReceived < proposal_.votesReceived) {

823:              _standardFundingProposals[proposals_[targetProposalId_]].votesReceived > _standardFundingProposals[proposals_[targetProposalId_ - 1]].votesReceived

864:          return _findProposalIndex(proposalId_, _fundedProposalSlates[_distributions[distributionId].fundedSlateHash]) != -1;

886:       * @param  votingPower_            The voter's voting power in the funding round. Equal to the square of their tokens in the voting snapshot.

887:       * @param  remainingVotingPower_   The voter's remaining quadratic voting power in the given distribution period's funding round.

961:      function getFundingVotesCast(uint24 distributionId_, address account_) external view override returns (FundingVoteParams[] memory) {

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

File: ajna-grants/src/grants/GrantFund.sol

50:           return mechanism == FundingMechanism.Standard ? _standardProposalState(proposalId_) : _getExtraordinaryProposalState(proposalId_);

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/GrantFund.sol#L50

File: ajna-grants/src/grants/interfaces/IGrantFund.sol

31:        * @notice Create a proposalId from a hash of proposal's targets, values, and calldatas arrays, and a description hash.

36:        * @param descriptionHash_ The hash of the proposal's description string. Generated by keccak256(bytes(description))).

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IGrantFund.sol#L31

File: ajna-grants/src/grants/interfaces/IStandardFunding.sol

132:       * @notice Contains information about voters during a vote made by a QuadraticVoter in the Funding stage of a distribution period.

152:          uint128 votingPower;           // amount of votes originally available to the voter, equal to the sum of the square of their initial votes

190:       * @param  targets_         List of contracts the proposal calldata will interact with. Should be the Ajna token contract for all proposals.

205:       * @dev    All proposals can be submitted by anyone. There can only be one value in each array. Interface inherits from OZ.propose().

206:       * @param  targets_     List of contracts the proposal calldata will interact with. Should be the Ajna token contract for all proposals.

220:       * @notice Check if a slate of proposals meets requirements, and maximizes votes. If so, update QuarterlyDistribution.

223:       * @return newTopSlate_    Boolean indicating whether the new proposal slate was set as the new top slate for distribution.

318:      function getFundingVotesCast(uint24 distributionId_, address account_) external view returns (FundingVoteParams[] memory);

325:       * @return votesReceived     The amount of votes the proposal has received in it's distribution period's screening round.

327:       * @return qvBudgetAllocated The amount of quadratic vote budget allocated to the proposal in it's distribution period's funding round.

344:       * @notice Retrieve the top ten proposals that have received the most votes in a given distribution period's screening round.

358:       * @return votingPower          The voter's voting power in the funding round. Equal to the square of their tokens in the voting snapshot.

359:       * @return remainingVotingPower The voter's remaining quadratic voting power in the given distribution period's funding round.

368:       * @notice Get the remaining quadratic voting power available to the voter in the funding stage of a distribution period.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IStandardFunding.sol#L132

File: ajna-grants/src/grants/libraries/Maths.sol

14:        * @dev Utilizes the babylonian method: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L14

[N‑12] Typos

There are 16 instances of this issue:

File: ajna-core/src/PositionManager.sol

/// @audit bucekt
83:           uint256 depositTime;      // lender deposit time in from bucekt

/// @audit memorialzied
434:       *  @return `True` if the bucket went bankrupt after that position memorialzied their `LP`.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L83

File: ajna-core/src/RewardsManager.sol

/// @audit aready
189:          // update to bucket list exchange rates, from buckets are aready updated on claim

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L189

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

/// @audit succesful
69:           // check proposal is succesful and hasn't already been executed

/// @audit compatability
186:       * @dev    Used by GrantFund.state() for analytics compatability purposes.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L69

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit calculat
270:       * @param  currentDistribution_ Struct of the distribution period to calculat rewards for.

/// @audit succesful
357:          // check proposal is succesful and hasn't already been executed

/// @audit compatability
501:       * @dev    Used by GrantFund.state() for analytics compatability purposes.

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

File: ajna-grants/src/grants/interfaces/IExtraordinaryFunding.sol

/// @audit succesful
122:       *  @return executed        Whether a succesful proposal has been executed.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IExtraordinaryFunding.sol#L122

File: ajna-grants/src/grants/interfaces/IFunding.sol

/// @audit paramteres
21:        * @notice User submitted a proposal with invalid paramteres.

/// @audit execcution
38:        * @notice Proposal didn't meet requirements for execcution.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IFunding.sol#L21

File: ajna-grants/src/grants/interfaces/IStandardFunding.sol

/// @audit distrubtions
82:        *  @param  startBlock     Block number of the quarterly distrubtions start.

/// @audit distrubtions
83:        *  @param  endBlock       Block number of the quarterly distrubtions end.

/// @audit whinch
175:       * @param  distributionId_ Id of distribution from whinch delegatee wants to claim his reward.

/// @audit succesfully
189:       * @dev    Check for proposal being succesfully funded or previously executed is handled by Governor.execute().

/// @audit succesfully
316:       * @return FundingVoteParams The list of FundingVoteParams structs that have been succesfully cast the voter.

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IStandardFunding.sol#L82

[N‑13] NatSpec @param is missing

There are 5 instances of this issue:

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

/// @audit Missing: '@param tokensRequested_'
159       /**
160        * @notice Check if a proposal for extraordinary funding has succeeded.
161        * @param  proposalId_ The ID of the proposal being checked.
162        * @return Boolean indicating whether the proposal has succeeded.
163        */
164       function _extraordinaryProposalSucceeded(
165           uint256 proposalId_,
166           uint256 tokensRequested_
167:      ) internal view returns (bool) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L159-L167

File: ajna-grants/src/grants/base/Funding.sol

/// @audit Missing: '@param proposalId_'
46         /**
47         * @notice Execute the calldata of a passed proposal.
48         * @param targets_   The list of smart contract targets for the calldata execution. Should be the Ajna token address.
49         * @param values_    Unused. Should be 0 since all calldata is executed on the Ajna token's transfer method.
50         * @param calldatas_ The list of calldatas to execute.
51         */
52        function _execute(
53            uint256 proposalId_,
54            address[] memory targets_,
55            uint256[] memory values_,
56:           bytes[] memory calldatas_

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L46-L56

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit Missing: '@param distributionId_'
867       /**
868        * @notice Retrieve the number of votes available to an account in the current screening stage.
869        * @param  account_ The account to retrieve votes for.
870        * @return votes_   The number of votes available to an account in this screening stage.
871        */
872:      function _getVotesScreening(uint24 distributionId_, address account_) internal view returns (uint256 votes_) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/StandardFunding.sol#L867-L872

File: ajna-grants/src/grants/libraries/Maths.sol

/// @audit Missing: '@param x'
/// @audit Missing: '@param n'
45        /** @notice Raises a WAD to the power of an integer and returns a WAD */
46:       function wpow(uint256 x, uint256 n) internal pure returns (uint256 z) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L45-L46

[N‑14] NatSpec @return argument is missing

There is one instance of this issue:

File: ajna-grants/src/grants/libraries/Maths.sol

/// @audit Missing: '@return'
45        /** @notice Raises a WAD to the power of an integer and returns a WAD */
46:       function wpow(uint256 x, uint256 n) internal pure returns (uint256 z) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L45-L46

[N‑15] Event is not properly indexed

Index event fields make the field more quickly accessible to off-chain tools that parse events. This is especially useful when it comes to filtering based on an address. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Where applicable, each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three applicable fields, all of the applicable fields should be indexed.

There is one instance of this issue:

File: ajna-grants/src/grants/interfaces/IFunding.sol

54        event ProposalCreated(
55            uint256 proposalId,
56            address proposer,
57            address[] targets,
58            uint256[] values,
59            string[] signatures,
60            bytes[] calldatas,
61            uint256 startBlock,
62            uint256 endBlock,
63            string description
64:       );

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IFunding.sol#L54-L64

[N‑16] Consider using delete rather than assigning zero to clear values

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic

There is one instance of this issue:

File: ajna-core/src/PositionManager.sol

197:                      position.lps = 0;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L197

[N‑17] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

There is one instance of this issue:

File: Various Files

[N‑18] Large or complicated code bases should implement invariant tests

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement invariant fuzzing tests. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

There is one instance of this issue:

File: Various Files

[N‑19] Function ordering does not follow the Solidity style guide

According to the Solidity style guide, functions should be laid out in the following order :constructor(), receive(), fallback(), external, public, internal, private, but the cases below do not follow this pattern

There are 7 instances of this issue:

File: ajna-core/src/PositionManager.sol

/// @audit _bucketBankruptAfterDeposit() came earlier
450       function getLP(
451           uint256 tokenId_,
452           uint256 index_
453:      ) external override view returns (uint256) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L450-L453

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

/// @audit _getVotesExtraordinary() came earlier
263:      function getMinimumThresholdPercentage() external view returns (uint256) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L263

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit _setNewDistributionId() came earlier
236       function claimDelegateReward(
237           uint24 distributionId_
238:      ) external override returns(uint256 rewardClaimed_) {

/// @audit _getDelegateReward() came earlier
300       function updateSlate(
301           uint256[] calldata proposalIds_,
302           uint24 distributionId_
303:      ) external override returns (bool newTopSlate_) {

/// @audit _standardProposalState() came earlier
519       function fundingVote(
520           FundingVoteParams[] memory voteParams_
521:      ) external override returns (uint256 votesCast_) {

/// @audit _getVotesFunding() came earlier
917       function getDelegateReward(
918           uint24 distributionId_,
919           address voter_
920:      ) external view override returns (uint256 rewards_) {

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

File: ajna-grants/src/grants/GrantFund.sol

/// @audit findMechanismOfProposal() came earlier
45        function state(
46            uint256 proposalId_
47:       ) external view override returns (ProposalState) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/GrantFund.sol#L45-L47

[N‑20] Contract does not follow the Solidity style guide's suggested layout ordering

The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering

There is one instance of this issue:

File: ajna-grants/src/grants/interfaces/IFunding.sol

/// @audit event VoteCast came earlier
78        enum FundingMechanism {
79            Standard,
80            Extraordinary
81:       }

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IFunding.sol#L78-L81

Gas Optimizations

[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 2 instances of this issue:

File: ajna-core/src/PositionManager.sol

54        /// @dev Mapping of `token id => ajna pool address` for which token was minted.
55        mapping(uint256 => mapping(uint256 => Position)) internal positions;
56        /// @dev Mapping of `token id => nonce` value used for permit.
57        mapping(uint256 => uint96)                       internal nonces;
58        /// @dev Mapping of `token id => bucket indexes` associated with position.
59:       mapping(uint256 => EnumerableSet.UintSet)        internal positionIndexes;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L54-L59

File: ajna-core/src/RewardsManager.sol

71        /// @dev `epoch => rewards claimed` mapping.
72        mapping(uint256 => uint256) public override rewardsClaimed;
73        /// @dev `epoch => update bucket rate rewards claimed` mapping.
74:       mapping(uint256 => uint256) public override updateRewardsClaimed;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L71-L74

[G‑02] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

There are 4 instances of this issue:

File: ajna-grants/src/grants/base/StandardFunding.sol

203:          uint256[] memory fundingProposalIds = _fundedProposalSlates[fundedSlateHash];

209:              Proposal memory proposal = _standardFundingProposals[fundingProposalIds[i]];

379:          QuarterlyDistribution memory currentDistribution = _distributions[_currentDistributionId];

575:          QuarterlyDistribution memory currentDistribution = _distributions[_currentDistributionId];

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

[G‑03] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There is one instance of this issue:

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

/// @audit _fundedExtraordinaryProposals on line 208
213:              return 0.5 * 1e18 + (_fundedExtraordinaryProposals.length * (0.05 * 1e18));

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L213

[G‑04] Multiple accesses of a mapping/array should use a local variable cache

The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata

There are 24 instances of this issue:

File: ajna-core/src/RewardsManager.sol

/// @audit stakes[tokenId_] on line 330
331:          uint256 lastClaimedEpoch = stakes[tokenId_].lastClaimedEpoch;

/// @audit stakes[tokenId_] on line 331
332:          uint256 stakingEpoch     = stakes[tokenId_].stakingEpoch;

/// @audit stakes[tokenId_] on line 356
357:              stakes[tokenId_].ajnaPool,

/// @audit stakes[tokenId_] on line 357
358:              stakes[tokenId_].lastClaimedEpoch

/// @audit stakes[tokenId_] on line 368
369:              stakes[tokenId_].snapshot[bucketId_].rateAtStakeTime

/// @audit stakes[tokenId_] on line 389
390:          uint256 lastClaimedEpoch = stakes[tokenId_].lastClaimedEpoch;

/// @audit stakes[tokenId_] on line 390
391:          uint256 stakingEpoch     = stakes[tokenId_].stakingEpoch;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L331

File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol

/// @audit _extraordinaryFundingProposals[proposalId_] on line 286
287:              _extraordinaryFundingProposals[proposalId_].startBlock,

/// @audit _extraordinaryFundingProposals[proposalId_] on line 287
288:              _extraordinaryFundingProposals[proposalId_].endBlock,

/// @audit _extraordinaryFundingProposals[proposalId_] on line 288
289:              _extraordinaryFundingProposals[proposalId_].tokensRequested,

/// @audit _extraordinaryFundingProposals[proposalId_] on line 289
290:              _extraordinaryFundingProposals[proposalId_].votesReceived,

/// @audit _extraordinaryFundingProposals[proposalId_] on line 290
291:              _extraordinaryFundingProposals[proposalId_].executed

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L287

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit _distributions[distributionId_] on line 200
201:          uint256 fundsAvailable  = _distributions[distributionId_].fundsAvailable;

/// @audit _standardFundingProposals[<etc>] on line 823
823:              _standardFundingProposals[proposals_[targetProposalId_]].votesReceived > _standardFundingProposals[proposals_[targetProposalId_ - 1]].votesReceived

/// @audit _distributions[distributionId_] on line 937
938:              _distributions[distributionId_].startBlock,

/// @audit _distributions[distributionId_] on line 938
939:              _distributions[distributionId_].endBlock,

/// @audit _distributions[distributionId_] on line 939
940:              _distributions[distributionId_].fundsAvailable,

/// @audit _distributions[distributionId_] on line 940
941:              _distributions[distributionId_].fundingVotePowerCast,

/// @audit _distributions[distributionId_] on line 941
942:              _distributions[distributionId_].fundedSlateHash

/// @audit _standardFundingProposals[proposalId_] on line 970
971:              _standardFundingProposals[proposalId_].distributionId,

/// @audit _standardFundingProposals[proposalId_] on line 971
972:              _standardFundingProposals[proposalId_].votesReceived,

/// @audit _standardFundingProposals[proposalId_] on line 972
973:              _standardFundingProposals[proposalId_].tokensRequested,

/// @audit _standardFundingProposals[proposalId_] on line 973
974:              _standardFundingProposals[proposalId_].fundingVotesReceived,

/// @audit _standardFundingProposals[proposalId_] on line 974
975:              _standardFundingProposals[proposalId_].executed

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

[G‑05] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 7 instances of this issue:

File: ajna-core/src/PositionManager.sol

416       function _isAjnaPool(
417           address pool_,
418           bytes32 subsetHash_
419:      ) internal view returns (bool) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L416-L419

File: ajna-core/src/RewardsManager.sol

384       function _calculateAndClaimRewards(
385           uint256 tokenId_,
386           uint256 epochToClaim_
387:      ) internal returns (uint256 rewards_) {

487       function _calculateExchangeRateInterestEarned(
488           address pool_,
489           uint256 nextEventEpoch_,
490           uint256 bucketIndex_,
491           uint256 bucketLP_,
492           uint256 exchangeRate_
493:      ) internal view returns (uint256 interestEarned_) {

519       function _calculateNewRewards(
520           address ajnaPool_,
521           uint256 interestEarned_,
522           uint256 nextEpoch_,
523           uint256 epoch_,
524           uint256 rewardsClaimedInEpoch_
525:      ) internal view returns (uint256 newRewards_) {

606       function _getBurnEpochsClaimed(
607           uint256 lastClaimedEpoch_,
608           uint256 burnEpochToStartClaim_
609:      ) internal pure returns (uint256[] memory burnEpochsClaimed_) {

743       function _updateBucketExchangeRate(
744           address pool_,
745           uint256 bucketIndex_,
746:          uint256 burnEpoch_

768       function _updateBucketExchangeRateAndCalculateRewards(
769           address pool_,
770           uint256 bucketIndex_,
771           uint256 burnEpoch_,
772           uint256 totalBurned_,
773           uint256 interestEarned_
774:      ) internal returns (uint256 rewards_) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L384-L387

[G‑06] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a }

There is one instance of this issue:

File: ajna-core/src/RewardsManager.sol

/// @audit if-condition on line 500
504:                  interestEarned_ = Maths.wmul(nextExchangeRate - exchangeRate_, bucketLP_);

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L504

[G‑07] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 8 instances of this issue:

File: ajna-core/src/RewardsManager.sol

229:          for (uint256 i = 0; i < positionIndexes.length; ) {

290:          for (uint256 i = 0; i < positionIndexes.length; ) {

440:          for (uint256 i = 0; i < positionIndexes_.length; ) {

680:              for (uint256 i = 0; i < indexes_.length; ) {

704:                  for (uint256 i = 0; i < indexes_.length; ) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L229

File: ajna-grants/src/grants/base/Funding.sol

62:           for (uint256 i = 0; i < targets_.length; ++i) {

112:          for (uint256 i = 0; i < targets_.length;) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L62

File: ajna-grants/src/grants/base/StandardFunding.sol

491:          for (uint i = 0; i < proposalIdSubset_.length;) {

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

[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

There is one instance of this issue:

File: ajna-grants/src/grants/base/Funding.sol

62:           for (uint256 i = 0; i < targets_.length; ++i) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L62

[G‑09] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 3 instances of this issue:

File: ajna-grants/src/grants/interfaces/IExtraordinaryFunding.sol

/// @audit executeExtraordinary(), proposeExtraordinary(), voteExtraordinary(), getSliceOfNonTreasury(), getSliceOfTreasury(), getExtraordinaryProposalInfo(), getExtraordinaryProposalSucceeded(), getMinimumThresholdPercentage(), getVotesExtraordinary()
9:    interface IExtraordinaryFunding {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IExtraordinaryFunding.sol#L9

File: ajna-grants/src/grants/interfaces/IGrantFund.sol

/// @audit hashProposal(), state(), fundTreasury()
9:    interface IGrantFund is

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IGrantFund.sol#L9

File: ajna-grants/src/grants/interfaces/IStandardFunding.sol

/// @audit startNewDistributionPeriod(), claimDelegateReward(), executeStandard(), proposeStandard(), updateSlate(), fundingVote(), screeningVote(), getDelegateReward(), getDistributionId(), getDistributionPeriodInfo(), getFundedProposalSlate(), getFundingPowerVotes(), getFundingVotesCast(), getProposalInfo(), getSlateHash(), getTopTenProposals(), getVoterInfo(), getVotesFunding(), getVotesScreening()
9:    interface IStandardFunding {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/interfaces/IStandardFunding.sol#L9

[G‑10] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

There are 4 instances of this issue:

File: ajna-grants/src/grants/base/Funding.sol

/// @audit uint128 tokensRequested_
137:              tokensRequested_ += SafeCast.toUint128(tokensRequested);

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/base/Funding.sol#L137

File: ajna-grants/src/grants/base/StandardFunding.sol

/// @audit uint24 newDistributionId_
146:          newDistributionId_ = _setNewDistributionId();

/// @audit uint128 sum_
493:              sum_ += uint128(_standardFundingProposals[proposalIdSubset_[i]].fundingVotesReceived);

/// @audit uint8 support
623:          voteParams_.votesUsed < 0 ? support = 0 : support = 1;

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

[G‑11] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There is one instance of this issue:

File: ajna-grants/src/grants/libraries/Maths.sol

6:        uint256 public constant WAD = 10**18;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L6

[G‑12] Division by two should use bit shifting

<x> / 2 is the same as <x> >> 1. While the compiler uses the SHR opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMPs to and from a compiler utility function that introduces checks which can be avoided by using unchecked {} around the division by two

There are 4 instances of this issue:

File: ajna-grants/src/grants/libraries/Maths.sol

21:               uint256 x = y / 2 + 1;

24:                   x = (y / x + x) / 2;

34:           return (x * y + 10**18 / 2) / 10**18;

38:           return (x * 10**18 + y / 2) / y;

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-grants/src/grants/libraries/Maths.sol#L21

[G‑13] Constructors can be marked payable

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

There are 2 instances of this issue:

File: ajna-core/src/PositionManager.sol

116       constructor(
117           ERC20PoolFactory erc20Factory_,
118           ERC721PoolFactory erc721Factory_
119:      ) PermitERC721("Ajna Positions NFT-V1", "AJNA-V1-POS", "1") {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/PositionManager.sol#L116-L119

File: ajna-core/src/RewardsManager.sol

95:       constructor(address ajnaToken_, IPositionManager positionManager_) {

https://github.com/code-423n4/2023-05-ajna/blob/6995f24bdf9244fa35880dda21519ffc131c905c/ajna-core/src/RewardsManager.sol#L95

@CloudEllie
Copy link
Author

Judge comments:

[M‑01] Use of transferFrom() rather than safeTransferFrom() for NFTs in will lead to the loss of NFTs 1
L

[M‑02] _safeMint() should be used rather than _mint() wherever possible 1
L

[L‑01] Loss of precision 1
L

[L‑02] Array lengths not checked 5
R

[N‑01] The nonReentrant modifier should occur before all other modifiers 1
R

[N‑02] require()/revert() statements should have descriptive reason strings 1
NC

[N‑03] External calls in an un-bounded for-loop may result in a DOS 1
L

[N‑04] constants should be defined rather than using magic numbers 15
R

[N‑05] Numeric values having to do with time should use time units for readability 3
I

[N‑06] Events that mark critical parameter changes should contain both the old and the new value 1
NC

[N‑07] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant 2
I

[N‑08] Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18) 5
NC

[N‑09] Use @inheritdoc rather than using a non-standard annotation 1
NC

[N‑10] Inconsistent spacing in comments 4
NC

[N‑11] Lines are too long 70
NC

[N‑12] Typos 16
NC

[N‑13] NatSpec @param is missing 5
NC

[N‑14] NatSpec @return argument is missing 1
13

[N‑15] Event is not properly indexed 1
R (proposalId and proposer)

[N‑16] Consider using delete rather than assigning zero to clear values 1
I

[N‑17] Contracts should have full test coverage 1
I

[N‑18] Large or complicated code bases should implement invariant tests 1
I

[N‑19] Function ordering does not follow the Solidity style guide 7
NC

[N‑20] Contract does not follow the Solidity style guide's suggested layout ordering 1
NC

Gas Optimizations
Issue Instances Total Gas Saved
[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 2 -
NC

[G‑02] Using storage instead of memory for structs/arrays saves gas 4 16800
R

[G‑03] State variables should be cached in stack variables rather than re-reading them from storage 1 97
L

[G‑04] Multiple accesses of a mapping/array should use a local variable cache 24 1008
I

[G‑05] internal functions only called once can be inlined to save gas 7 140
I

[G‑06] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement 1 85
NC

[G‑07] .length should not be looked up in every loop of a for-loop 8 24
NC

[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops 1 60
NC

[G‑09] Optimize names to save gas 3 66
I

[G‑10] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 4 -
I

[G‑11] Using private rather than public for constants, saves gas 1 -
I

[G‑12] Division by two should use bit shifting 4 80
NC

[G‑13] Constructors can be marked payable
I

safeTransfer for ERC20 vs ERC721 was the reason I ended up awarding this bot the best

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment