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
Rank: 102/114
Findings: 1
Award: $22.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0x73696d616f, 0xSmartContract, 0xnev, Audit_Avengers, Aymen0909, Blckhv, Eurovickk, K42, Kenshin, Rageur, Raihan, ReyAdmirado, SAAJ, SAQ, Shubham, Tomio, Walter, ayden, codeslide, descharre, dicethedev, hunter_w3b, j4ld1na, kaveyjoe, okolicodes, patitonar, petrichor, pontifex, yongskiws
22.2767 USDC - $22.28
Issues | Instances | |
---|---|---|
G-01 | Default value initialization. | 24 |
G-02 | Do not calculate constants. | 6 |
G-03 | Using calldata instead of memory for read-only arguments in external functions saves gas. | 6 |
G-04 | abi.encode() is less efficient than abi.encodePacked() . | 7 |
G-05 | Use assembly to check for address(0) . | 1 |
G-06 | Revert as early as possible. | 1 |
G-07 | When possible, use assembly instead of unchecked{++i} . | 1 |
G-08 | Assembly for If Statements . Save Gas. | 1 |
If a variable is not set/initialized, it is assumed to have the default value (0
, false
, 0x0
etc depending on the data type).
Explicitly initializing it with its default value is an anti-pattern and wastes gas.
There are 24 instances:
ajna-core/src/PositionManager.sol#L181
for (uint256 i = 0; i < indexesLength; ) {
ajna-core/src/PositionManager.sol#L364
for (uint256 i = 0; i < indexesLength; ) {
ajna-core/src/PositionManager.sol#L474
uint256 filteredIndexesLength = 0;
ajna-core/src/PositionManager.sol#L476
for (uint256 i = 0; i < indexesLength; ) {
ajna-core/src/RewardsManager.sol#L163
for (uint256 i = 0; i < fromBucketLength; ) {
ajna-core/src/RewardsManager.sol#L229
for (uint256 i = 0; i < positionIndexes.length; ) {
ajna-core/src/RewardsManager.sol#L290
for (uint256 i = 0; i < positionIndexes.length; ) {
ajna-core/src/RewardsManager.sol#L440
for (uint256 i = 0; i < positionIndexes_.length; ) {
ajna-core/src/RewardsManager.sol#L680
for (uint256 i = 0; i < indexes_.length; ) {
ajna-core/src/RewardsManager.sol#L704
for (uint256 i = 0; i < indexes_.length; ) {
ajna-grants/src/grants/base/Funding.sol#L62
for (uint256 i = 0; i < targets_.length; ++i) {
ajna-grants/src/grants/base/Funding.sol#L112
for (uint256 i = 0; i < targets_.length;) {
ajna-grants/src/grants/base/StandardFunding.sol#L63
uint24 internal _currentDistributionId = 0;
ajna-grants/src/grants/base/StandardFunding.sol#L208
for (uint i = 0; i < numFundedProposals; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L324
for (uint i = 0; i < numProposalsInSlate; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L431
uint256 totalTokensRequested = 0;
ajna-grants/src/grants/base/StandardFunding.sol#L434
for (uint i = 0; i < numProposalsInSlate_; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L468
for (uint i = 0; i < numProposals; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L491
for (uint i = 0; i < proposalIdSubset_.length;) {
ajna-grants/src/grants/base/StandardFunding.sol#L549
for (uint256 i = 0; i < numVotesCast; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L582
for (uint256 i = 0; i < numVotesCast; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L770
for (int256 i = 0; i < arrayLength;) {
ajna-grants/src/grants/base/StandardFunding.sol#L797
for (int256 i = 0; i < numVotesCast; ) {
ajna-grants/src/grants/base/StandardFunding.sol#L848
for (uint256 i = 0; i < numVotesCast; ) {
Recommended Mitigation Steps:
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
There are 6 instances:
ajna-core/src/RewardsManager.sol#L46
uint256 internal constant REWARD_CAP = 0.8 * 1e18;
ajna-core/src/RewardsManager.sol#L50
uint256 internal constant UPDATE_CAP = 0.1 * 1e18;
ajna-core/src/RewardsManager.sol#L55
uint256 internal constant REWARD_FACTOR = 0.5 * 1e18;
ajna-core/src/RewardsManager.sol#L59
uint256 internal constant UPDATE_CLAIM_REWARD = 0.05 * 1e18;
ajna-grants/src/grants/base/StandardFunding.sol#L27
uint256 internal constant GLOBAL_BUDGET_CONSTRAINT = 0.03 * 1e18;
ajna-grants/src/grants/libraries/Maths.sol#L6
uint256 public constant WAD = 10**18;
Recommended Mitigation Steps:
calldata
instead of memory
for read-only arguments in external
functions saves gas.When a function with a memory
array is called externally, the abi.decode()
step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length
). Using calldata
directly, obliviates the need for such a loop in the contract code and runtime execution. Structs have the same overhead as an array of length one.
There are 6 instances:
ajna-grants/src/grants/GrantFund.sol#L23-L25
function hashProposal( address[] memory targets_, uint256[] memory values_, bytes[] memory calldatas_, bytes32 descriptionHash_ ) external pure override returns (uint256 proposalId_) {
ajna-core/src/RewardsManager.sol#L137-L138
function moveStakedLiquidity( uint256 tokenId_, uint256[] memory fromBuckets_, uint256[] memory toBuckets_, uint256 expiry_ ) external nonReentrant override {
ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L57-L59
function executeExtraordinary( address[] memory targets_, uint256[] memory values_, bytes[] memory calldatas_, bytes32 descriptionHash_ ) external nonReentrant override returns (uint256 proposalId_) {
ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L87-L89
function proposeExtraordinary( uint256 endBlock_, address[] memory targets_, uint256[] memory values_, bytes[] memory calldatas_, string memory description_) external override returns (uint256 proposalId_) {
ajna-grants/src/grants/base/StandardFunding.sol#L344-L346
function executeStandard( address[] memory targets_, uint256[] memory values_, bytes[] memory calldatas_, bytes32 descriptionHash_ ) external nonReentrant override returns (uint256 proposalId_) {
ajna-grants/src/grants/base/StandardFunding.sol#L367-L370
function proposeStandard( address[] memory targets_, uint256[] memory values_, bytes[] memory calldatas_, string memory description_ ) external override returns (uint256 proposalId_) {
Recommended Mitigation Steps:
calldata
.abi.encode()
is less efficient than abi.encodePacked()
.abi.encode
will apply ABI encoding rules. Therefore all elementary types are padded to 32 bytes and dynamic arrays include their length. Therefore it is possible to also decode this data again (with abi.decode
) when the type are known.
abi.encodePacked
will only use the minimal required memory to encode the data. E.g. an address will only use 20 bytes and for dynamic arrays only the elements will be stored without length. For more info see the Solidity docs for packed mode.
There are 7 instances:
ajna-grants/src/grants/base/Funding.sol#L158
proposalId_ = uint256( keccak256(abi.encode(targets_, values_, calldatas_, descriptionHash_)) );
ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L62
proposalId_ = _hashProposal( targets_, values_, calldatas_, keccak256( abi.encode(DESCRIPTION_PREFIX_HASH_EXTRAORDINARY, descriptionHash_) ) );
ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L92
proposalId_ = _hashProposal( targets_, values_, calldatas_, keccak256( abi.encode( DESCRIPTION_PREFIX_HASH_EXTRAORDINARY, keccak256(bytes(description_)) ) ) );
ajna-grants/src/grants/base/StandardFunding.sol#L314
bytes32 newSlateHash = keccak256(abi.encode(proposalIds_));
ajna-grants/src/grants/base/StandardFunding.sol#L349
proposalId_ = _hashProposal( targets_, values_, calldatas_, keccak256(abi.encode(DESCRIPTION_PREFIX_HASH_STANDARD, descriptionHash_)) );
ajna-grants/src/grants/base/StandardFunding.sol#L372
proposalId_ = _hashProposal( targets_, values_, calldatas_, keccak256( abi.encode( DESCRIPTION_PREFIX_HASH_STANDARD, keccak256(bytes(description_)) ) ) );
ajna-grants/src/grants/base/StandardFunding.sol#L983
return keccak256(abi.encode(proposalIds_));
Recommended Mitigation Steps:
abi.encode()
for abi.encodePacked()
.address(0)
.There an instance:
ajna-core/src/RewardsManager.sol#L96
if (ajnaToken_ == address(0)) revert DeployWithZeroAddress();
Recommended Mitigation Steps:
assembly { if iszero(ajnaToken_) { mstore(0x00, "zero address") revert(0x00, 0x20) } } ```
You pay gas for everything before the require()
in the revert case.
The an instance:
ajna-core/src/PositionManager.sol#L233
if (!_isAjnaPool(params_.pool, params_.poolSubsetHash)) revert NotAjnaPool();
Recommended Mitigation Steps:
tokenId_ = _nextId++;
.unchecked{++i}
.You can also use unchecked{++i;}
for even more gas savings but this will not check to see if i
overflows. For best gas savings, use inline assembly, however this limits the functionality you can achieve.
Example:
//loop with unchecked{++i} function uncheckedPlusPlusI() public pure { uint256 j = 0; for (uint256 i; i < 10; ) { j++; unchecked { ++i; } } }
Gas: 1329
//loop with inline assembly function inlineAssemblyLoop() public pure { assembly { let j := 0 for { let i := 0 } lt(i, 10) { i := add(i, 0x01) } { j := add(j, 0x01) } } }
Gas: 709
There an instance:
ajna-core/src/RewardsManager.sol#L293
for (uint256 i = 0; i < positionIndexes.length; ) { delete stakeInfo.snapshot[positionIndexes[i]]; // reset BucketState struct for current position unchecked { ++i; } }
Recommended Mitigations Steps:
Assembly
for If Statements
. Save Gas.ajna-core/src/RewardsManager.sol#L815
Example:
assembly { if gt(x, 2) { x := mul(2, x) } }
There an instances:
if (rewardsEarned_ > ajnaBalance) rewardsEarned_ = ajnaBalance;
Recommended Mitigations Steps:
#0 - c4-judge
2023-05-17T10:51:45Z
Picodes marked the issue as grade-b