Maia DAO Ecosystem - 0xAnah's results

Efficient liquidity renting and management across chains with Curvenized Uniswap V3.

General Information

Platform: Code4rena

Start Date: 30/05/2023

Pot Size: $300,500 USDC

Total HM: 79

Participants: 101

Period: about 1 month

Judge: Trust

Total Solo HM: 36

Id: 242

League: ETH

Maia DAO Ecosystem

Findings Distribution

Researcher Performance

Rank: 86/101

Findings: 1

Award: $62.33

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

62.3314 USDC - $62.33

Labels

bug
G (Gas Optimization)
grade-b
G-03

External Links

GAS OPTIMIZATIONS

[G-01] Unused Imports

The following files were imported but were not used. These files would costs gas during deployment and generally this is a bad coding practice. I would recommend that the unused imports be removed

68 Instances

In the following files Ownable was imported with the statement import {Ownable} from "solady/auth/Ownable.sol"; but was not used. (15 Instances)

In the following files ERC20 was imported with the statement import {ERC20} from "solmate/tokens/ERC20.sol"; but was not used. (12 Instances)

In the following files SafeCastLib was imported with the statement import {SafeCastLib} from "solady/utils/SafeCastLib.sol"; but was not used. (4 Instances)

In the following files ERC20hTokenBranch was imported with the statement import {ERC20hTokenBranch as ERC20hToken} from "./token/ERC20hTokenBranch.sol"; but was not used. (4 Instances)

In the following files SafeTransferLib was imported with the statement import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; but was not used. (3 Instances)

In the following files IAnycallProxy was imported with the statement import {IAnycallProxy} from "../interfaces/IAnycallProxy.sol"; but was not used. (2 Instances)

In the following files IBaseV2Guage was imported with the statement import {IBaseV2Gauge} from "@gauges/interfaces/IBaseV2Gauge.sol" but was not used. (2 Instances)

In the following files IBranchRouter was imported with the statement import {IBranchRouter as IRouter} from "./interfaces/IBranchRouter.sol"; but was not used. (2 Instances)

In the following file Errors was imported with the statement import {Errors} from "./interfaces/Errors.sol"; but was not used. (1 Instance)

In the following file BaseV2Guage was imported with the statement import {BaseV2Gauge} from "@gauges/BaseV2Gauge.sol"; but was not used. (1 Instance)

In the following file bHermesVotes was imported with the statement import {bHermesVotes as vMaiaVotes} from "@hermes/tokens/bHermesVotes.sol"; but was not used. (1 Instance)

In the following file IRewardsDepot was imported with the statement import {RewardsDepot, IRewardsDepot} from "./RewardsDepot.sol"; but was not used. (1 Instance)

In the following file FlywheelCore was imported with the statement import {FlywheelCore} from "../base/FlywheelCore.sol"; but was not used. (1 Instance)

In the following file TalosStrategyStaked was imported with the statement import {DeployStaked, TalosStrategyStaked} from "../TalosStrategyStaked.sol"; but was not used. (1 Instance)

In the following file TalosStrategyVanilla was imported with the statement import {DeployVanilla, TalosStrategyVanilla} from "../TalosStrategyVanilla.sol"; but was not used. (1 Instance)

In the following file TalosManager was imported with the statement import {TalosManager} from "../TalosManager.sol"; but was not used. (1 Instance)

In the following file IUniswapV3Staker was imported with the statement import {IUniswapV3Staker} from "@v3-staker/interfaces/IUniswapV3Staker.sol"; but was not used. (1 Instance)

In the following file TalosOptimizer was imported with the statement import {OptimizerFactory, TalosOptimizer} from "../factories/OptimizerFactory.sol"; but was not used. (1 Instance)

In the following file PositionKey was imported with the statement import {PositionKey} from "@uniswap/v3-periphery/contracts/libraries/PositionKey.sol"; but was not used. (1 Instance)

In the following file MultiRewardsDepot was imported with the statement import {MultiRewardsDepot} from "@rewards/depots/MultiRewardsDepot.sol"; but was not used. (1 Instance)

