Maia DAO Ecosystem - Sathish9098'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: 31/101

Findings: 2

Award: $1,413.76

QA:
grade-a
Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

803.432 USDC - $803.43

Labels

bug
grade-a
QA (Quality Assurance)
sponsor confirmed
edited-by-warden
Q-15

External Links

LOW FINDINGS

Low CountIssuesInstances
[L-1]Lack of CEI (Check-Effects-Interactions) might be susceptible to a reentrancy attack3
[L-2]address(getUlyssesLP(routes[0].from).asset()).safeTransferFrom(msg.sender, address(this), amount) only transfer first value1
[L-3]Incorrect Handling of Negative Values in uniswapV3SwapCallback() Function2
[L-4]addPartner() and addVault() Functions Lack Address Existence Check in partners and vaults Arrays1
[L-5]Invalid getUserGaugeBoost[user][msg.sender] when totalSupply is Zero1
[L-6]Unchecked Subtraction of uint32 from uint in incrementFreezeWindow() Function May Lead to Unexpected Results1
[L-7]decimals() not part of ERC20 standard1
[L-8]Potential Array Index Out of Bounds Exception Due to Missing Length Check in assets and assetsAmounts Arrays1
[L-9]Missing Token Deposit Limit in the deposit() Function1
[L-10]distribute() Function Lacks Gauge Identification for Incentive Creation1
[L-11]Default Strategy in addStrategyForRewards() Function May Not Suit the Gauge1
[L-12]Inability to Remove Flywheel in createBribeFlywheel Function Poses Limitation1
[L-13]Tautology when checking msg.sender1
[L-14]Ensuring Proper Verification of Callee Address in Constructor to Avoid Expected Revert1
[L-15]Timelocks not implemented as per documentation-
[L-16]Unexpected Results when Converting Negative Values using uint256(int256)2
[L-17]Lack of Pool, token Removal Mechanism in UlyssesFactory Contract1
[L-18]Update depositNonce Value Incrementation in every Function Calls3
[L-19]Unused/empty fallback() function2
[L-20]withdraw() should use lock modifier to avoid reentrancy attack1
[L-21]Non-zero approval may be problems for some tokens like USDT1
[L-22]Contract existence of target[i] not checked before low level .call function1
[L-23]Gas griefing/theft is possible on unsafe external call1
[L-24]Use .call instead of .transfer to send ether1
[L-25]Return values of transfer()/transferFrom() not checked2

NON CRITICAL FINDINGS

NC CountIssuesInstances
[NC-1]Add an event for critical parameter changes4
[NC-2]Function does not followed the standard solidity naming1
[NC-3]No same value input control4
[NC-4]Status of approve() functions not checked2
[NC-5]Rectify all warnings to avoid unexpected behavior-

[L-1] Lack of CEI (Check-Effects-Interactions) might be susceptible to a reentrancy attack

Impact

Token withdrawal is performed by calling the withdraw function of the IPortStrategy contract with the specified parameters. Following that, the getPortStrategyTokenDebt and getStrategyTokenDebt mappings are updated by subtracting the amountToWithdraw from their respective values.it might be susceptible to a reentrancy attack. In a reentrancy attack, an external contract can maliciously call back into the contract during the execution of the withdrawal process, potentially leading to unexpected behavior and security vulnerabilities.

POC

FILE: 2023-05-maia/src/ulysses-omnichain/BranchPort.sol

180: IPortStrategy(_strategy).withdraw(address(this), _token, amountToWithdraw);
181:
182:        getPortStrategyTokenDebt[_strategy][_token] -= amountToWithdraw;
183:        getStrategyTokenDebt[_token] -= amountToWithdraw;

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchPort.sol#L180-L183

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchPort.sol#L248-L250

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchPort.sol#L276-L277

CEI pattern could involve updating the necessary state variables, such as getPortStrategyTokenDebt and getStrategyTokenDebt, before invoking the withdraw function of the IPortStrategy contract

FILE: 2023-05-maia/src/ulysses-omnichain/BranchPort.sol

+ 182:        getPortStrategyTokenDebt[_strategy][_token] -= amountToWithdraw;
+ 183:        getStrategyTokenDebt[_token] -= amountToWithdraw;
180: IPortStrategy(_strategy).withdraw(address(this), _token, amountToWithdraw);
181:
- 182:        getPortStrategyTokenDebt[_strategy][_token] -= amountToWithdraw;
- 183:        getStrategyTokenDebt[_token] -= amountToWithdraw;

[L-2] address(getUlyssesLP(routes[0].from).asset()).safeTransferFrom(msg.sender, address(this), amount) only transfer first value

Impact

The swap function you provided has a transfer statement that only transfers the amount from msg.sender to the contract address for routes[0].from. This means that it transfers the amount for only the first element in the routes array, and subsequent elements are not accounted for in the transfer.

POC

FILE: 2023-05-maia/src/ulysses-amm/UlyssesRouter.sol

function swap(uint256 amount, uint256 minOutput, Route[] calldata routes) external returns (uint256) {
        address(getUlyssesLP(routes[0].from).asset()).safeTransferFrom(msg.sender, address(this), amount);

        uint256 length = routes.length;

        for (uint256 i = 0; i < length;) {
            amount = getUlyssesLP(routes[i].from).swapIn(amount, routes[i].to);

            unchecked {
                ++i;
            }
        }

        if (amount < minOutput) revert OutputTooLow();

        unchecked {
            --length;
        }

        address(getUlyssesLP(routes[length].to).asset()).safeTransfer(msg.sender, amount);

        return amount;
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/UlyssesRouter.sol#L73-L96

Swap function that incorporates the loop to transfer amounts for all elements in the routes array.


for (uint256 i = 0; i < routes.length; i++) {
        address(getUlyssesLP(routes[i].from).asset()).safeTransferFrom(msg.sender, address(this), amount);
    }

[L-3] Incorrect Handling of Negative Values in uniswapV3SwapCallback() Function

Impact

The function uniswapV3SwapCallback() does not handle negative values in the amount0 and amount1 parameters in the right way.

The function checks if the value of amount0 or amount1 is equal to zero, and if it is, it reverts. However, this does not prevent the function from being called with negative values.

As per docs the function should check if the value of amount0 or amount1 is less than zero, and if it is, it should send the tokens to the pool. If the value is greater than zero, it should receive the tokens from the pool

POC

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

function uniswapV3SwapCallback(int256 amount0, int256 amount1, bytes calldata _data) external {
        if (msg.sender != address(pool)) revert CallerIsNotPool();
        if (amount0 == 0 && amount1 == 0) revert AmountsAreZero();
        SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData));
        bool zeroForOne = data.zeroForOne;

        if (zeroForOne) address(token0).safeTransfer(msg.sender, uint256(amount0));
        else address(token1).safeTransfer(msg.sender, uint256(amount1));
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/talos/base/TalosBaseStrategy.sol#L333-L341

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L646-L652

Need to implement controls to handle negative values also

[L-4] addPartner() and addVault() Functions Lack Address Existence Check in partners and vaults Arrays

In Solidity, arrays can accept duplicate values. There are no inherent restrictions or limitations on storing duplicate values in arrays.

Impact

Create confusion and uncertainty about which PartnerManager, IBaseVault accounts is the legitimate one.This could make it difficult for users to interact with the contract or for partners to coordinate their activities

Increase the risk of errors and inconsistencies. If multiple PartnerManager,IBaseVault accounts are able to approve or deny transactions, it could be difficult to track which account has approved or denied a particular transaction. This could lead to errors and inconsistencies in the contract's state.

POC

FILE: 2023-05-maia/src/maia/factories/PartnerManagerFactory.sol

function addPartner(PartnerManager newPartnerManager) external onlyOwner {
        uint256 id = partners.length;
        partners.push(newPartnerManager);
        partnerIds[newPartnerManager] == id;

        emit AddedPartner(newPartnerManager, id);
    }