In the following file CoreBranchRouter was imported with the statement import {CoreBranchRouter} from "../CoreBranchRouter.sol"; but was not used. (1 Instance)

In the following file IRootBridgeAgent was imported with the statement import {IRootBridgeAgent} from "../interfaces/IRootBridgeAgent.sol"; but was not used. (1 Instance)

In the following file AnycallFlags was imported with the statement import {AnycallFlags} from "./lib/AnycallFlags.sol"; but was not used. (1 Instance)

In the following file IAnycallConfig was imported with the statement import {IAnycallConfig} from "./interfaces/IAnycallConfig.sol"; but was not used. (1 Instance)

In the following file IAnycallExecutor was imported with the statement import {IAnycallExecutor} from "./interfaces/IAnycallExecutor.sol"; but was not used. (1 Instance)

In the following file ArbitrumBranchBridgeAgent was imported with the statement import {ArbitrumBranchBridgeAgent} from "./ArbitrumBranchBridgeAgent.sol"; but was not used. (1 Instance)

In the following file IERC20hTokenBranchFactory was imported with the statement import {IERC20hTokenBranchFactory as IFactory} from "./interfaces/IERC20hTokenBranchFactory.sol"; but was not used. (1 Instance)

In the following file ICoreBranchRouter was imported with the statement import {ICoreBranchRouter} from "./interfaces/ICoreBranchRouter.sol"; but was not used. (1 Instance)

In the following file IVirtualAccount, Call were imported with the statement import {IVirtualAccount, Call} from "./interfaces/IVirtualAccount.sol"; but were not used. (1 Instance)

In the following file ERC20hTokenRoot was imported with the statement import {ERC20hTokenRoot} from "./token/ERC20hTokenRoot.sol"; but was not used. (1 Instance)

In the following file IRootRouter was imported with the statement import {IRootRouter as IRouter} from "./interfaces/IRootRouter.sol"; but was not used. (1 Instance)

In the following file IRootBridgeAgentFactory was imported with the statement import {IRootBridgeAgentFactory} from "./interfaces/IRootBridgeAgentFactory.sol"; but was not used. (1 Instance)

[G-02] Sort Solidity operations using short-circuit mode

Short-circuiting is a solidity contract development model that uses OR/AND logic to sequence different cost operations. It puts low gas cost operations in the front and high gas cost operations in the back, so that if the front is low If the cost operation is feasible, you can skip (short-circuit) the subsequent high-cost Ethereum virtual machine operation.

f(x) is a low gas cost operation

g(y) is a high gas cost operation

Sort operations with different gas costs as follows

f(x) || g(y)

f(x) && g(y)

5 Instances

 require(
        msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending admin only"
);

in msg.sender == pendingAdmin SLOAD (cold access) operation is performed it would be better if msg.sender != address(0) was witten first in the require statement so that in scenarios where it results to false the EVM would not need to read from storage thereby saving 2100 gas units.

The code should be refactored to:

require(
             msg.sender != address(0) && msg.sender == pendingAdmin, "GovernorBravo:_acceptAdmin: pending admin only"
);
require(
            (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender))
                || (msg.sender != userAddress),
            "GovernorBravo::propose: proposer votes below proposal threshold"
);

in the (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) of the require statement the EVM would perform more operations than it would perform in the (msg.sender != userAddress). It would be better if the EVM performs (msg.sender != userAddress) operations before (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) operations so that in senarios where (msg.sender != userAddress) results to true the gas consuming operations of (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) would be avoided.

The code should be refactored to:

require(
        (msg.sender != userAddress) || (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)),
            "GovernorBravo::propose: proposer votes below proposal threshold"
);
if (destinationIds[address(destination)] != 0 || destinationId == id) 
    revert InvalidPool();

In the destinationIds[address(destination)] of the if statement the EVM would perform an SLOAD (cold access) operation which cost 2100 gas units and is more gas consuming than the destinationId == id which just compares an immutable and a stack variable. It would be better if destinationId == id operations is performed before destinationIds[address(destination)] so that in scenarios where destinationId == id results to true the EVM would not need to read the storage for destinationIds[address(destination)] thereby saving 2100 gas.

The code should be refactored to:

if (destinationId == id || destinationIds[address(destination)] != 0) 
    revert InvalidPool();
if ((isNotRestake || block.timestamp < endTime) || owner != msg.sender)

In the (isNotRestake || block.timestamp < endTime) of the if statement the EVM would perform SLOAD (cold access), EQ and ISZERO operations which cost 2106 gas units and is more gas consuming than the owner != msg.sender which just compares a stack variable and an address literal. It would be better if owner != msg.sender operations is performed before (isNotRestake || block.timestamp < endTime) so that in scenarios where owner != msg.sender results to true the EVM would not need to perform operations for (isNotRestake || block.timestamp < endTime) thereby saving 2106 gas.

The code should be refactored to:

if (owner != msg.sender || (isNotRestake || block.timestamp < endTime))
 if ((!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) && (msg.sender != userAddress)) {
            revert UnauthorizedCaller();
}

in the (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) of the if statement the EVM would perform more operations than it would perform in the (msg.sender != userAddress). It would be better if the EVM performs (msg.sender != userAddress) operations before (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) operations so that in senarios where (msg.sender != userAddress) results to false the gas consuming operations of (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) would be avoided.

The code should be refactored to:

 if ((msg.sender != userAddress) && (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender))) {
            revert UnauthorizedCaller();
}

[G-03] Making constant variables private will save gas during deployment.

When constants are marked internal extra getter functions are created increasing the deployment cost. One can still read these variables through the source code. In order to eliminate the extra deployment gas cost introduced by these internal constant variables there is a number of mitigating steps we can use depending on how they are to be used. If they need to be accessed by an external contract a separate single getter function can be used to return all constants as a tuple or if they are to be inherited they can be made and if they are not needed by external contracts or inherited then they should be made private.

3 Instances (Gas Saved: 124153)

 bytes32 public constant DELEGATION_TYPEHASH =
        keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

should be refactored to:

 bytes32 private constant DELEGATION_TYPEHASH =
        keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

Test Results

| Methods │ ·············|··························|··············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·············|··························|··············|·············|·············|···············|·············· | GasTest · createPrivateConstants · - · - · 66028 · 1 · - │ ·············|··························|··············|·············|·············|···············|·············· | GasTest · createPublicConstants · - · - · 95251 · 1 · - │ ·············|··························|··············|·············|·············|···············|·············· | Deployments · · % of limit · │ ········································|··············|·············|·············|···············|··············
    string public constant name = "vMaia Governor Bravo";

    /// @notice The minimum setable proposal threshold
    uint256 public constant MIN_PROPOSAL_THRESHOLD = 0.005 ether; // 0.5% of GovToken

    /// @notice The maximum setable proposal threshold
    uint256 public constant MAX_PROPOSAL_THRESHOLD = 0.05 ether; // 5% of GovToken

    /// @notice The minimum setable voting period
    uint256 public constant MIN_VOTING_PERIOD = 80640; // About 2 weeks

    /// @notice The max setable voting period
    uint256 public constant MAX_VOTING_PERIOD = 161280; // About 4 weeks

    /// @notice The min setable voting delay
    uint256 public constant MIN_VOTING_DELAY = 40320; // About 1 weeks

    /// @notice The max setable voting delay
    uint256 public constant MAX_VOTING_DELAY = 80640; // About 2 weeks

    /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
    uint256 public constant quorumVotes = 0.35 ether; // 35% of GovToken

    /// @notice The maximum number of actions that can be included in a proposal
    uint256 public constant proposalMaxOperations = 10; // 10 actions

    /// @notice The divisor value used in percentage calculations
    uint256 public constant DIVISIONER = 1 ether;