function addVault(IBaseVault newVault) external onlyOwner {
        uint256 id = vaults.length;
        vaults.push(newVault);
        vaultIds[newVault] == id;

        emit AddedVault(newVault, id);
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/maia/factories/PartnerManagerFactory.sol#L58-L64

This code first checks if the newPartnerManager,newVault addresses already exists in the partners, vaults arrays

```solidity if (partners[partnerIds[partnerManager]] == newPartnerManager) revert InvalidPartnerManager(); if (vaults[vaultIds[vault]] == newVault) revert InvalidVault();

[L-5] Invalid getUserGaugeBoost[user][msg.sender] when totalSupply is Zero

Impact

The function getUserGaugeBoost[user][msg.sender] will always be invalid if the total supply of the gauge is zero. This is because the totalGaugeBoost field in the GaugeState struct is initialized with the total supply of the gauge. If the total supply is zero, then the totalGaugeBoost field will also be zero, which will make the getUserGaugeBoost[user][msg.sender] value invalid

POC

FILE: Breadcrumbs2023-05-maia/src/erc-20/ERC20Boost.sol

131: getUserGaugeBoost[user][msg.sender] =
132:            GaugeState({userGaugeBoost: userGaugeBoost, totalGaugeBoost: totalSupply.toUint128()});

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-20/ERC20Boost.sol#L131-L132

The function could be modified to check if the total supply of the gauge is greater than zero before initializing the totalGaugeBoost field. This would prevent the getUserGaugeBoost[user][msg.sender] value from being invalid


if (totalSupply > 0) {
        getUserGaugeBoost[user][msg.sender] =
            GaugeState({userGaugeBoost: userGaugeBoost, totalGaugeBoost: totalSupply.toUint128()});
    }

[L-6] Unchecked Subtraction of uint32 from uint in incrementFreezeWindow() Function May Lead to Unexpected Results

Impact

The function unchecked { if (cycle - block.timestamp <= incrementFreezeWindow) revert IncrementFreezeError(); } in that unchecked keyword tells the compiler to not check for overflows. This means that if the subtraction of cycle and block.timestamp overflows, the function will not revert.

This may be not problem now but may cause problems in the future .

FILE: 2023-05-maia/src/erc-20/ERC20Gauges.sol

204:  unchecked {
205:            if (cycle - block.timestamp <= incrementFreezeWindow) revert IncrementFreezeError();
206:        }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-20/ERC20Gauges.sol#L204-L206

Remove unchecked from cycle - block.timestamp subtraction operation. If any overflow/Underflows this will revert

[L-7] decimals() not part of ERC20 standard

Impact

decimals() is not part of the official ERC20 standard and might fail for tokens that do not implement it. While in practice it is very unlikely, as usually most of the tokens implement it, this should still be considered as a potential issue.

POC

FILE: Breadcrumbs2023-05-maia/src/erc-4626/ERC4626MultiToken.sol

51: require(ERC20(_assets[i]).decimals() == 18);

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-4626/ERC4626MultiToken.sol#L51

This code is more robust than the original code because it will not fail if the token does not implement the decimals() function.

if (erc20(_assets[i]).supportsInterface(IERC20Decimals(1))) {
  require(erc20(_assets[i]).decimals() == 18);
} else {
  revert('Token does not implement IERC20Decimals');
}

This code will revert if the token does not support the IERC20Decimals interface or if the decimals property of the token is not equal to 18. This ensures that the token will always have the correct decimals property.

[L-8] Potential Array Index Out of Bounds Exception Due to Missing Length Check in assets and assetsAmounts Arrays

Impact

The issue is that the assets[i] variable is a state variable, while the assetsAmounts variable is a user-defined array. This means that there is a chance that the two variables could be mismatched. If the two variables are mismatched, then the safeTransferFrom function could revert.

If the two variables length are mismatched, then an array index out of bound exception may occur

POC

FILE: 2023-05-maia/src/erc-4626/ERC4626MultiToken.sol

66: function receiveAssets(uint256[] memory assetsAmounts) private {
67:        uint256 length = assetsAmounts.length;
68:        for (uint256 i = 0; i < length;) {
69:            assets[i].safeTransferFrom(msg.sender, address(this), assetsAmounts[i]);
70:
71:            unchecked {
72:                i++;
73:            }

Add the length check


require(assets.length==assetsAmounts.length, "The length mismatched");

[L-9] Missing Token Deposit Limit in the deposit() Function

Impact

The deposit() function does not check to see if the total amount of tokens that the user is trying to deposit is greater than the total supply of tokens in the contract.

If an attacker were to deposit a large amount of tokens, it could cause the contract to run out of funds. This could also cause the contract to become unstable.

POC

FILE: Breadcrumbs2023-05-maia/src/erc-4626/ERC4626MultiToken.sol

function deposit(uint256[] calldata assetsAmounts, address receiver)
        public
        virtual
        nonReentrant
        returns (uint256 shares)
    {
        // Check for rounding error since we round down in previewDeposit.
        require((shares = previewDeposit(assetsAmounts)) != 0, "ZERO_SHARES");

        // Need to transfer before minting or ERC777s could reenter.
        receiveAssets(assetsAmounts);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assetsAmounts, shares);

        afterDeposit(assetsAmounts, shares);
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-4626/ERC4626MultiToken.sol#L93-L110

You could add a limit to the amount of tokens that can be deposited


require(shares <= maxSharesAllowed, "INSUFFICIENT_BALANCE");

[L-10] distribute() Function Lacks Gauge Identification for Incentive Creation

Impact

The function distribute is not safe. The attacker could call the function and create an incentive from a gauge that they control. This could allow the attacker to steal the incentive funds.

POC

FILE: 2023-05-maia/src/gauges/UniswapV3Gauge.sol

53: function distribute(uint256 amount) internal override {
54:        IUniswapV3Staker(uniswapV3Staker).createIncentiveFromGauge(amount);
55:    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/gauges/UniswapV3Gauge.sol#L53C60-L55

If the gauge parameter is not null, then the function will call the createIncentiveFromGauge function of the IUniswapV3Staker contract. The createIncentiveFromGauge function will create an incentive from the specified gauge and deposit the incentive funds into the contract.

This code will make the distribute function more secure and will prevent an attacker from stealing the incentive funds.


function distribute(uint256 amount, address gauge) internal override {
    IUniswapV3Staker(uniswapV3Staker).createIncentiveFromGauge(amount, gauge);
}

[L-11] Default Strategy in addStrategyForRewards() Function May Not Suit the Gauge

Impact

Using a default strategy that is not appropriate for the gauge could be significant. For example, if the default strategy is designed for a different type of gauge, it could result in the incentive funds being distributed in an incorrect manner. This could lead to a number of problems,

  • The incentive funds could be distributed to the wrong parties.
  • The incentive funds could be distributed in an unfair manner.
  • The incentive funds could be used for malicious purposes.

POC

FILE: 2023-05-maia/src/gauges/factories/BribesFactory.sol

function addGaugetoFlywheel(address gauge, address bribeToken) external onlyGaugeFactory {
        if (address(flywheelTokens[bribeToken]) == address(0)) createBribeFlywheel(bribeToken);

        flywheelTokens[bribeToken].addStrategyForRewards(ERC20(gauge));
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/gauges/factories/BribesFactory.sol#L72-L76

You could modify the function to allow the caller to specify the strategy that they want to add to the flywheel

[L-12] Inability to Remove Flywheel in createBribeFlywheel Function Poses Limitation

Impact

This could be a problem if the flywheel is no longer needed or if it is no longer being used.

POC

FILE: 2023-05-maia/src/gauges/factories/BribesFactory.sol

 function createBribeFlywheel(address bribeToken) public {
        if (address(flywheelTokens[bribeToken]) != address(0)) revert BribeFlywheelAlreadyExists();

        FlywheelCore flywheel = new FlywheelCore(
            bribeToken,
            FlywheelBribeRewards(address(0)),
            flywheelGaugeWeightBooster,
            address(this)
        );

        flywheelTokens[bribeToken] = flywheel;

        uint256 id = bribeFlywheels.length;
        bribeFlywheels.push(flywheel);
        bribeFlywheelIds[flywheel] = id;
        activeBribeFlywheels[flywheel] = true;

        flywheel.setFlywheelRewards(address(new FlywheelBribeRewards(flywheel, rewardsCycleLength)));

        emit BribeFlywheelCreated(bribeToken, flywheel);
    }

Implement deleteBribeFlywheel() function to remove flywheel is no longer needed or if it is no longer being used.

[L-13] Tautology when checking msg.sender

Impact

While taking into account the address(0) output of msg.sender is something that must always be done: in this particular case the check is a tautology

Indeed:

  • if msg.sender == pendingAdmin is true, then msg.sender != address(0) will always be true
  • if msg.sender == pendingAdmin is false, then msg.sender != address(0) will never be evaluated

POC

FILE: 2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

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

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L507-L509

[L-14] Ensuring Proper Verification of Callee Address in Constructor to Avoid Expected Revert

Impact

delegateTo function is called in the constructor, it means that it is being invoked during the contract deployment process. In that case, it is important to ensure that the callee address passed to the function is indeed a valid contract address.

POC

FILE: Breadcrumbs2023-05-maia/src/governance/GovernorBravoDelegator.sol


 constructor(
        address timelock_,
        address govToken_,
        address admin_,
        address implementation_,
        uint256 votingPeriod_,
        uint256 votingDelay_,
        uint256 proposalThreshold_
    ) public {
        // Admin set to msg.sender for initialization
        admin = msg.sender;

        delegateTo(
            implementation_,
            abi.encodeWithSignature(
                "initialize(address,address,uint256,uint256,uint256)",
                timelock_,
                govToken_,
                votingPeriod_,
                votingDelay_,
                proposalThreshold_
            )
        );

        _setImplementation(implementation_);

        admin = admin_;
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegator.sol#L20

Include the validation before calling the delegateTo

require(Address.isContract(callee), "callee is not a contract"); // Check if callee is a contract

[L-15] Timelocks not implemented as per documentation

``Does it use a timelock function?: true``

Impact

The documentation for the contract states that there are timelocks in place to prevent users from withdrawing their collateral asset immediately after depositing it. However, the code for the contract does not actually implement any timelocks.

This is a potential security vulnerability, as it could allow a user to withdraw their collateral asset immediately after depositing it, even if the collateral asset has not yet reached the required level of liquidity.

Add the timelocks for critical setter functions as per documentation

[L-16] Unexpected Results when Converting Negative Values using uint256(int256)

Impact

When converting a negative int256 value to a uint256 using uint256(int256), the result will be unexpected. if you try to convert a negative int256 value to a uint256, the result will be interpreted as the two's complement representation of the negative value. This means that the resulting uint256 value will be a large positive number.

PROOF: We attempt to convert the negative value -10 to a uint256. However, the resulting value will not be the expected value of -10 but rather a large positive number.

If you execute the function, the returned value will be 115792089237316195423570985008687907853269984665640564039457584007913129639934. This result corresponds to the two's complement representation of -10 when interpreted as an unsigned uint256.

POC

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

339: if (zeroForOne) address(token0).safeTransfer(msg.sender, uint256(amount0));
340: else address(token1).safeTransfer(msg.sender, uint256(amount1));

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/talos/base/TalosBaseStrategy.sol#L339-L340

Carefully handle conversions between signed and unsigned integer types and ensure that the values are within the valid ranges of the target type.

[L-17] Lack of Pool, token Removal Mechanism in UlyssesFactory Contract

Impact

Typically, in decentralized exchange protocols, such as the Uniswap protocol, there is a mechanism to remove pools that are no longer desired or have become inactive. This removal mechanism is important for several reasons, including reducing clutter and maintaining an up-to-date list of active and relevant pools.

Without a pool removal mechanism in the UlyssesFactory contract, inactive or unwanted pools may persist indefinitely, leading to a cluttered pool list. This could potentially cause confusion for users and impact the overall efficiency of the protocol.

POC

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/factories/UlyssesFactory.sol#L38

Consider implementing a mechanism within the UlyssesFactory contract that allows for the removal of pools,tokens

[L-18] Update depositNonce Value Incrementation in every Function Calls

Impact

The function uses the depositNonce variable to encode the data for the cross-chain call. However, the depositNonce variable always returns the current value, not the incremented value. This means that if the function is called multiple times, the same nonce will be used for each call. This could lead to a replay attack

POC

FILE: 2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol

226: bytes memory packedData = abi.encodePacked(
227:            bytes1(0x04), msg.sender, depositNonce, _params, msg.value.toUint128(), _remoteExecutionGas
228:        );

248:    depositNonce,

bytes memory packedData = abi.encodePacked(
            bytes1(0x06),
            msg.sender,
            uint8(_dParams.hTokens.length),
            depositNonce,
            _dParams.hTokens,
            _dParams.tokens,
            _dParams.amounts,
            _deposits,
            _dParams.toChain,
            _params,
            msg.value.toUint128(),
            _remoteExecutionGas
        );

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchBridgeAgent.sol#L226-L228

Ensures that a different nonce is used for each call


depositNonce++

[L-19] Unused/empty fallback() function

Impact

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert. Having no access control on the function means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds.

POC

FILE: 2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol

1419: fallback() external payable {}

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchBridgeAgent.sol#L1419

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L1334

[L-20] withdraw() should use lock modifier to avoid reentrancy attack

Impact

The function withdraw() should use the lock modifier to avoid reentrancy attacks. The lock modifier will prevent the contract from being called again while it is still executing. This will help to protect the contract from being exploited by an attacker.

POC

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/BranchPort.sol

function withdraw(address _recipient, address _underlyingAddress, uint256 _deposit)
        external
        virtual
        requiresBridgeAgent


 modifier lock() {
        require(_unlocked == 1);
        _unlocked = 2;
        _;
        _unlocked = 1;
    }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchPort.sol#L206-L209

Use lock modifier for withdraw() function

[L-21] Non-zero approval may be problems for some tokens like USDT

// Addresses of the output hTokens this tokens can be anything

Impact

There are some tokens like USDT did not allow to approve allowance from non-zero value to non-zero value.If you try to approve another non-zero value when it's already had allowance, TX will fail.

POC

FILE: 2023-05-maia/src/ulysses-omnichain/BranchPort.sol

120: ERC20hTokenRoot(outputToken).approve(bridgeAgentAddress, amountOut);

148: ERC20hTokenRoot(outputTokens[i]).approve(bridgeAgentAddress, amountsOut[i]);

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/MulticallRootRouter.sol#L148

Add an approve(0) before approving

[L-22] Contract existence of target[i] not checked before low level .call function

Impact

The existence of target[i] is not checked before making the .call function. This could potentially lead to issues if target[i] is not a valid contract address. If11 target[i]11 does not point to a valid contract, the .call function may revert, resulting in a failed transaction.

POC

FILE: 2023-05-maia/src/ulysses-omnichain/VirtualAccount.sol

49:(bool success, bytes memory data) = calls[i].target.call(calls[i].callData);

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/VirtualAccount.sol#L49

Check contract existence before .call

address targetAddress = calls[i].target;
require(Address.isContract(targetAddress), "Invalid contract address");

[L-23] Gas griefing/theft is possible on unsafe external call

Impact

https://twitter.com/pashovkrum/status/1607024043718316032?t=xs30iD6ORWtE2bTTYsCFIQ&s=19

If the external contract at calls[i].target is a malicious contract and specifically designed to consume excessive gas, it can manipulate the execution flow to consume more gas than necessary, resulting in a higher gas cost for your transaction. This gas cost cannot be fully recovered because the unused gas is not returned due to the malicious contract's behavior

POC

FILE: 2023-05-maia/src/ulysses-omnichain/VirtualAccount.sol

49: (bool success, bytes memory data) = calls[i].target.call(calls[i].callData);

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/VirtualAccount.sol#L49

Usage like below, ‘out’ and ‘outsize’ values are given (0,0) . Thus, this storage disappears and may come from external contracts a possible Gas griefing/theft problem is avoided.

[L-24] Use .call instead of .transfer to send ether

Impact

.transfer will relay 2300 gas and .call will relay all the gas. If the receive/fallback function from the recipient proxy contract has complex logic, using .transfer will fail, causing integration issues.

POC

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

409: if (amount0 > 0) _token0.transfer(msg.sender, amount0);
410: if (amount1 > 0) _token1.transfer(msg.sender, amount1);

Use .call instead of .transfer

[L-25] Return values of transfer()/transferFrom() not checked

Impact

If the return value of these functions is not checked, potential errors or failures during the token transfer can go unnoticed. This can lead to unexpected behavior and vulnerabilities in the contract

POC

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

409: if (amount0 > 0) _token0.transfer(msg.sender, amount0);
410: if (amount1 > 0) _token1.transfer(msg.sender, amount1);

Check the return value of transfer() and transferFrom() and handle any failed transfers appropriately

NON CRITICAL FINDINGS

[NC-1] Add an event for critical parameter changes

Adding events for critical parameter changes will facilitate offchain monitoring and indexing.

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L86-L89

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L92-L95

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L98-L101

[NC-2] Function does not followed the standard solidity naming

The external function names not start with _

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L397

[NC-3] No same value input control

The setter functions does not have any checks to prevent the same value from being passed. This means that an onlyOwner could call the function with the same address, which would effectively do nothing

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L86-L89

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L92-L95

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L98-L101

[NC-4] Status of approve() functions not checked

When using the approve() function to grant an allowance for a specific address to spend tokens on behalf of the sender, it is crucial to verify the return value of the function

FILE: 2023-05-maia/src/ulysses-omnichain/BranchPort.sol

120: ERC20hTokenRoot(outputToken).approve(bridgeAgentAddress, amountOut);

148: ERC20hTokenRoot(outputTokens[i]).approve(bridgeAgentAddress, amountsOut[i]);

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/MulticallRootRouter.sol#L148

[NC-5] Rectify all warnings to avoid unexpected behaviour

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/governance/GovernorBravoDelegator.sol:7:1: | 7 | contract GovernorBravoDelegator is GovernorBravoDelegatorStorage, GovernorBravoEvents { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/governance/GovernorBravoDelegator.sol:71:5: | 71 | fallback() external payable { | ^ (Relevant source part starts here and spans across multiple lines).

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/out-of-scope/governance/GovernorBravoDelegator.sol:6:1: | 6 | contract GovernorBravoDelegator is GovernorBravoDelegatorStorage, GovernorBravoEvents { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/out-of-scope/governance/GovernorBravoDelegator.sol:70:5: | 70 | fallback() external payable { | ^ (Relevant source part starts here and spans across multiple lines).

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/ulysses-omnichain/BranchBridgeAgent.sol:58:1: | 58 | contract BranchBridgeAgent is IBranchBridgeAgent { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1419:5: | 1419 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/ulysses-omnichain/ArbitrumBranchBridgeAgent.sol:67:1: | 67 | contract ArbitrumBranchBridgeAgent is BranchBridgeAgent { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1419:5: | 1419 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/ulysses-omnichain/CoreBranchRouter.sol:19:1: | 19 | contract CoreBranchRouter is BaseBranchRouter { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/CoreBranchRouter.sol:284:5: | 284 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/ulysses-omnichain/ArbitrumCoreBranchRouter.sol:37:1: | 37 | contract ArbitrumCoreBranchRouter is CoreBranchRouter { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/CoreBranchRouter.sol:284:5: | 284 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/ulysses-omnichain/RootBridgeAgent.sol:90:1: | 90 | contract RootBridgeAgent is IRootBridgeAgent { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/RootBridgeAgent.sol:1334:5: | 1334 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> test/ulysses-omnichain/mocks/MockRootBridgeAgent.t.sol:23:1: | 23 | contract MockRootBridgeAgent is RootBridgeAgent { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/RootBridgeAgent.sol:1334:5: | 1334 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> test/ulysses-omnichain/mocks/MockBranchBridgeAgent.t.sol:16:1: | 16 | contract MockBranchBridgeAgent is BranchBridgeAgent { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1419:5: | 1419 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> test/ulysses-omnichain/ArbitrumBranchTest.t.sol:42:1: | 42 | contract ArbitrumBranchTest is DSTestPlus { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> test/ulysses-omnichain/ArbitrumBranchTest.t.sol:43:5: | 43 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> test/ulysses-omnichain/BranchBridgeAgentTest.t.sol:21:1: | 21 | contract BranchBridgeAgentTest is Test { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> test/ulysses-omnichain/BranchBridgeAgentTest.t.sol:87:5: | 87 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> test/ulysses-omnichain/RootTest.t.sol:53:1: | 53 | contract RootTest is DSTestPlus { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> test/ulysses-omnichain/RootTest.t.sol:547:5: | 547 | fallback() external payable {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (2462): Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient. --> src/governance/GovernorBravoDelegator.sol:8:5: | 8 | constructor( | ^ (Relevant source part starts here and spans across multiple lines).

Warning (2462): Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient. --> src/out-of-scope/governance/GovernorBravoDelegator.sol:7:5: | 7 | constructor( | ^ (Relevant source part starts here and spans across multiple lines).

Warning (8417): Warning: Since the VM version paris, "difficulty" was replaced by "prevrandao", which now returns a random number based on the beacon chain. --> test/ulysses-omnichain/mocks/Multicall2.sol:49:22: | 49 | difficulty = block.difficulty; | ^^^^^^^^^^^^^^^^

Warning (5740): Warning: Unreachable code. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1037:13: | 1037 | return; | ^^^^^^^

Warning (5740): Warning: Unreachable code. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1052:13: | 1052 | return; | ^^^^^^^

Warning (5740): Warning: Unreachable code. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1071:13: | 1071 | return; | ^^^^^^^

Warning (5740): Warning: Unreachable code. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1151:17: | 1151 | return (true, "already executed tx"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (5740): Warning: Unreachable code. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1173:17: | 1173 | return (true, "already executed tx"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning (5740): Warning: Unreachable code. --> src/ulysses-omnichain/BranchBridgeAgent.sol:1197:17: | 1197 | return (true, "already executed tx"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

#0 - c4-judge

2023-07-07T10:12:07Z

trust1995 marked the issue as grade-b

#1 - c4-judge

2023-07-25T14:57:26Z

trust1995 marked the issue as grade-a

#3 - c4-sponsor

2023-09-14T14:35:48Z

0xBugsy (sponsor) confirmed

Awards

610.3258 USDC - $610.33

Labels

bug
G (Gas Optimization)
grade-a
sponsor confirmed
edited-by-warden
G-27

External Links

GAS OPTIMIZATIONS

Table Of Contents

Total approximate Gas Saved as per report - 65000 GAS

FINDINGS

NOTE: Some functions have been truncated where necessary to just show affected parts of the code Through out the report some places might be denoted with audit tags to show the actual place affected.

[G-1] Pack structs by putting variables that can fit together next to each other

Saves 10000 GAS, 5 SLOT

As the solidity EVM works with 32 bytes, variables less than 32 bytes should be packed inside a struct so that they can be stored in the same slot, this saves gas when writing to storage ~20000 gas

IBranchBridgeAgent.sol: hToken and toChain can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol#L26-L33

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol

26: struct DepositInput {
27:    //Deposit Info
28:    address hToken; //Input Local hTokens Address.
+ 32:    uint24 toChain; //Destination chain for interaction.
29:    address token; //Input Native / underlying Token Address.
30:    uint256 amount; //Amount of Local hTokens deposited for interaction.
31:    uint256 deposit; //Amount of native tokens deposited for interaction.
- 32:    uint24 toChain; //Destination chain for interaction.
33: }

IBranchBridgeAgent.sol: numberOfAssets,depositNonce,toChain,depositedGas can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol#L55-L65

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol

55: struct DepositMultipleParams {
56:    //Deposit Info
- 57:    uint8 numberOfAssets; //Number of assets to deposit.
- 58:    uint32 depositNonce; //Deposit nonce.
59:    address[] hTokens; //Input Local hTokens Address.
60:    address[] tokens; //Input Native / underlying Token Address.
61:    uint256[] amounts; //Amount of Local hTokens deposited for interaction.
62:    uint256[] deposits; //Amount of native tokens deposited for interaction.
63:    uint24 toChain; //Destination chain for interaction.
64:    uint128 depositedGas; //BRanch chain gas token amount sent with request.
+ 57:    uint8 numberOfAssets; //Number of assets to deposit.
+ 58:    uint32 depositNonce; //Deposit nonce.
65: }

IRootBridgeAgent.sol: depositNonce,hToken,toChain can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol#L63-L71

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol

63: struct DepositParams {
64:    //Deposit Info
65:    uint32 depositNonce; //Deposit nonce.
+ 70:    uint24 toChain; //Destination chain for interaction.
66:    address hToken; //Input Local hTokens Address.
67:    address token; //Input Native / underlying Token Address.
68:    uint256 amount; //Amount of Local hTokens deposited for interaction.
69:    uint256 deposit; //Amount of native tokens deposited for interaction.
- 70:    uint24 toChain; //Destination chain for interaction.
71: }

IRootBridgeAgent.sol: numberOfAssets,depositNonce,toChain can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol#L73-L82

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol

73: struct DepositMultipleParams {
74:    //Deposit Info
- 75:    uint8 numberOfAssets; //Number of assets to deposit.
- 76:    uint32 depositNonce; //Deposit nonce.
77:    address[] hTokens; //Input Local hTokens Address.
78:    address[] tokens; //Input Native / underlying Token Address.
79:    uint256[] amounts; //Amount of Local hTokens deposited for interaction.
80:    uint256[] deposits; //Amount of native tokens deposited for interaction.
+ 75:    uint8 numberOfAssets; //Number of assets to deposit.
+ 76:    uint32 depositNonce; //Deposit nonce.
81:    uint24 toChain; //Destination chain for interaction.
82: }

GovernorBravoInterfaces.sol#L105-L136: proposer,canceled,executed can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoInterfaces.sol#L105-L136

FILE: Breadcrumbs2023-05-maia/src/governance/GovernorBravoInterfaces.sol

105: struct Proposal {
106:        /// @notice Unique id for looking up a proposal
107:        uint256 id;
108:        /// @notice Creator of the proposal
109:        address proposer;
+ 130:        /// @notice Flag marking whether the proposal has been canceled
+ 131:        bool canceled;
+ 132:        /// @notice Flag marking whether the proposal has been executed
+ 133:        bool executed;
110:        /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
111:        uint256 eta;
112:        /// @notice the ordered list of target addresses for calls to be made
113:        address[] targets;
114:        /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
115:        uint256[] values;
116:        /// @notice The ordered list of function signatures to be called
117:        string[] signatures;
118:        /// @notice The ordered list of calldata to be passed to each call
119:        bytes[] calldatas;
120:        /// @notice The block at which voting begins: holders must delegate their votes prior to this block
121:        uint256 startBlock;
122:        /// @notice The block at which voting ends: votes must be cast prior to this block
123:        uint256 endBlock;
124:        /// @notice Current number of votes in favor of this proposal
125:        uint256 forVotes;
126:        /// @notice Current number of votes in opposition to this proposal
127:        uint256 againstVotes;
128:        /// @notice Current number of votes for abstaining for this proposal
129:        uint256 abstainVotes;
- 130:        /// @notice Flag marking whether the proposal has been canceled
- 131:        bool canceled;
- 132:        /// @notice Flag marking whether the proposal has been executed
- 133:        bool executed;
134:        /// @notice Receipts of ballots for the entire set of voters
135:        mapping(address => Receipt) receipts;
136:    }

[G-2] State variables can be packed to use fewer storage slots

Saves 16000 GAS, 8 SLOT

The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to each other in storage and this will pack the values together into a single 32 byte storage slot (if the values combined are <= 32 bytes). If the variables packed together are retrieved together in functions we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas)

RootBridgeAgent.sol: settlementNonce,accumulatedFees state variables can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT.

accumulatedFees mostly saves the value 1 the other values also not exceeds the 20000. So uint128 is more than enough.

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L164-L190

  • NOTE: The line numbers are skipped unwanted areas
FILE: 2023-05-maia/src/ulysses-omnichain/RootBridgeAgent.sol

- 160:    uint32 public settlementNonce;

    /// @notice Mapping from Settlement nonce to Deposit Struct.
    mapping(uint32 => Settlement) public getSettlement;

    /*///////////////////////////////////////////////////////////////
                            EXECUTOR STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice If true, bridge agent has already served a request with this nonce from  a given chain. Chain -> Nonce -> Bool
    mapping(uint256 => mapping(uint32 => bool)) public executionHistory;

    /*///////////////////////////////////////////////////////////////
                        GAS MANAGEMENT STATE
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MIN_FALLBACK_RESERVE = 155_000; // 100_000 for anycall + 55_000 for fallback
    uint256 internal constant MIN_EXECUTION_OVERHEAD = 155_000; // 100_000 for anycall + 30_000 Pre 1st Gas Checkpoint Execution + 25_000 Post last Gas Checkpoint Execution

    uint256 public initialGas;
    UserFeeInfo public userFeeInfo;

    /*///////////////////////////////////////////////////////////////
                        DAO STATE
    //////////////////////////////////////////////////////////////*/
+ 160:    uint32 public settlementNonce;
- 190:    uint256 public accumulatedFees;
+ 190:    uint128 public accumulatedFees;

RootBridgeAgent.sol: coreRootBridgeAgentAddress,bridgeAgentFactoriesLenght,_setup state variables can be packed together: Gas saved: 2 * 2k = 4k , 2 SLOT

bridgeAgentFactoriesLenght the type can be changed to uint64 instead of uint256. bridgeAgentFactoriesLenght this value is incremented only once in initialize() function. the value is not changed any where inside the contract

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootPort.sol#L83

  • NOTE: The line numbers are skipped unwanted areas
FILE: 2023-05-maia/src/ulysses-omnichain/RootPort.sol

- 40: address public coreRootBridgeAgentAddress;



+ 40: address public coreRootBridgeAgentAddress;
- 83: uint256 public bridgeAgentFactoriesLenght;
+ 126: bool internal _setup;
+ 83: uint64 public bridgeAgentFactoriesLenght;
- 126: bool internal _setup;

BaseV2Minter.sol: daoShare,dao and tailEmission,initializer state variables can be packed together: Gas saved: 2 * 2k = 4k , 2 SLOT

daoShare,tailEmission can be uint96 instead of uint256. Because _daoShare > max_dao_share,tail_emission > max_tail_emission checks. max_dao_share,max_tail_emission are constants and stores the value of max_dao_share=300,max_tail_emission=100. So daoShare,tailEmission can't store more than constants values.

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L42-L43

FILE: 2023-05-maia/src/hermes/minters/BaseV2Minter.sol

39: address public override dao;
+ 42:    uint96 public override daoShare = 100;
40:
41:    /// @inheritdoc IBaseV2Minter
- 42:    uint256 public override daoShare = 100;
- 43:    uint256 public override tailEmission = 20;
44:    /// @inheritdoc IBaseV2Minter
45:
46:    /// @inheritdoc IBaseV2Minter
47:    uint256 public override weekly;
48:    /// @inheritdoc IBaseV2Minter
49:    uint256 public override activePeriod;
50:
+ 43:    uint96 public override tailEmission = 20;
51:    address internal initializer;

ERC20hTokenRoot.sol: localChainId,rootPortAddress state variables can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

The uint96 type is a 96-bit unsigned integer, which is more than enough for the vast majority of chain IDs. Also other contracts only using uint24 for chainId . it is perfectly safe to change the uint256 public localChainId; declaration to uint96 public localChainId;. This will save some gas, and it will make the contract more efficient.

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/token/ERC20hTokenRoot.sol#L16C33-L28

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/token/ERC20hTokenRoot.sol

15:  /// @inheritdoc IERC20hTokenRoot
- 16:    uint256 public localChainId;
+ 16:    uint96 public localChainId;
17:
18:    /// @inheritdoc IERC20hTokenRoot
19:    address public rootPortAddress;
20:
21:    /// @inheritdoc IERC20hTokenRoot
22:    address public localBranchPortAddress;
23:
24:    /// @inheritdoc IERC20hTokenRoot
25:    address public factoryAddress;
26:
27:    /// @inheritdoc IERC20hTokenRoot
28:    mapping(uint256 => uint256) public getTokenBalance;

ERC20hTokenRootFactory.sol: coreRootRouterAddress,hTokensLenght state variables can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

hTokensLenght uint96 is more than enough to htokens length. Because all new tokens pushed to hTokens array.The array exceeds 1024 elements EVM reverts. So maximum hTokensLenght may be 1024

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/factories/ERC20hTokenRootFactory.sol#L22-L26

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/factories/ERC20hTokenRootFactory.sol

22:    address public coreRootRouterAddress;
+ 26:    uint96 public hTokensLenght;
23:
24:    ERC20hTokenRoot[] public hTokens;
25:
- 26:    uint256 public hTokensLenght;

vMaia.sol: currentMonth,unstakePeriodEnd state variables can be packed together: Gas saved: 1 * 2k = 2k , 1 SLOT

currentMonth only stores month values (0-12) and unstakePeriodEnd this stores timestamp. For currentMonth,unstakePeriodEnd uint128 is more than enough. So we can save 1 SLOT and 2000 GAS

uint128 alone stores more than 10000 years timestamp

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/maia/vMaia.sol#L34-L35

FILE: 2023-05-maia/src/maia/vMaia.sol

- 34: uint256 private currentMonth;
- 35:    uint256 private unstakePeriodEnd;
+ 34: uint128 private currentMonth;
+ 35:    uint128 private unstakePeriodEnd;

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

Saves 1900 GAS, 19 SLOD

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.

BaseV2Minter.sol: dao should be cached with local address stack variable : Saves 100 GAS,1 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/minters/BaseV2Minter.sol#L145

FILE: 2023-05-maia/src/hermes/minters/BaseV2Minter.sol

+ address _dao=dao ;
- 145: if (dao != address(0)) underlying.safeTransfer(dao, share);
+ 145: if (_dao != address(0)) underlying.safeTransfer(_dao, share);

ERC20Boost.sol: gaugeState.userGaugeBoost should be cached with local uint256 stack variable : Saves 400 GAS,4 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-20/ERC20Boost.sol#L177-L185

FILE: Breadcrumbs2023-05-maia/src/erc-20/ERC20Boost.sol

+ uint256 userGaugeBoost_=gaugeState.userGaugeBoost;
- 177: if (boost >= gaugeState.userGaugeBoost) {
+ 177: if (boost >= userGaugeBoost_) {
178:            _userGauges[msg.sender].remove(gauge);
179:            delete getUserGaugeBoost[msg.sender][gauge];
180:
181:            emit Detach(msg.sender, gauge);
182:        } else {
183:            gaugeState.userGaugeBoost -= boost.toUint128();
184:
- 185:            emit DecrementUserGaugeBoost(msg.sender, gauge, gaugeState.userGaugeBoost);
+ 185:            emit DecrementUserGaugeBoost(msg.sender, gauge, userGaugeBoost_);
186:        }




210:           GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
211:
+ uint256 userGaugeBoost_=gaugeState.userGaugeBoost;
- 212: if (_deprecatedGauges.contains(gauge) || boost >= gaugeState.userGaugeBoost) {
+ 212: if (_deprecatedGauges.contains(gauge) || boost >= userGaugeBoost_) {
213:                require(_userGauges[msg.sender].remove(gauge)); // Remove from set. Should never fail.
214:                delete getUserGaugeBoost[msg.sender][gauge];
215:
216:                emit Detach(msg.sender, gauge);
217:            } else {
218:                gaugeState.userGaugeBoost -= boost.toUint128();
219:
- 220:                emit DecrementUserGaugeBoost(msg.sender, gauge, gaugeState.userGaugeBoost);
+ 220:                emit DecrementUserGaugeBoost(msg.sender, gauge, userGaugeBoost_);
221:            }

RootBridgeAgent.sol: settlement.hTokens[i] ,settlement.toChain should be cached with local address and uint24 stack variable : Saves 200 GAS,2 SLOD per iterations

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L591-L612

FILE: 2023-05-maia/src/ulysses-omnichain/RootBridgeAgent.sol

596:for (uint256 i = 0; i < settlement.hTokens.length;) {
597:            //Check if asset
+        address hTokens_ = settlement.hTokens[i] ;
+        uint24  toChain_= settlement.toChain;
- 598:            if (settlement.hTokens[i] != address(0)) { //@audit settlement.hTokens[i] first SLOD
+ 598:            if (hTokens_ != address(0)) {
599:                //Move hTokens from Branch to Root + Mint Sufficient hTokens to match new port deposit
600:                IPort(localPortAddress).bridgeToRoot(
601:                    msg.sender,
- 602:                    IPort(localPortAddress).getGlobalTokenFromLocal(settlement.hTokens[i], settlement.toChain), //@audit settlement.hTokens[i] second SLOD , //@audit settlement.toChain first SLOD
+ 602:                    IPort(localPortAddress).getGlobalTokenFromLocal(hTokens_ , toChain_),
603:                    settlement.amounts[i],
604:                    settlement.deposits[i],
- 605:                    settlement.toChain  //@audit settlement.toChain second SLOD
+ 605:                    toChain_
606:                );
607:            }

RootBridgeAgent.sol: userFeeInfo.gasToBridgeOut should be cached with local uint128 stack variable : Saves 200 GAS,2 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L746-L759

FILE: 2023-05-maia/src/ulysses-omnichain/RootBridgeAgent.sol

746: uint256 _initialGas = initialGas;
+ uint128 _gasToBridgeOut = userFeeInfo.gasToBridgeOut ;
747:
748:        if (_toChain == localChainId) {
749:            //Transfer gasToBridgeOut Local Branch Bridge Agent if remote initiated call.
750:            if (_initialGas > 0) {
- 751:                address(wrappedNativeToken).safeTransfer(getBranchBridgeAgent[localChainId], userFeeInfo.gasToBridgeOut);
+ 751:                address(wrappedNativeToken).safeTransfer(getBranchBridgeAgent[localChainId],_gasToBridgeOut );
752:            }
753:
- 754:            return uint128(userFeeInfo.gasToBridgeOut);
+ 754:            return uint128(_gasToBridgeOut);
755:        }
756:
757:        if (_initialGas > 0) {
- 758:            if (userFeeInfo.gasToBridgeOut <= MIN_FALLBACK_RESERVE * tx.gasprice) revert InsufficientGasForFees();
+ 758:            if (_gasToBridgeOut <= MIN_FALLBACK_RESERVE * tx.gasprice) revert InsufficientGasForFees();
- 759:            (amountOut, gasToken) = _gasSwapOut(userFeeInfo.gasToBridgeOut, _toChain);
+ 759:            (amountOut, gasToken) = _gasSwapOut(_gasToBridgeOut, _toChain);

UlyssesPool.sol: newTotalWeights should be used instead of totalWeights : Saves 100 GAS,1 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/UlyssesPool.sol#L241-L243

FILE: 2023-05-maia/src/ulysses-amm/UlyssesPool.sol

241:  totalWeights = newTotalWeights;
242:
- 243:        if (totalWeights > MAX_TOTAL_WEIGHT || oldTotalWeights == newTotalWeights) {
+ 243:        if (newTotalWeights> MAX_TOTAL_WEIGHT || oldTotalWeights == newTotalWeights) {

GovernorBravoDelegateMaia.sol: proposal.proposer should be cached with local address variable : Saves 200 GAS,2 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L234C9-L247C14

FILE: 2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

+ address _proposer= proposal.proposer;
- if (msg.sender != proposal.proposer && msg.sender != admin) {
+ if (msg.sender != _proposer && msg.sender != admin) {
            // Whitelisted proposers can't be canceled for falling below proposal threshold
            if (isWhitelisted(proposal.proposer)) {
                require(
-                     (govToken.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < 
+                     (govToken.getPriorVotes(_proposer, sub256(block.number, 1)) < 
                   getProposalThresholdAmount())
                        && msg.sender == whitelistGuardian,
                    "GovernorBravo::cancel: whitelisted proposer"
                );
            } else {
                require(
-                     (govToken.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < 
+                     (govToken.getPriorVotes(_proposer, sub256(block.number, 1)) < getProposalThresholdAmount()),
                    "GovernorBravo::cancel: proposer above threshold"
                );
            }

GovernorBravoDelegateMaia.sol: pendingAdmin should be cached with local address variable : Saves 400 GAS,4 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L508

FILE: 2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

+ address oldPendingAdmin = pendingAdmin;
507: require(
- 508:            msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending
+ 508:            msg.sender == oldPendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending  admin only"
509:        );
510:
511:        // Save current values for inclusion in log
512:        address oldAdmin = admin;
- 513:        address oldPendingAdmin = pendingAdmin;
514:
515:        // Store admin with value pendingAdmin
- 516:        admin = pendingAdmin;
+ 516:        admin = oldPendingAdmin ;
517:
518:        // Clear the pending value
519:        pendingAdmin = address(0);
520:
- 521:        emit NewAdmin(oldAdmin, admin);
+ 521:        emit NewAdmin(oldAdmin, oldPendingAdmin );
- 522:        emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
+ 522:        emit NewPendingAdmin(oldPendingAdmin, address(0));

ERC20Boost.sol: gaugeState.userGaugeBoost should be cached with local uint128 variable : Saves 100 GAS,1 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-20/ERC20Boost.sol#L176-L185

FILE: Breadcrumbs2023-05-maia/src/erc-20/ERC20Boost.sol

176: GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
+   uint128 _userGaugeBoost = gaugeState.userGaugeBoost;
- 177:        if (boost >= gaugeState.userGaugeBoost) {
+ 177:        if (boost >= _userGaugeBoost ) {
178:            _userGauges[msg.sender].remove(gauge);
179:            delete getUserGaugeBoost[msg.sender][gauge];
180:
181:            emit Detach(msg.sender, gauge);
182:        } else {
- 183:            gaugeState.userGaugeBoost -= boost.toUint128();
+ 183:            gaugeState.userGaugeBoost = _userGaugeBoost - boost.toUint128();
184:
185:            emit DecrementUserGaugeBoost(msg.sender, gauge, gaugeState.userGaugeBoost);

TalosBaseStrategy.sol: liquidity should be cached with local uint128 variable : Saves 200 GAS,2 SLOD

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

214: uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
+  uint128 _liquidity = liquidity;
- 215:        shares = supply == 0 ? liquidityDifference * MULTIPLIER : (liquidityDifference * supply) / liquidity;
+ 215:        shares = supply == 0 ? liquidityDifference * MULTIPLIER : (liquidityDifference * supply) / _liquidity;
- 216:        liquidity += liquidityDifference;
+ 216:        liquidity =_liquidity + liquidityDifference;

INonfungiblePositionManager _nonfungiblePositionManager = nonfungiblePositionManager; // Saves an extra SLOAD
        {
+         uint _liquidity= liquidity ;
-            uint128 liquidityToDecrease = uint128((_liquidity* shares) / totalSupply);

            (amount0, amount1) = _nonfungiblePositionManager.decreaseLiquidity(
                INonfungiblePositionManager.DecreaseLiquidityParams({
                    tokenId: _tokenId,
                    liquidity: liquidityToDecrease,
                    amount0Min: amount0Min,
                    amount1Min: amount1Min,
                    deadline: block.timestamp
                })
            );

            if (amount0 == 0 && amount1 == 0) revert AmountsAreZero();

            _burn(_owner, shares);

-             liquidity -= liquidityToDecrease;
+             liquidity =_liquidity - liquidityToDecrease;
        }

[G-4] Avoid emit state variable when stack variable available

Saves 900 GAS, 9 SLOD

The gas cost for emitting a state variable is 100 gas, while the gas cost for emitting a stack variable is 8 gas. This means that emitting a stack variable instead of a state variable can save 92 gas

GovernorBravoDelegateMaia.sol: Emit and return stack variable newProposalID instead of state variable newProposal.id : Saves 200 GAS, 2 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L164-L167

FILE: Breadcrumbs2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

164: emit ProposalCreated(
- 165: newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock,description
+ 165: newProposalID, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock,description
166:        );
- 167:      return newProposal.id;
+ 167:      return newProposalID;

GovernorBravoDelegateMaia.sol: Emit stack variables newVotingDelay,newVotingPeriod,newProposalThreshold,account instead of votingDelay,votingPeriod,proposalThreshold , whitelistGuardian state variables : Saves 500 GAS, 5 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L406

FILE: Breadcrumbs2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

404: votingDelay = newVotingDelay;
405:
- 406:    emit VotingDelaySet(oldVotingDelay, votingDelay);
+ 406:    emit VotingDelaySet(oldVotingDelay, newVotingDelay);


420: votingPeriod = newVotingPeriod;
421:
- 422: emit VotingPeriodSet(oldVotingPeriod, votingPeriod);
+ 422: emit VotingPeriodSet(oldVotingPeriod, newVotingPeriod);

437:  proposalThreshold = newProposalThreshold;
438:
- 439:  emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);
+ 439:  emit ProposalThresholdSet(oldProposalThreshold, newProposalThreshold);

464: whitelistGuardian = account;
465:
- 466: emit WhitelistGuardianSet(oldGuardian, whitelistGuardian);
+ 466: emit WhitelistGuardianSet(oldGuardian, account);

GovernorBravoDelegator.sol: Emit stack variable implementation_ instead of implementation state variable : Saves 100 GAS, 1 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegator.sol#L48-L50

FILE: 2023-05-maia/src/governance/GovernorBravoDelegator.sol

48:  implementation = implementation_;
49:
- 50:    emit NewImplementation(oldImplementation, implementation);
+ 50:    emit NewImplementation(oldImplementation, implementation_);

TalosBaseStrategy.sol: Emit stack variable _tokenId instead of tokenId state variable : Saves 100 GAS, 1 SLOD

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

155:   tokenId = _tokenId;
156:
157:        _mint(receiver, shares);
158:        if (totalSupply > optimizer.maxTotalSupply()) revert ExceedingMaxTotalSupply();
159:
- 160:`        emit Initialize(tokenId, msg.sender, receiver, amount0, amount1, shares);
+ 160:`        emit Initialize(_tokenId, msg.sender, receiver, amount0, amount1, shares);

[G-5] require() or revert() statements that check input arguments should be at the top of the function (Also restructured some if’s)

Saves 4300 GAS

Fail early and cheaply

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 a Gcoldsload (2100 gas) in a function that may ultimately revert in the unhappy case.

GovernorBravoDelegateMaia.sol: Cheaper to check the function parameters,constants before checking govToken.getPriorVotes(msg.sender, sub256(block.number, 1)) external calls. Saves 2100 gas

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L112-L124

FILE: Breadcrumbs2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

111: // Reject proposals before initiating as Governor
112:        require(initialProposalId != 0, "GovernorBravo::propose: Governor Bravo not active");
+ 123:        require(targets.length != 0, "GovernorBravo::propose: must provide actions");
+ 124:        require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions");
113:        // Allow addresses above proposal threshold and whitelisted addresses to propose
+ 119:        require(
+ 120:            targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,
+ 121:            "GovernorBravo::propose: proposal function information arity mismatch"
+ 122:        );
114:        require(
115:            govToken.getPriorVotes(msg.sender, sub256(block.number, 1)) > getProposalThresholdAmount()
116:                || isWhitelisted(msg.sender),
117:            "GovernorBravo::propose: proposer votes below proposal threshold"
118:        );
- 119:        require(
- 120:            targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,
- 121:            "GovernorBravo::propose: proposal function information arity mismatch"
- 122:        );
- 123:        require(targets.length != 0, "GovernorBravo::propose: must provide actions");
- 124:        require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions");

UlyssesPool.sol: Cheaper to check the function parameters, constants before checking factory.owner() external calls. Saves 2100 gas

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/UlyssesPool.sol#L324-L327

FILE: Breadcrumbs2023-05-maia/src/ulysses-amm/UlyssesPool.sol

323: function setProtocolFee(uint256 _protocolFee) external nonReentrant {
- 324:        if (msg.sender != factory.owner()) revert Unauthorized();
325:
326:        // Revert if the protocol fee is larger than 1%
327:        if (_protocolFee > MAX_PROTOCOL_FEE) revert InvalidFee();
+ 324:        if (msg.sender != factory.owner()) revert Unauthorized();
328:
329:        protocolFee = _protocolFee;
330:    }

RootPort.sol: Cheaper to check the _bridgeAgentFactory,_coreRootRouter function parameters before checking _setup state variable. Saves around 60-75 gas .

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootPort.sol#L129-L131

FILE: 2023-05-maia/src/ulysses-omnichain/RootPort.sol

128: function initialize(address _bridgeAgentFactory, address _coreRootRouter) external onlyOwner {
- 129:        require(_setup, "Setup ended.");
130:        require(_bridgeAgentFactory != address(0), "Bridge Agent Factory cannot be 0 address.");
131:        require(_coreRootRouter != address(0), "Core Root Router cannot be 0 address.");
+ 129:        require(_setup, "Setup ended.");
132:
133:        isBridgeAgentFactory[_bridgeAgentFactory] = true;


- 145: require(_setup, "Setup ended.");
- 146:        require(isBridgeAgent[_coreRootBridgeAgent], "Core Bridge Agent doesn't exist.");
147:        require(_coreRootBridgeAgent != address(0), "Core Root Bridge Agent cannot be 0 address.");
148:        require(_coreLocalBranchBridgeAgent != address(0), "Core Local Branch Bridge Agent cannot be 0 address.");
149:        require(_localBranchPortAddress != address(0), "Local Branch Port Address cannot be 0 address.");
+ 145: require(_setup, "Setup ended.");
+ 146:        require(isBridgeAgent[_coreRootBridgeAgent], "Core Bridge Agent doesn't exist.");

[G-6] Multiple accesses of a mapping/array should use a local variable cache

Saves 1800 GAS, 18 SLOD

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 perform the same offset calculation every time. Help the Optimizer by saving a storage variable’s reference instead of repeatedly fetching it

To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array.

As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it

MultiRewardsDepot.sol: _assets[rewardsContract] mapping should be cached : Saves 100 GAS, 1 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/rewards/depots/MultiRewardsDepot.sol#L60-L62

FILE: 2023-05-maia/src/rewards/depots/MultiRewardsDepot.sol

+ address rewardsContract_=_assets[rewardsContract];
- 60: emit AssetRemoved(rewardsContract, _assets[rewardsContract]);
+ 60: emit AssetRemoved(rewardsContract, rewardsContract_);
61:
- 62: delete _isAsset[_assets[rewardsContract]];
+ 62: delete _isAsset[rewardsContract_];

MultiRewardsDepot.sol: _assets[rewardsContract] mapping should be cached : Saves 100 GAS, 1 SLOD

FILE: 2023-05-maia/src/maia/factories/PartnerManagerFactory.sol

+ uint256 _partnerManager = partners[partnerIds[partnerManager]];
- 81: if (partners[partnerIds[partnerManager]] != partnerManager) revert InvalidPartnerManager();
+ 81: if (_partnerManager  != partnerManager) revert InvalidPartnerManager();
- 82:  delete partners[partnerIds[partnerManager]];
+ 82:  delete partners[_partnerManager];

BranchBridgeAgent.sol: uint8(getDeposit[_depositNonce].hTokens.length),getDeposit[_depositNonce].tokens[0],getDeposit[_depositNonce].hTokens[0],getDeposit[_depositNonce].amounts[0],getDeposit[_depositNonce].deposits[0],getDeposit[nonce].hTokens,getDeposit[nonce].tokens, getDeposit[nonce].amounts, getDeposit[nonce].deposits,getDeposit[_depositNonce].depositedGas mappings should be cached : Saves 1500 GAS, 15 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchBridgeAgent.sol#L332

FILE: 2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol

+ uint8 _hTokensLength = uint8(getDeposit[_depositNonce].hTokens.length);

- 332: if (uint8(getDeposit[_depositNonce].hTokens.length) == 1) { //@audit 1 SLOD
+ 332: if (_hTokensLength  == 1) {

- 365: } else if (uint8(getDeposit[_depositNonce].hTokens.length) > 1) { //@audit 2 SLOD
+ 365: } else if (_hTokensLength > 1) {

372: msg.sender,
- 373: uint8(getDeposit[_depositNonce].hTokens.length), //@audit 3 SLOD
+ 373: _hTokensLength,
374: nonce,

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchBridgeAgent.sol#L332-L364

FILE: 2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol

if (uint8(getDeposit[_depositNonce].hTokens.length) == 1) {
+     address tokens_= getDeposit[_depositNonce].tokens[0];
+     address hTokens_=getDeposit[_depositNonce].hTokens[0];
+     uint256 amounts_=getDeposit[_depositNonce].amounts[0];
+    uint256  deposits_= getDeposit[_depositNonce].deposits[0];
            if (_isSigned) {
                packedData = abi.encodePacked( //@audit GAS is this possible to cache getDeposit[_depositNonce]
                    bytes1(0x05),
                    msg.sender,
                    _depositNonce,
-                    getDeposit[_depositNonce].hTokens[0], //@audit 1 SLOD
+                    hTokens_,
-                     getDeposit[_depositNonce].tokens[0], //@audit 1 SLOD
+                     tokens_,
-                    getDeposit[_depositNonce].amounts[0], //@audit 1 SLOD
+                    amounts_,
                    _normalizeDecimals(
                        
-                 getDeposit[_depositNonce].deposits[0],ERC20(getDeposit[_depositNonce].tokens[0]).decimals()  //@audit 2 SLOD
+                 deposits_,ERC20(tokens_).decimals()
                    ),
                    _toChain,
                    _params,
                    msg.value.toUint128(),
                    _remoteExecutionGas
                );
            } else {
                packedData = abi.encodePacked(
                    bytes1(0x02),
                    _depositNonce,
-                     getDeposit[_depositNonce].hTokens[0], //@audit 2 SLOD
+                      hTokens_,
-                     getDeposit[_depositNonce].tokens[0], //@audit 3 SLOD
+                     tokens_,
-                    getDeposit[_depositNonce].amounts[0],   //@audit 2 SLOD
+                    amounts_,
                    _normalizeDecimals(
                        
-            getDeposit[_depositNonce].deposits[0],ERC20(getDeposit[_depositNonce].tokens[0]).decimals() //@audit 4 SLOD
+            deposits_,ERC20(tokens_).decimals()
FILE: 2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol

 } else if (uint8(getDeposit[_depositNonce].hTokens.length) > 1) {
            //Nonce
         uint32 nonce = _depositNonce; 

+    address[]  hTokens_=getDeposit[nonce].hTokens; 
+    address[]  tokens_=getDeposit[nonce].tokens;
+    uint256[]  amounts_=getDeposit[nonce].amounts; 
+    uint256[]  deposits_=getDeposit[nonce].deposits; 

            if (_isSigned) {
                packedData = abi.encodePacked(
                    bytes1(0x06),
                    msg.sender,
                    uint8(getDeposit[_depositNonce].hTokens.length),
                    nonce,
-                    getDeposit[nonce].hTokens, //@audit 1 SLOD
+                    hTokens_,
-                    getDeposit[nonce].tokens, //@audit 1 SLOD
+                    tokens_,
-                    getDeposit[nonce].amounts, //@audit 1 SLOD
+                    amounts_,
-                    _normalizeDecimalsMultiple(getDeposit[nonce].deposits, getDeposit[nonce].tokens), //@audit 2 SLOD
+                    _normalizeDecimalsMultiple(deposits_, tokens_),
                    _toChain,
                    _params,
                    msg.value.toUint128(),
                    _remoteExecutionGas
                );
            } else {
                packedData = abi.encodePacked(
                    bytes1(0x03),
                    uint8(getDeposit[nonce].hTokens.length),
                    _depositNonce,
-                    getDeposit[nonce].hTokens,  //@audit 2 SLOD
+                    hTokens_,
-                    getDeposit[nonce].tokens,   //@audit 3 SLOD
+                    tokens_,
-                    getDeposit[nonce].amounts,  //@audit 2 SLOD
+                    amounts_,
-                     _normalizeDecimalsMultiple(getDeposit[nonce].deposits, getDeposit[nonce].tokens), //@audit 4 SLOD
+                     _normalizeDecimalsMultiple(deposits_, tokens_),
                    _toChain,
                    _params,
                    msg.value.toUint128(),
                    _remoteExecutionGas
                );
            }

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchBridgeAgent.sol#L1068-L1075

FILE: 2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol

+   uint256 depositedGas_= getDeposit[_depositNonce].depositedGas ;
- 1069: if (minExecCost > getDeposit[_depositNonce].depositedGas) {
+ 1069: if (minExecCost > gdepositedGas_) {
1070:            _forceRevert();
1071:            return;
1072:        }
1073:
1074:        //Update user deposit reverts if not enough gas => user must boost deposit with gas
- 1075:        getDeposit[_depositNonce].depositedGas -= minExecCost.toUint128();
+ 1075:        getDeposit[_depositNonce].depositedGas = depositedGas_ - minExecCost.toUint128();

RootBridgeAgent.sol: getSettlement[_settlementNonce].gasToBridgeOut mapping should be cached : Saves 100 GAS, 1 SLOD

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L839-L845

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/RootBridgeAgent.sol

838: //Check if sufficient balance
+ uint128 _gasToBridgeOut = getSettlement[_settlementNonce].gasToBridgeOut ;
- 839:        if (minExecCost > getSettlement[_settlementNonce].gasToBridgeOut) {
+ 839:        if (minExecCost > _gasToBridgeOut) {
840:            _forceRevert();
841:            return;
842:        }
843:
844:        //Update user deposit reverts if not enough gas
- 845:        getSettlement[_settlementNonce].gasToBridgeOut -= minExecCost.toUint128();
+ 845:        getSettlement[_settlementNonce].gasToBridgeOut =_gasToBridgeOut - minExecCost.toUint128();
846:    }

[G-7] Store external call values in immutable variables to improve performance and reduce gas costs

Saves 4600 GAS

You can save a Gcoldsload (2100 gas) in the address provider, plus the 100 gas overhead of the external call, for every _replenishGas(), by creating an immutable CONFIG variable which will store the IAnycallProxy(localAnyCallAddress).config() address. localAnyCallAddress is immutable variable so not changed any where in the contract. When ever we call IAnycallProxy(localAnyCallAddress).config() always return the same address. So its waste of external call instead we can store the IAnycallProxy(localAnyCallAddress).config() address with CONFIG immutable variable during constructor initialization time.

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/RootBridgeAgent.sol#L851

FILE: 2023-05-maia/src/ulysses-omnichain/RootBridgeAgent.sol

+ address public immutable CONFIG;

Inside the constructor

+ CONFIG = IAnycallProxy(localAnyCallAddress).config();

- 851: IAnycallConfig(IAnycallProxy(localAnyCallAddress).config()).deposit{value: _executionGasSpent}(address(this));
+ 851: IAnycallConfig(CONFIG).deposit{value: _executionGasSpent}(address(this));

- 1236: IAnycallConfig anycallConfig = IAnycallConfig(IAnycallProxy(localAnyCallAddress).config());
+ 1236: IAnycallConfig anycallConfig = IAnycallConfig(CONFIG);
FILE: 2023-05-maia/src/ulysses-amm/UlyssesPool.sol

+ address public immutable OWNER;

Inside the constructor

+ OWNER= factory.owner()

- 154: asset.safeTransfer(factory.owner(), claimed);
+ 154: asset.safeTransfer(OWNER, claimed);
- 324: if (msg.sender != factory.owner()) revert Unauthorized();
+ 324: if (msg.sender != OWNER) revert Unauthorized();

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

Saves 1040 GAS, 8 Instances

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

Saves: 100-130 GAS

bHermes.sol: The subtractions should be unchecked . /// @dev Never overflows since balandeOf >= userClaimed As per comment the values not going to overflow : Saves 390 GAS

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/hermes/bHermes.sol#L98-L101

FILE: Breadcrumbs2023-05-maia/src/hermes/bHermes.sol

98:   /// @dev Never overflows since balandeOf >= userClaimed.
99:        claimWeight(balance - userClaimedWeight[msg.sender]);
100:        claimBoost(balance - userClaimedBoost[msg.sender]);
101:        claimGovernance(balance - userClaimedGovernance[msg.sender]);

UlyssesPool.sol: balance - assets should be unchecked since not possible to overflow because of this condition check if (balance > assets) : Saves 130 GAS

FILE: Breadcrumbs2023-05-maia/src/ulysses-amm/UlyssesPool.sol

138: if (balance > assets) {
- 139:  return balance - assets;
+ 139:  return unchecked{balance - assets};
140: } else {

UlyssesPool.sol: newRebalancingFee - oldRebalancingFee should be unchecked since not possible to overflow because of this condition check if (oldRebalancingFee < newRebalancingFee) : Saves 260 GAS

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/UlyssesPool.sol#L217-L219

FILE: 2023-05-maia/src/ulysses-amm/UlyssesPool.sol

217:     if (oldRebalancingFee < newRebalancingFee) {
- 218:            asset.safeTransferFrom(msg.sender, address(this), newRebalancingFee - oldRebalancingFee);
+ unchecked {
+        asset.safeTransferFrom(msg.sender, address(this), newRebalancingFee - oldRebalancingFee);
+    }
219:     }

302: if (oldRebalancingFee < newRebalancingFee) {
- 303:            asset.safeTransferFrom(msg.sender, address(this), newRebalancingFee - oldRebalancingFee);
+ unchecked {
+        asset.safeTransferFrom(msg.sender, address(this), newRebalancingFee - oldRebalancingFee);
+    }
304:        }

BranchPort.sol: currBalance - minReserves, minReserves - currBalance should be unchecked since not possible to overflow because of this condition check currBalance > minReserves,currBalance < minReserves : Saves 260 GAS

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/BranchPort.sol#L129

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/BranchPort.sol

- 129: return currBalance > minReserves ? currBalance - minReserves : 0;
+ 129: unchecked {return currBalance > minReserves ? currBalance - minReserves : 0;}

- 141: return currBalance < minReserves ? minReserves - currBalance : 0;
+ 129: unchecked {return currBalance < minReserves ? minReserves - currBalance : 0;}

[G-9] Unused internal state variable and lock modifier can be removed to save overall contract deployment cost

Saves 25487 GAS

Unused internal state variable take up space in the contract's storage, which can increase the gas cost of deploying and calling the contract. If a state variable is never used, then it can be safely removed from the contract. Lock modifier can also increase the gas cost of calling a contract. Lock modifiers are used to prevent a contract from being called by an unauthorized address. However, if the contract is only ever called by a trusted address, then the lock modifier can be safely removed. The lock modifier not used through out the contract can be safely removed.

CoreRootRouter.sol: After Removed unused code the deployment cost saved : 25487 GAS

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-omnichain/CoreRootRouter.sol#L435-L443

FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/CoreRootRouter.sol

435: uint256 internal _unlocked = 1;
436:
437:    /// @notice Modifier for a simple re-entrancy check.
438:    modifier lock() {
439:        require(_unlocked == 1);
440:        _unlocked = 2;
441:        _;
442:        _unlocked = 1;
443:    }

[G-10] Use constants instead of type(uintx).max

Saves 81 GAS

Using constants instead of type(uint256).max can save gas. This is because the compiler can embed constants directly into the bytecode of your contract, which saves on gas costs.

type(uint256).max this code will use 3 gas to get the maximum value of a uint256.

But constant will use 0 gas to get the maximum value of a uint256.

BranchBridgeAgent.sol: Use constants instead of type(uintx).max

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/uni-v3-staker/UniswapV3Staker.sol#L70C8-L70C45

FILE: 2023-05-maia/src/uni-v3-staker/UniswapV3Staker.sol

70: if (liquidity == type(uint96).max) {

385: amount0Max: type(uint128).max,

386: amount1Max: type(uint128).max

456: if (liquidity >= type(uint96).max) stake.liquidityIfOverflow = 0;

506: if (liquidity >= type(uint96).max) {

509: liquidityNoOverflow: type(uint96).max,

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/rewards/rewards/FlywheelGaugeRewards.sol#L133C12-L133C67

FILE: 2023-05-maia/src/rewards/rewards/FlywheelGaugeRewards.sol

133: require(newRewards <= type(uint112).max); // safe cast

187: require(nextRewards <= type(uint112).max); // safe cast

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/maia/tokens/ERC4626PartnerManager.sol#L200-L203

FILE: 2023-05-maia/src/maia/tokens/ERC4626PartnerManager.sol

200:  address(gaugeWeight).safeApprove(newPartnerVault, type(uint256).max);

201:  address(gaugeBoost).safeApprove(newPartnerVault, type(uint256).max);

202:  address(governance).safeApprove(newPartnerVault, type(uint256).max);

203:  address(partnerGovernance).safeApprove(newPartnerVault, type(uint256).max);

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-4626/ERC4626MultiToken.sol#L143

FILE: Breadcrumbs2023-05-maia/src/erc-4626/ERC4626MultiToken.sol

143: if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;

165: if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;

200: shares = type(uint256).max;

270:  return type(uint256).max;

275:  return type(uint256).max;

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/talos/base/TalosBaseStrategy.sol#L130

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

130: address(_token0).safeApprove(address(_nonfungiblePositionManager), type(uint256).max);

131: address(_token1).safeApprove(address(_nonfungiblePositionManager), type(uint256).max);

249: if (allowed != type(uint256).max) allowance[_owner][msg.sender] = allowed - shares;

285: amount0Max: type(uint128).max,

286: amount1Max: type(uint128).max

367: amount0Max: type(uint128).max,

368: amount1Max: type(uint128).max

[G-11] Upgrade Solidity’s optimizer

Saves 1000 GAS

Make sure Solidity’s optimizer is enabled. It reduces gas costs. If you want to gas optimize for contract deployment (costs less to deploy a contract) then set the Solidity optimizer at a low number. If you want to optimize for run-time gas costs (when functions are called on a contract) then set the optimizer to a high number.

Set the optimization value higher than 800 in your hardhat.config.ts file.

FILE: hardhat.config.js overrides: { 'src/talos/TalosStrategyVanilla.sol': { version: '0.8.18', settings: { viaIR: true, optimizer: { enabled: true, runs: 200, }, metadata: { bytecodeHash: 'none', }, }, }, 'src/talos/TalosStrategyStaked.sol': { version: '0.8.18', settings: { viaIR: true, optimizer: { enabled: true, runs: 200, }, metadata: { bytecodeHash: 'none', }, }, }, 'src/ulysses-omnichain/RootBridgeAgent.sol': { version: '0.8.18', settings: { viaIR: true, optimizer: { enabled: true, runs: 200, }, metadata: { bytecodeHash: 'none', }, }, }, 'src/ulysses-omnichain/BranchBridgeAgent.sol': { version: '0.8.18', settings: { viaIR: true, optimizer: { enabled: true, runs: 200, }, metadata: { bytecodeHash: 'none', }, }, }, 'src/ulysses-omnichain/BranchBridgeAgentFactory.sol': { version: '0.8.18', settings: { viaIR: true, optimizer: { enabled: true, runs: 200, }, metadata: { bytecodeHash: 'none', }, }, }, }, },

[G-12] Combine events to save Glogtopic (375 gas)

We can combine the events into one singular event to save Glogtopic (375 gas) that would otherwise be paid for the additional one event.

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/governance/GovernorBravoDelegateMaia.sol#L521-L522

FILE: 2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

521: emit NewAdmin(oldAdmin, admin);
523: emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);

[G-13] 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


/// @audit getDepositEntry(),callOut(),callOutAndBridge(),callOutAndBridgeMultiple(),callOutSigned(),callOutSignedAndBridge(),callOutSignedAndBridgeMultiple(),retryDeposit(),retrySettlement(),retrieveDeposit(),redeemDeposit(),performSystemCallOut(),performCallOut(),performCallOutAndBridge(),performCallOutAndBridgeMultiple(),clearToken()
FILE: Breadcrumbs2023-05-maia/src/ulysses-omnichain/BranchBridgeAgent.sol


/// @audit getSettlementEntry(),retrySettlement(),redeemSettlement(),callOut(),callOutAndBridge(),callOutAndBridgeMultiple(),bridgeInMultiple(),
FILE: 2023-05-maia/src/ulysses-omnichain/RootBridgeAgent.sol

[G-14] Use nested require and if and, avoid multiple check combinations

Saves 360 GAS, 24 Instances

Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports. Saves 15 GAS, Per instance

FILE: Breadcrumbs2023-05-maia/src/talos/libraries/PoolVariables.sol

98: if (tick < 0 && tick % tickSpacing != 0) compressed--;

FILE: 2023-05-maia/src/talos/base/TalosBaseStrategy.sol

151: if (amount0 == 0 && amount1 == 0) revert AmountsAreZero();
121: if (amount0 == 0 && amount1 == 0) revert AmountsAreZero();
273: if (amount0 == 0 && amount1 == 0) revert AmountsAreZero();
335: if (amount0 == 0 && amount1 == 0) revert AmountsAreZero();
408: require(balance0 >= amount0 && balance1 >= amount1);

FILE: 2023-05-maia/src/erc-20/ERC20Gauges.sol

211: if (added && _userGauges[user].length() > maxGauges && !canContractExceedMaxGauges[user]) {
464: if (canExceedMax && account.code.length == 0) revert Errors.NonContractError();

FILE: 2023-05-maia/src/erc-20/ERC20MultiVotes.sol

257: if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
112: if (canExceedMax && account.code.length == 0) revert Errors.NonContractError();
201: if (newDelegate && delegateCount(delegator) > maxDelegates && !canContractExceedMaxDelegates[delegator]) {

FILE: 2023-05-maia/src/governance/GovernorBravoDelegateMaia.sol

67: require(
            votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD,
            "GovernorBravo::initialize: invalid voting period"
        );
71:         require(
            votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY,
            "GovernorBravo::initialize: invalid voting delay"
        );
75:        require(
            proposalThreshold_ >= MIN_PROPOSAL_THRESHOLD && proposalThreshold_ <= MAX_PROPOSAL_THRESHOLD,
            "GovernorBravo::initialize: invalid proposal threshold"
        );

119: require(
            targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,
            "GovernorBravo::propose: proposal function information arity mismatch"
        );

234: if (msg.sender != proposal.proposer && msg.sender != admin) {

237: require(
                    (govToken.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < getProposalThresholdAmount())
                        && msg.sender == whitelistGuardian,
                    "GovernorBravo::cancel: whitelisted proposer"
                );

298: require(
            proposalCount >= proposalId && proposalId > initialProposalId, "GovernorBravo::state: invalid proposal id"
        );

399: require(
            newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY,
            "GovernorBravo::_setVotingDelay: invalid voting delay"
        );

416: require(
            newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD,
            "GovernorBravo::_setVotingPeriod: invalid voting period"
        );

433: require(
            newProposalThreshold >= MIN_PROPOSAL_THRESHOLD && newProposalThreshold <= MAX_PROPOSAL_THRESHOLD,
            "GovernorBravo::_setProposalThreshold: invalid proposal threshold"
        );

507: require(
            msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending admin only" //@audit-issue unreached check. dravee zksyn low
        );

#0 - c4-judge

2023-07-07T10:15:21Z

trust1995 marked the issue as grade-a

#1 - c4-sponsor

2023-07-12T18:22:13Z

0xBugsy marked the issue as sponsor confirmed

#2 - 0xLightt

2023-09-07T11:28:50Z

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