should be refactored to:

    string internal constant name = "vMaia Governor Bravo";

    /// @notice The minimum setable proposal threshold
    uint256 internal constant MIN_PROPOSAL_THRESHOLD = 0.005 ether; // 0.5% of GovToken

    /// @notice The maximum setable proposal threshold
    uint256 internal constant MAX_PROPOSAL_THRESHOLD = 0.05 ether; // 5% of GovToken

    /// @notice The minimum setable voting period
    uint256 internal constant MIN_VOTING_PERIOD = 80640; // About 2 weeks

    /// @notice The max setable voting period
    uint256 internal constant MAX_VOTING_PERIOD = 161280; // About 4 weeks

    /// @notice The min setable voting delay
    uint256 internal constant MIN_VOTING_DELAY = 40320; // About 1 weeks

    /// @notice The max setable voting delay
    uint256 internal constant MAX_VOTING_DELAY = 80640; // About 2 weeks

    /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
    uint256 internal constant quorumVotes = 0.35 ether; // 35% of GovToken

    /// @notice The maximum number of actions that can be included in a proposal
    uint256 internal constant proposalMaxOperations = 10; // 10 actions

    /// @notice The divisor value used in percentage calculations
    uint256 internal constant DIVISIONER = 1 ether;

    function getConstants() public returns(
        string memory, 
        uint256,
        uint256,
        uint256,
        uint256,
        uint256,
        uint256,
        uint256,
        uint256,
        uint256
    ){
        return(name, MIN_PROPOSAL_THRESHOLD, MAX_PROPOSAL_THRESHOLD, MIN_VOTING_PERIOD, MAX_VOTING_PERIOD, MIN_VOTING_DELAY, MAX_VOTING_DELAY, quorumVotes, proposalMaxOperations, DIVISIONER)
    }

Test Results

·······································|····························|·············|······························ | Methods │ ·············|··························|··············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·············|··························|··············|·············|·············|···············|·············· | GasTest · createInternaConstants · - · - · 178878 · 1 · - │ ·············|··························|··············|·············|·············|···············|·············· | GasTest · createPublicConstants · - · - · 230148 · 1 · - │ ·············|··························|··············|·············|·············|···············|·············· | Deployments
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); /// @notice The EIP-712 typehash for the ballot struct used by the contract bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");

Should be refactored to:

bytes32 private constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); /// @notice The EIP-712 typehash for the ballot struct used by the contract bytes32 private constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");

Test Result:

········································|····························|·············|······························ | Methods │ ·············|··························|··············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·············|··························|··············|·············|·············|···············|·············· | GasTest · createPrivateConstants · - · - · 66028 · 1 · - │ ·············|··························|··············|·············|·············|···············|·············· | GasTest · createPublicConstants · - · - · 109679 · 1 · - │ ·············|··························|··············|·············|·············|···············|·············· | Deployments · · % of limit · │

[G-04] Move lesser gas costing require checks to the top

Require() or revert() statements that check input arguments or cost lesser gas should be at the top of the function. Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting alot of gas in a function that may ultimately revert in the unhappy case.

Instances

[G-05] State variables should be cached

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.

[G-06] Non efficient zero initialization

Solidity does not recognize null as a value, so uint variables are initialized to zero. Setting a uint variable to zero is redundant and can waste gas. In the code below there is no need setting the amount variable in the else block to zero because the default value of a uint256 variable is zero

function getAccruedRewards(ERC20 strategy) external override onlyFlywheel returns (uint256 amount) {
        uint32 timestamp = block.timestamp.toUint32();

        // if cycle has ended, reset cycle and transfer all available
        if (timestamp >= endCycle) {
            amount = getNextCycleRewards(strategy);

            // reset for next cycle
            uint256 newEndCycle = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength;
            endCycle = newEndCycle;

            emit NewRewardsCycle(timestamp, newEndCycle, amount);
        } else {
            amount = 0;
        }
    }

This code could be refactored to:

function getAccruedRewards(ERC20 strategy) external override onlyFlywheel returns (uint256 amount) {
        uint32 timestamp = block.timestamp.toUint32();

        // if cycle has ended, reset cycle and transfer all available
        if (timestamp >= endCycle) {
            amount = getNextCycleRewards(strategy);

            // reset for next cycle
            uint256 newEndCycle = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength;
            endCycle = newEndCycle;

            emit NewRewardsCycle(timestamp, newEndCycle, amount);
        } else {
            amount;
        }
    }

Please note that in the automated findings report this instance was not included.

#0 - c4-judge

2023-07-11T15:09:13Z

trust1995 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