Olas - Sathish9098's results

Olas is a unified network for off-chain services like automation, oracles, co-owned AI. It offers a stack for building services and a protocol for incentivizing their creation and their operation in a co-owned and decentralized way.

General Information

Platform: Code4rena

Start Date: 21/12/2023

Pot Size: $90,500 USDC

Total HM: 10

Participants: 39

Period: 18 days

Judge: LSDan

Total Solo HM: 5

Id: 315

League: ETH

Olas

Findings Distribution

Researcher Performance

Rank: 11/39

Findings: 3

Award: $438.30

QA:
grade-b
Gas:
grade-b
Analysis:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

21.8971 USDC - $21.90

Labels

bug
grade-b
insufficient quality report
QA (Quality Assurance)
edited-by-warden
Q-22

External Links

QA Report

[L-1] A year is not always 365 days

On leap years, the number of days is 366, so calculations during those years will return the wrong value

FILE: 2023-12-autonolas/governance/contracts/OLAS.sol

21:  // One year interval
22:    uint256 public constant oneYear = 1 days * 365;

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/OLAS.sol#L21-L22

FILE: 2023-12-autonolas/tokenomics/contracts/TokenomicsConstants.sol

// One year in seconds
    uint256 public constant ONE_YEAR = 1 days * 365;

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/TokenomicsConstants.sol#L15-L16

[L-2] Implications of Miner-Controlled Timestamps on Bond Maturity Calculations

Contract uses block.timestamp to determine when bonds mature.

Minor Manipulation Possible: Miners have some leeway to manipulate the block timestamp. They can't deviate wildly from the real time, but they can adjust the timestamp by small amounts (usually on the order of seconds to a few minutes).

Low Incentive for Abuse: In most cases, miners have little incentive to manipulate the timestamp in a way that would significantly impact your contract. The potential gain from slightly adjusting bond maturity times is generally low.

FILE: 2023-12-autonolas/tokenomics/contracts/Depository.sol

215: uint256 maturity = block.timestamp + vesting;

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Depository.sol#L215

[L-3] Risks with Arbitrary Payload Execution in Multisig State Modification within a Gnosis Safe Interaction

Since the contract executes the payload without internal validation, any vulnerabilities or flaws in the payload could be exploited, potentially compromising the multisig wallet.

If the payload is malformed or contains errors, the execution might fail, or worse, it could execute unintended modifications (like removing an existing owner instead of adding a new one).

POC

  • Suppose there are three owners (A, B, and C) of a Gnosis Safe multisig wallet, with a threshold of 2 (meaning any transaction requires approval from at least two of the three owners).
  • The team decides to add a new owner D and increase the threshold to 3.
  • They generate a payload that, when executed, would add D as an owner and update the threshold.
  • They pass this payload to the GnosisSafeSameAddressMultisig contract, which then forwards it to the Gnosis Safe contract.
FILE: 2023-12-autonolas/registries/contracts/multisigs
/GnosisSafeSameAddressMultisig.sol

 function create(
        address[] memory owners,
        uint256 threshold,
        bytes memory data
    ) external returns (address multisig)
    {
        // Check for the correct data length
        uint256 dataLength = data.length;
        if (dataLength < DEFAULT_DATA_LENGTH) {
            revert IncorrectDataLength(DEFAULT_DATA_LENGTH, data.length);
        }

        // Read the proxy multisig address (20 bytes) and the multisig-related data
        assembly {
            multisig := mload(add(data, DEFAULT_DATA_LENGTH))
        }

        // Check that the multisig address corresponds to the authorized multisig proxy bytecode hash
        bytes32 multisigProxyHash = keccak256(multisig.code);
        if (proxyHash != multisigProxyHash) {
            revert UnauthorizedMultisig(multisig);
        }

        // If provided, read the payload that is going to change the multisig ownership and threshold
        // The payload is expected to be the `execTransaction()` function call with all its arguments and signature(s)
        if (dataLength > DEFAULT_DATA_LENGTH) {
            uint256 payloadLength = dataLength - DEFAULT_DATA_LENGTH;
            bytes memory payload = new bytes(payloadLength);
            for (uint256 i = 0; i < payloadLength; ++i) {
                payload[i] = data[i + DEFAULT_DATA_LENGTH];
            }

            // Call the multisig with the provided payload
            (bool success, ) = multisig.call(payload);
            if (!success) {
                revert MultisigExecFailed(multisig);
            }
        }

        // Get the provided proxy multisig owners and threshold
        address[] memory checkOwners = IGnosisSafe(multisig).getOwners();
        uint256 checkThreshold = IGnosisSafe(multisig).getThreshold();

        // Verify updated multisig proxy for provided owners and threshold
        if (threshold != checkThreshold) {
            revert WrongThreshold(checkThreshold, threshold);
        }
        uint256 numOwners = owners.length;
        if (numOwners != checkOwners.length) {
            revert WrongNumOwners(checkOwners.length, numOwners);
        }
        // The owners' addresses in the multisig itself are stored in reverse order compared to how they were added:
        // https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552#code#F6#L56
        // Thus, the check must be carried out accordingly.
        for (uint256 i = 0; i < numOwners; ++i) {
            if (owners[i] != checkOwners[numOwners - i - 1]) {
                revert WrongOwner(owners[i]);
            }
        }
    }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/registries/contracts/multisigs/GnosisSafeSameAddressMultisig.sol#L85-L144

  • Implement a validation mechanism within the GnosisSafeSameAddressMultisig contract to verify the payload's structure and intent before execution. This could include checks to ensure the payload aligns with expected changes.

Dry-Run Simulation: Before executing the payload on the actual Gnosis Safe contract, run a simulation or a "dry-run" of the payload in a test environment or a separate function to predict its effects.

[L-4] Risks of Gnosis Safe's Reverse Order Owner Storage on the Verification Logic of GnosisSafeSameAddressMultisig Contract

  • The GnosisSafeSameAddressMultisig contract's correctness in verifying owners depends entirely on this specific storage order of Gnosis Safe.

  • If Gnosis Safe ever changes this detail (e.g., owners are stored in the order they were added), the verification logic in GnosisSafeSameAddressMultisig would fail to correctly verify the owners.

  • This reliance on an external contract's specific implementation detail, albeit stable for now, introduces a point of fragility in the GnosisSafeSameAddressMultisig contract.

Example Scenario

Initial Owners: Imagine a Gnosis Safe with two owners, Alice and Bob. Adding a New Owner: A new owner, Charlie, is added, making the owner list Alice, Bob, Charlie. Gnosis Safe Storage: Internally, Gnosis Safe stores them as Charlie, Bob, Alice. Verification in GnosisSafeSameAddressMultisig: If GnosisSafeSameAddressMultisig is verifying the owners, it should expect Charlie, Bob, Alice, not Alice, Bob, Charlie.

FILE: 2023-12-autonolas/tokenomics/contracts
/Depository.sol

function create(
        address[] memory owners,
        uint256 threshold,
        bytes memory data
    ) external returns (address multisig)
    {
        // Check for the correct data length
        uint256 dataLength = data.length;
        if (dataLength < DEFAULT_DATA_LENGTH) {
            revert IncorrectDataLength(DEFAULT_DATA_LENGTH, data.length);
        }

        // Read the proxy multisig address (20 bytes) and the multisig-related data
        assembly {
            multisig := mload(add(data, DEFAULT_DATA_LENGTH))
        }

        // Check that the multisig address corresponds to the authorized multisig proxy bytecode hash
        bytes32 multisigProxyHash = keccak256(multisig.code);
        if (proxyHash != multisigProxyHash) {
            revert UnauthorizedMultisig(multisig);
        }

        // If provided, read the payload that is going to change the multisig ownership and threshold
        // The payload is expected to be the `execTransaction()` function call with all its arguments and signature(s)
        if (dataLength > DEFAULT_DATA_LENGTH) {
            uint256 payloadLength = dataLength - DEFAULT_DATA_LENGTH;
            bytes memory payload = new bytes(payloadLength);
            for (uint256 i = 0; i < payloadLength; ++i) {
                payload[i] = data[i + DEFAULT_DATA_LENGTH];
            }

            // Call the multisig with the provided payload
            (bool success, ) = multisig.call(payload);
            if (!success) {
                revert MultisigExecFailed(multisig);
            }
        }

        // Get the provided proxy multisig owners and threshold
        address[] memory checkOwners = IGnosisSafe(multisig).getOwners();
        uint256 checkThreshold = IGnosisSafe(multisig).getThreshold();

        // Verify updated multisig proxy for provided owners and threshold
        if (threshold != checkThreshold) {
            revert WrongThreshold(checkThreshold, threshold);
        }
        uint256 numOwners = owners.length;
        if (numOwners != checkOwners.length) {
            revert WrongNumOwners(checkOwners.length, numOwners);
        }
        // The owners' addresses in the multisig itself are stored in reverse order compared to how they were added:
        // https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552#code#F6#L56
        // Thus, the check must be carried out accordingly.
        for (uint256 i = 0; i < numOwners; ++i) {
            if (owners[i] != checkOwners[numOwners - i - 1]) {
                revert WrongOwner(owners[i]);
            }
        }
    }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/registries/contracts/multisigs/GnosisSafeSameAddressMultisig.sol#L85-L144

Implement a mechanism to verify the version or specific implementation of the Gnosis Safe contract being interacted with, ensuring it matches the expected logic.

[L-5] GnosisSafeMultisig contract, there is no implementation of event logging for significant operations, particularly for the creation of a new multisig instance

The absence of events does not directly impact the contract’s core functionality. The contract operations (like creating a multisig) will execute as intended, regardless of whether events are emitted.

The primary impact is on transparency and traceability. For users or developers trying to track the contract's activities, the absence of events makes it more difficult to see the history and results of transactions, especially in a decentralize.

FILE: 2023-12-autonolas/registries/contracts/multisigs
/GnosisSafeMultisig.sol

/// @dev Creates a gnosis safe multisig.
    /// @param owners Set of multisig owners.
    /// @param threshold Number of required confirmations for a multisig transaction.
    /// @param data Packed data related to the creation of a chosen multisig.
    /// @return multisig Address of a created multisig.
    function create(
        address[] memory owners,
        uint256 threshold,
        bytes memory data
    ) external returns (address multisig)
    {
        // Parse the data into gnosis-specific set of variables
        (address to, address fallbackHandler, address paymentToken, address payable paymentReceiver, uint256 payment,
            uint256 nonce, bytes memory payload) = _parseData(data);

        // Encode the gnosis setup function parameters
        bytes memory safeParams = abi.encodeWithSelector(GNOSIS_SAFE_SETUP_SELECTOR, owners, threshold,
            to, payload, fallbackHandler, paymentToken, payment, paymentReceiver);

        // Create a gnosis safe multisig via the proxy factory
        multisig = IGnosisSafeProxyFactory(gnosisSafeProxyFactory).createProxyWithNonce(gnosisSafe, safeParams, nonce);
    }

Add event


event MultisigCreated(address indexed multisig, address[] owners, uint256 threshold);

[L-6] Potential precision lose in checkpoint() function

Function: checkpoint()

FILE: 2023-12-autonolas/tokenomics/contracts
/Tokenomics.sol

 // Bonding and top-ups in OLAS are recalculated based on the inflation schedule per epoch
        // Actual maxBond of the epoch
        tp.epochPoint.totalTopUpsOLAS = uint96(inflationPerEpoch);
        incentives[4] = (inflationPerEpoch * tp.epochPoint.maxBondFraction) / 100;

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L951

Explanation:

The calculation of inflationPerEpoch multiplies curInflationPerSecond (a uint96 value) by the difference in seconds (diffNumSeconds), which is likely a large number. The result is then used in a percentage-based calculation for incentives[4]. The division by 100 can lead to precision loss since Solidity does not support floating-point arithmetic.

#0 - c4-pre-sort

2024-01-10T14:44:55Z

alex-ppg marked the issue as insufficient quality report

#1 - alex-ppg

2024-01-10T14:51:11Z

L-1 dupe of #430

#2 - c4-judge

2024-01-18T21:52:14Z

dmvt marked the issue as grade-b

Findings Information

🌟 Selected for report: 0xAnah

Also found by: 0x11singh99, 0xA5DF, IllIllI, JCK, Raihan, Sathish9098, alphacipher, c3phas, hunter_w3b, naman1778

Labels

bug
G (Gas Optimization)
grade-b
sufficient quality report
edited-by-warden
G-11

Awards

52.4645 USDC - $52.46

External Links

GAS OPTIMIZATIONS

[G-1] manager and _locked state variables can be packed within same slot : Saves 2000 GAS , 1 SLOT

_locked can be a uint8 instead of a uint256. It only stores the values 1 and 2 because the _locked variable is used solely for the Reentrancy lock. Therefore, using uint8 is perfectly safe and will not affect the protocol.

FILE: 2023-12-autonolas/registries/contracts/GenericRegistry.sol

// Owner address
    address public owner;
    // Unit manager
    address public manager;
+    // Reentrancy lock
+    uint8 internal _locked = 1;
    // Base URI
    string public baseURI;
    // Unit counter
    uint256 public totalSupply;
    // Reentrancy lock
-    uint256 internal _locked = 1;

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/registries/contracts/GenericRegistry.sol#L14-L23

[G-2] IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold check is redundant

The check IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold might be redundant across all iterations of your loop. If the donator's vote count and the veOLASThreshold remain constant throughout the execution of this loop (which they likely do), the result of IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold will be the same for each iteration.

By doing this, you reduce the number of calls to IVotingEscrow(ve).getVotes(donator), which can save gas, especially if this is a call to an external contract. Saves 2100 GAS for every iterations and external call

FILE: 2023-12-autonolas/tokenomics/contracts/Tokenomics.sol

// Loop over service Ids to calculate their partial contributions
+      bool isDonatorEligible =   IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold) ? true : false;
        for (uint256 i = 0; i < numServices; ++i) {
            // Check if the service owner or donator stakes enough OLAS for its components / agents to get a top-up
            // If both component and agent owner top-up fractions are zero, there is no need to call external contract
            // functions to check each service owner veOLAS balance
            bool topUpEligible;
            if (incentiveFlags[2] || incentiveFlags[3]) {
                address serviceOwner = IToken(serviceRegistry).ownerOf(serviceIds[i]);
                topUpEligible = (IVotingEscrow(ve).getVotes(serviceOwner) >= veOLASThreshold  ||
-                    IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold) ? true : false;
+        isDonatorEligible ;
            }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L710

[G-3] nextEpochLen , nextVeOLASThreshold storage variables should be cached

FILE: 2023-12-autonolas/tokenomics/contracts/Tokenomics.sol

 // Update epoch length and set the next value back to zero
+        uint32 nextEpochLen_ = nextEpochLen ;
-            if (nextEpochLen > 0) {
+            if (nextEpochLen_ > 0) {
-                curEpochLen = nextEpochLen;
-                epochLen = uint32(curEpochLen);
+                epochLen = uint32(nextEpochLen_);
                nextEpochLen = 0;
            }

            // Update veOLAS threshold and set the next value back to zero
+       uint96 nextVeOLASThreshold_ = nextVeOLASThreshold ;
-            if (nextVeOLASThreshold > 0) {
+            if (nextVeOLASThreshold_ > 0) {
-                veOLASThreshold = nextVeOLASThreshold;
+                veOLASThreshold = nextVeOLASThreshold_;
                nextVeOLASThreshold = 0;
            }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L987-L999

[G-4] lastPointNumber - 1 subtraction can be unchecked

Using unchecked for lastPointNumber - 1 is a good optimization to reduce gas costs. Since already checked that lastPointNumber > 0, the subtraction will not underflow.

FILE: Breadcrumbs2023-12-autonolas/governance/contracts/veOLAS.sol

uint256 lastPointNumber = mapUserPoints[account].length;
        if (lastPointNumber > 0) {
+     unchecked {
            pv = mapUserPoints[account][lastPointNumber - 1];
+               }
        }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/veOLAS.sol#L148

[G-5] Optimize the _verifyData() function for better gas efficiency

Original Code
FILE: 2023-12-autonolas/governance/contracts/multisigs/GuardCM.sol

function _verifyData(address target, bytes memory data, uint256 chainId) internal {
        // Push a pair of key defining variables into one key
        // target occupies first 160 bits
        uint256 targetSelectorChainId = uint256(uint160(target));
        // selector occupies next 32 bits
        targetSelectorChainId |= uint256(uint32(bytes4(data))) << 160;
        // chainId occupies next 64 bits
        targetSelectorChainId |= chainId << 192;

        // Check the authorized combination of target and selector
        if (!mapAllowedTargetSelectorChainIds[targetSelectorChainId]) {
            revert NotAuthorized(target, bytes4(data), chainId);
        }
    }
Optimized Code

function _verifyData(address target, bytes memory data, uint256 chainId) internal {
    require(data.length >= 4, "Data too short");

    // Combine target, selector, and chainId into one key using bit manipulation
    uint256 targetSelectorChainId = (uint256(uint160(target)) | (uint256(bytes4(data)) << 160) | (chainId << 192));

    // Check the authorized combination
    if (!mapAllowedTargetSelectorChainIds[targetSelectorChainId]) {
        revert NotAuthorized(target, bytes4(data), chainId);
    }
}
Explanation of the Optimization

Combined Bitwise Operations: The original code performs separate bitwise operations and assignments to compute targetSelectorChainId. In the optimized version, these operations are combined into a single expression. This reduces the number of assignment operations and makes the code more concise.

Data Length Check: Added a require statement to ensure that the data array is at least 4 bytes long. This is necessary because the code assumes data contains at least 4 bytes (to extract bytes4(data)). Without this check, the code could potentially revert due to an out-of-bounds access.

[G-6] Cache _timeLaunch + ONE_YEAR computation to optimize gas

We can cache the repeated computation of _timeLaunch + ONE_YEAR to avoid calculating it multiple times.

FILE: 2023-12-autonolas/tokenomics/contracts/Tokenomics.sol

 // Time launch of the OLAS contract
        uint256 _timeLaunch = IOLAS(_olas).timeLaunch();
        // Check that the tokenomics contract is initialized no later than one year after the OLAS token is deployed
+      uint  timeLaunchPlusOneYear = _timeLaunch + ONE_YEAR ; 
-        if (block.timestamp >= (_timeLaunch + ONE_YEAR)) {
+        if (block.timestamp >= timeLaunchPlusOneYear ) {
-            revert Overflow(_timeLaunch + ONE_YEAR, block.timestamp);
+            revert Overflow(timeLaunchPlusOneYear , block.timestamp);              
        }
        // Seconds left in the deployment year for the zero year inflation schedule
        // This value is necessary since it is different from a precise one year time, as the OLAS contract started earlier
-        uint256 zeroYearSecondsLeft = uint32(_timeLaunch + ONE_YEAR - block.timestamp);
+        uint256 zeroYearSecondsLeft = uint32(timeLaunchPlusOneYear - block.timestamp);

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L318-L325

[G-7] eBond -= amount subtraction can be unchecked

If certain that eBond is always greater than or equal to amount, using unchecked for the subtraction eBond -= amount can save some gas by skipping overflow/underflow checks.

FILE: 2023-12-autonolas/tokenomics/contracts/Tokenomics.sol

// Effective bond must be bigger than the requested amount
        uint256 eBond = effectiveBond;
        if (eBond >= amount) {
+      unchecked {
            // The effective bond value is adjusted with the amount that is reserved for bonding
            // The unrealized part of the bonding amount will be returned when the bonding program is closed
            eBond -= amount;
+            }
            effectiveBond = uint96(eBond);
            success = true;
            emit EffectiveBondUpdated(eBond);
        }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L620

[G-8] Caching the Result of Typecasting uint32(_epochLen) to avoid repetitive typecasting

uint32(_epochLen) is used to convert _epochLen to a 32-bit unsigned integer. This is done three times within this snippet.

FILE: 2023-12-autonolas/tokenomics/contracts/Tokenomics.sol

+    uint32 tempEpochLen = uint32(_epochLen);  // Caching the typecast result
 // Check for the epochLen value to change
-        if (uint32(_epochLen) >= MIN_EPOCH_LENGTH && uint32(_epochLen) <= ONE_YEAR) {
+        if (tempEpochLen >= MIN_EPOCH_LENGTH && tempEpochLen  <= ONE_YEAR) {
            nextEpochLen = tempEpochLen;
        } else {
            _epochLen = epochLen;
        }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L535-L540

[G-9] Don't Cache state variables only used once

FILE: Treasury.sol

// Increase the amount of LP token reserves
-        uint256 reserves = mapTokenReserves[token] + tokenAmount;
-        mapTokenReserves[token] = reserves;
+        mapTokenReserves[token] = mapTokenReserves[token] + tokenAmount;

[G-10] owner storage variable should be cached with stack variable

Caching the owner in a state variable is more gas-efficient compared to making recursive calls. This approach saves 300 gas and 3 SLOAD

FILE: 2023-12-autonolas/tokenomics/contracts/Depository.sol

123: function changeOwner(address newOwner) external {
        // Check for the contract ownership
+    address owner_ =  owner ; 
- if (msg.sender != owner) {
+ if (msg.sender != owner_) {
-            revert OwnerOnly(msg.sender, owner);
+            revert OwnerOnly(msg.sender, owner_);
        }

        // Check for the zero address
        if (newOwner == address(0)) {
            revert ZeroAddress();
        }

        owner = newOwner;
        emit OwnerUpdated(newOwner);
    }
    
143:    function changeManagers(address _tokenomics, address _treasury) external {
        // Check for the contract ownership
+    address owner_ =  owner ;         
-        if (msg.sender != owner) {
+        if (msg.sender != owner_) {
-            revert OwnerOnly(msg.sender, owner);
+            revert OwnerOnly(msg.sender, owner_);
        }
        
163:         function changeBondCalculator(address _bondCalculator) external {
        // Check for the contract ownership
+    address owner_ =  owner ;         
-        if (msg.sender != owner) {
+        if (msg.sender != owner_) {
-            revert OwnerOnly(msg.sender, owner);
+            revert OwnerOnly(msg.sender, owner_);
        }

183: function create(address token, uint256 priceLP, uint256 supply, uint256 vesting) external returns (uint256 productId) {
        // Check for the contract ownership
+    address owner_ =  owner ;         
-        if (msg.sender != owner) {
+        if (msg.sender != owner_) {
-            revert OwnerOnly(msg.sender, owner);
+            revert OwnerOnly(msg.sender, owner_);
        }
        
244:         function close(uint256[] memory productIds) external returns (uint256[] memory closedProductIds) {
        // Check for the contract ownership
+    address owner_ =  owner ;         
-        if (msg.sender != owner) {
+        if (msg.sender != owner_) {
-            revert OwnerOnly(msg.sender, owner);
+            revert OwnerOnly(msg.sender, owner_);
        }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Depository.sol#L125-L127

[G-11] tokenomics storage variable should be cached with stack variable

Caching the tokenomics in a state variable is more gas-efficient compared to making recursive calls. This approach saves 100 gas and 1 SLOAD

FILE: 2023-12-autonolas/tokenomics/contracts/Depository.sol

225: // Check if the bond amount is beyond the limits
+   address tokenomics_ = tokenomics ;
-        if (!ITokenomics(tokenomics).reserveAmountForBondProgram(supply)) {
+        if (!ITokenomics(tokenomics_).reserveAmountForBondProgram(supply)) {
-            revert LowerThan(ITokenomics(tokenomics).effectiveBond(), supply);
+            revert LowerThan(ITokenomics(tokenomics_).effectiveBond(), supply);
        }


https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Depository.sol#L226

[G-12] epsilonRate and timeLaunch storage variable should be cached with stack variable

Caching the epsilonRate in a state variable is more gas-efficient compared to making recursive calls. This approach saves 100 gas and 1 SLOAD

FILE: 2023-12-autonolas/tokenomics/contracts/Tokenomics.sol

+  uint64 epsilonRate_ = epsilonRate ; 
 // Compare with epsilon rate and choose the smallest one
-        if (fKD > epsilonRate) {
+        if (fKD > epsilonRate_) {
-            fKD = epsilonRate;
+            fKD = epsilonRate_;
        }
       
924:  // Current year
+    uint32  timeLaunch_ = timeLaunch ;
-        uint256 numYears = (block.timestamp - timeLaunch) / ONE_YEAR;
+        uint256 numYears = (block.timestamp - timeLaunch_) / ONE_YEAR;
        // Amounts for the yearly inflation change from year to year, so if the year changes in the middle
        // of the epoch, it is necessary to adjust epoch inflation numbers to account for the year change
        if (numYears > currentYear) {
            // Calculate remainder of inflation for the passing year
            // End of the year timestamp
 -           uint256 yearEndTime = timeLaunch + numYears * ONE_YEAR;
 +           uint256 yearEndTime = timeLaunch_ + numYears * ONE_YEAR;
        

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L856-L858

[G-13] Don't cache external calls only used once

FILE: 2023-12-autonolas/governance/contracts/veOLAS.sol

 function getUserPoint(address account, uint256 idx) public view returns (PointVoting memory uPoint) {
        // Get the number of user points
-        uint256 userNumPoints = IVEOLAS(ve).getNumUserPoints(account);
-        if (userNumPoints > 0) {
+        if (IVEOLAS(ve).getNumUserPoints(account) > 0) {
            uPoint = IVEOLAS(ve).getUserPoint(account, idx);
        }
    }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/wveOLAS.sol#L193-L199

[G-14] Use assembly for back to back external calls

Using inline assembly in Solidity for back-to-back external calls can indeed optimize gas usage, particularly by reducing the overhead associated with these calls.

FILE: 2023-12-autonolas/governance/contracts/wveOLAS.sol

 // Get the total number of supply points
        uint256 numPoints = IVEOLAS(ve).totalNumPoints();
        PointVoting memory sPoint = IVEOLAS(ve).mapSupplyPoints(numPoints);

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/wveOLAS.sol#L264-L266

[G-15] Use efficient Compounded Inflation Calculations in getSupplyCapForYear() function could be more gas efficient

Implemented an efficient algorithm for compound interest calculation that uses fewer iterations, similar to exponentiation by squaring. This reduces the number of loop iterations significantly for large numYears.

Actual code:

FILE: Breadcrumbs2023-12-autonolas/tokenomics/contracts
/TokenomicsConstants.sol

function getSupplyCapForYear(uint256 numYears) public pure returns (uint256 supplyCap) {
        // For the first 10 years the supply caps are pre-defined
        if (numYears < 10) {
            uint96[10] memory supplyCaps = [
                529_659_000_00e16,
                569_913_084_00e16,
                641_152_219_50e16,
                708_500_141_72e16,
                771_039_876_00e16,
                828_233_282_97e16,
                879_860_040_11e16,
                925_948_139_65e16,
                966_706_331_40e16,
                1_000_000_000e18
            ];
            supplyCap = supplyCaps[numYears];
        } else {
            // Number of years after ten years have passed (including ongoing ones)
            numYears -= 9;
            // Max cap for the first 10 years
            supplyCap = 1_000_000_000e18;
            // After that the inflation is 2% per year as defined by the OLAS contract
            uint256 maxMintCapFraction = 2;

            // Get the supply cap until the current year
            for (uint256 i = 0; i < numYears; ++i) {
                supplyCap += (supplyCap * maxMintCapFraction) / 100;
            }
            // Return the difference between last two caps (inflation for the current year)
            return supplyCap;
        }
    }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/TokenomicsConstants.sol#L30-L61

Optimized Code


function getSupplyCapForYear(uint256 numYears) public pure returns (uint256 supplyCap) {
    uint96[10] memory supplyCaps = [
        529_659_000_00e16, 569_913_084_00e16, 641_152_219_50e16, 708_500_141_72e16,
        771_039_876_00e16, 828_233_282_97e16, 879_860_040_11e16, 925_948_139_65e16,
        966_706_331_40e16, 1_000_000_000e18
    ];

    if (numYears < 10) {
        return supplyCaps[numYears];
    }

    // Calculate compounded inflation using a more efficient formula
    return calculateCompoundedInflation(1_000_000_000e18, numYears - 9, 2);
}

function calculateCompoundedInflation(uint256 initialCap, uint256 years, uint256 inflationRate) internal pure returns (uint256) {
    while (years > 0) {
        if (years % 2 == 1) {
            initialCap += (initialCap * inflationRate) / 100;
        }
        inflationRate = (inflationRate * 2) % 100;
        years /= 2;
    }
    return initialCap;
}
Original Method (Linear Approach)

The original method calculates compounded inflation by iteratively adding 2% of the current supply cap to the cap for each year beyond the 10th. In this linear approach, the number of iterations (and thus operations) is directly proportional to the number of years. For example, for 20 years beyond the 10th, it performs 20 iterations.

Optimized Method (Exponentiation by Squaring)

The optimized method changes the way the compounded inflation is calculated. Instead of linearly iterating through each year, it uses a method that can be likened to exponentiation by squaring, a technique to efficiently compute powers. In this method, the number of operations grows logarithmically with the number of years, not linearly. This is because each iteration effectively squares the effect of the inflation, drastically reducing the number of iterations required as the number of years increases.

Example of Gas Savings

Consider calculating the supply cap for 20 years after the 10th year. Linear Approach: Requires 20 iterations. Optimized Approach: It effectively halves the number of remaining years in each iteration. The number of iterations is around logâ‚‚(20), which is about 4 or 5 iterations, significantly less than 20.

[G-16] Optimize getCurrentPriceLP for better gas efficiency

The getCurrentPriceLP function, we can refine the optimizations further to enhance gas efficiency. The primary goal is to streamline the code by eliminating redundant operations and ensuring the calculations are done in the most gas-efficient manner.

Original Code

FILE: 2023-12-autonolas/tokenomics/contracts/GenericBondCalculator.sol

 function getCurrentPriceLP(address token) external view returns (uint256 priceLP)
    {
        IUniswapV2Pair pair = IUniswapV2Pair(token);
        uint256 totalSupply = pair.totalSupply();
        if (totalSupply > 0) {
            address token0 = pair.token0();
            address token1 = pair.token1();
            uint256 reserve0;
            uint256 reserve1;
            // requires low gas
            (reserve0, reserve1, ) = pair.getReserves();
            // token0 != olas && token1 != olas, this should never happen
            if (token0 == olas || token1 == olas) {
                // If OLAS is in token0, assign its reserve to reserve1, otherwise the reserve1 is already correct
                if (token0 == olas) {
                    reserve1 = reserve0;
                }
                // Calculate the LP price based on reserves and totalSupply ratio multiplied by 1e18
                // Inspired by: https://github.com/curvefi/curve-contract/blob/master/contracts/pool-templates/base/SwapTemplateBase.vy#L262
                priceLP = (reserve1 * 1e18) / totalSupply;
            }
        }
    }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/GenericBondCalculator.sol#L67-L91

Optimized Code


function getCurrentPriceLP(address token) external view returns (uint256 priceLP) {
    IUniswapV2Pair pair = IUniswapV2Pair(token);
    (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();

    if (reserve0 == 0 || reserve1 == 0) {
        return 0;
    }

    uint256 totalSupply = pair.totalSupply();
    if (totalSupply == 0) {
        return 0;
    }

    address token0 = pair.token0();
    priceLP = (token0 == olas ? reserve0 : reserve1) * 1e18 / totalSupply;
}
Explanations of Optimizations
  • Early Check for Zero Reserves: Check if either reserve0 or reserve1 is zero at the beginning. If either is zero, the function returns early. This is based on the assumption that if either reserve is zero, the price calculation isn't meaningful.

Gas Saving: This check prevents further execution if there is no liquidity in the pool, saving gas in such scenarios.

  • Deferred Total Supply Check: The totalSupply check is moved after the reserves check. Since the total supply is irrelevant if the reserves are zero, this ordering is more efficient.

Gas Saving: This ensures that the (potentially more expensive) totalSupply call is only made if necessary, saving gas when there are no reserves.

  • Streamlined Price Calculation: The price calculation is condensed into a single line. This is more concise and avoids the need for a conditional statement to swap reserve0 and reserve1.

Gas Saving: Reducing the number of conditional statements and assignments decreases the computational steps and thus the gas cost.

  • Remove Redundant Token Check: The function no longer checks for the case where neither token0 nor token1 is olas, as it's assumed that olas will be one of the tokens in the pair. This removes an unnecessary condition.

Gas Saving: Fewer conditional checks mean fewer operations to perform, leading to lower gas usage.

[G-17] !matured this check is redundant inside the for loop

Particularly addressing the redundant check of the matured flag. Since matured does not change within the loop, we can optimize the function by checking its value outside the loop and altering the loop's behavior accordingly.

FILE: 2023-12-autonolas/tokenomics/contracts/Depository.sol

 function getBonds(address account, bool matured) external view
        returns (uint256[] memory bondIds, uint256 payout)
    {
        // Check the address
        if (account == address(0)) {
            revert ZeroAddress();
        }

        uint256 numAccountBonds;
        // Calculate the number of pending bonds
        uint256 numBonds = bondCounter;
        bool[] memory positions = new bool[](numBonds);
        // Record the bond number if it belongs to the account address and was not yet redeemed
        for (uint256 i = 0; i < numBonds; ++i) {
            // Check if the bond belongs to the account
            // If not and the address is zero, the bond was redeemed or never existed
            if (mapUserBonds[i].account == account) {
                // Check if requested bond is not matured but owned by the account address
                if (!matured ||
                    // Or if the requested bond is matured, i.e., the bond maturity timestamp passed
                    block.timestamp >= mapUserBonds[i].maturity)
                {
                    positions[i] = true;
                    ++numAccountBonds;
                    // The payout is always bigger than zero if the bond exists
                    payout += mapUserBonds[i].payout;
                }
            }
        }

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Depository.sol#L453

#0 - c4-pre-sort

2024-01-10T16:03:35Z

alex-ppg marked the issue as sufficient quality report

#1 - c4-judge

2024-01-18T21:31:26Z

dmvt marked the issue as grade-b

#2 - sathishpic22

2024-01-23T16:30:44Z

Hi @dmvt

Thank you for your prompt evaluation.

I would like to express my concern regarding the assessment of my reports. I am confident in the validity and high quality of my findings, which, as demonstrated, contribute significantly to gas savings. Additionally, my reports consistently highlight low-impact findings more extensively than others.

However, I have noticed that reports receiving an 'A' grade seem to encompass fewer impactful findings and often overlook instances from bot races. It appears that there is a tendency to disregard certain findings in these evaluations.

I firmly believe that a more thorough review of my submissions will reveal their substantial value and alignment with our objectives. I appreciate your attention to this matter and look forward to any feedback that could further enhance the quality of my work.

[G-1] manager and _locked state variables can be packed within same slot : Saves 2000 GAS , 1 SLOT

[G-2] IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold check is redundant

This finding will saves 1 external call for even iterations and saves 2100 GAS for each iterations.

[G-3] nextEpochLen , nextVeOLASThreshold storage variables should be cached - Saves 400 GAS and 4 SLOD

[G-4] lastPointNumber - 1 subtraction can be unchecked - Saves 100-200 GAS After unchecked

[G-5] Optimize the _verifyData() function for better gas efficiency

[G-6] Cache _timeLaunch + ONE_YEAR computation to optimize gas - Saves 2 redundant computations and saves 1000 GAS

[G-7] eBond -= amount subtraction can be unchecked

[G-8] Caching the Result of Typecasting uint32(_epochLen) to avoid repetitive typecasting

[G-9] Don't Cache state variables only used once

[G-10] owner storage variable should be cached with stack variable - Saves 500 GAS , 5 SLOD

[G-11] tokenomics storage variable should be cached with stack variable

[G-12] epsilonRate and timeLaunch storage variable should be cached with stack variable

[G-13] Don't cache external calls only used once

[G-14] Use assembly for back to back external calls

[G-15] Use efficient Compounded Inflation Calculations in getSupplyCapForYear() function could be more gas efficient

[G-16] Optimize getCurrentPriceLP for better gas efficiency

[G-17] !matured this check is redundant inside the for loop

I respectfully request a re-evaluation of my reports. Based on the depth and quality of my findings, I am confident that they merit a higher grade than currently assigned.

I appreciate the opportunity to present my perspective on this matter and am eager to engage in further discussions to clarify any aspects of my work. Your consideration of this request is greatly valued.

Thank you

#3 - dmvt

2024-01-23T20:32:58Z

While I appreciate your effort, gas reports are graded on a curve. I start by disregarding any issue that doesn't state how much gas will be saved. Without this metric, it takes more trouble than it's worth for the sponsor to evaluate. Sometimes this can be overridden by extremely detailed descriptions, but very infrequently. Your report had 6 valid issue under this metric. On the curve of all valid reports, this makes yours a B in this contest.

Findings Information

🌟 Selected for report: hunter_w3b

Also found by: Sathish9098, joaovwfreire, pavankv, peanuts

Labels

analysis-advanced
grade-b
sufficient quality report
A-02

Awards

363.9423 USDC - $363.94

External Links

Olas(Autonolas) Analysis

Olas functions as an integrated platform designed to facilitate a range of off-chain services, encompassing automation processes, oracle systems, and collaboratively owned artificial intelligence capabilities. It provides a comprehensive suite for the development and deployment of these services, underpinned by a protocol that promotes the incentivization of both the creation and ongoing decentralized operation of these services. This infrastructure is engineered to support a co-owned, decentralized model, emphasizing collaborative development and management within its ecosystem.

Protocol Risk model

Systemic risks

Governance

Cross-Chain Communication Security risk

Bridge Contract Dependability (FxGovernorTunnel and HomeMediator):
  • These contracts are responsible for relaying governance decisions between the Ethereum mainnet and other networks like Polygon and Gnosis Chain.
  • They process encoded data sent across the bridge, executing transactions based on this data.
  • Any vulnerability in these contracts (e.g., incorrect message decoding, improper validation of message sender) could lead to unauthorized or incorrect execution of governance actions.
Underlying Bridge Technology Risks (FxPortal, AMB)
  • FxPortal: Used for communication between Ethereum and Polygon. Relies on the security and efficiency of Polygon's PoS bridge.
  • Arbitrary Message Bridge (AMB) : Facilitates communication between Ethereum and Gnosis Chain.
  • These bridges operate by relaying messages and require stringent security to prevent interception or alteration of messages. A breach could result in lost, delayed, or manipulated governance instructions.
Bridge Reliability and Downtime
  • Any downtime or performance issues in the bridge technologies can delay the transmission of governance decisions, impacting the timeliness of their implementation.
  • Prolonged bridge downtimes could render cross-chain governance inoperative, forcing reliance on single-chain governance mechanisms, which may not be feasible for certain decisions.
Cross-Chain Reorg Risks

Blockchain reorganizations (reorgs) can affect the finality of cross-chain messages. A reorg on the source chain after a message has been sent but before it's finalized on the destination chain could result in discrepancies or double executions.

Governance Proposal and Execution Delays risk

Implications of Timelock Delays:
  • Delayed Response in Dynamic Situations: In a rapidly changing environment, such as in response to security threats, market movements, or urgent protocol upgrades, the delay can hinder the protocol's ability to implement timely decisions.
  • Reduced Flexibility: The set delay period (which might be several days or even longer) means that once a proposal is approved, the protocol must wait for this period to elapse before any action can be taken, even if the situation demands an immediate response.
  • Potential for Market Manipulation: In cases where governance decisions are market-sensitive (e.g., tokenomic changes), the known delay period can be exploited by market participants for profit, potentially leading to price manipulation or front-running.
  • Security vs. Agility Trade-off : The protocol must balance the need for security (preventing hasty or malicious actions) with the need for agility (responding swiftly to critical issues).

Risk of Low Voter Participation

Consequences of Low Voter Turnout:
  • Unrepresentative Decisions: With low participation, the decisions made could disproportionately reflect the will of a small group of voters, which might not align with the broader community's interest.
  • Easier Manipulation: Low turnout reduces the number of votes required to pass proposals, making it easier for a minority or even malicious actors to influence governance decisions.
  • Reduced Protocol Legitimacy: Consistently low participation can undermine the legitimacy of the governance process, as decisions are made by a small segment of the holder base.
Technical and Sociological Factors:
  • Complexity and Engagement: The technical complexity of proposals or a disconnect between stakeholders' understanding and the matters at hand can lead to lower engagement.
  • Token Distribution: If veOLAS tokens are concentrated in the hands of a few holders, it can demotivate broader community participation, leading to apathy among smaller holders.
  • Incentive Structures: Lack of adequate incentives for voting can result in voter apathy.

Registries

Dependency and Interoperability Risks

  • Risk: The registries depend on each other (e.g., AgentRegistry depending on ComponentRegistry). Issues in one registry could cascade to others, especially considering the dependency checks in contracts like AgentRegistry.
  • Impact: A failure or vulnerability in one registry could compromise the integrity of another, affecting the overall reliability of the system.

Data Integrity and Validation Risks:

  • Risk: The registries handle critical data, such as IPFS hashes and dependencies. Inadequate validation of this data could lead to incorrect or malicious data being registered.
  • Impact: Incorrect data could lead to faulty operations or could be used to exploit other parts of the system, such as the deployment of compromised agents or components.

Tokenomics

Liquidity Pool Dominance

  • Risk: Large liquidity providers might become overly influential, controlling a significant portion of the OLAS tokens.
  • Impact: This could lead to market manipulation or centralization of token ownership, affecting the token's price and stability.

Economic Sustainability

  • Risk: The long-term viability of the tokenomics model is crucial. Unsustainable incentive models could lead to resource drainage.
  • Impact: If the incentives become unsustainable, it could lead to a decrease in developer participation and liquidity provision, harming the protocol's growth.

Token Concentration and Governance

  • Risk: Token concentration in the hands of a few might lead to governance issues, where major holders wield disproportionate influence.
  • Impact : This could skew decision-making processes and lead to decisions that favor a minority of stakeholders.

Gaming the Incentive System:

  • Risk: If the incentive mechanisms are not well-designed, there's a risk of developers gaming the system to earn rewards without contributing meaningfully.
  • Impact: Such exploitation could drain resources and detract from the protocol's goal of fostering genuinely useful code.

Technical risks

Governance

Smart Contract Bugs and Flaws

Even with rigorous testing and audits, there's always a risk of undiscovered bugs in smart contracts, which could lead to malfunctions or exploits. This includes risks in both the governance contracts (like GovernorOLAS) and associated contracts (like FxGovernorTunnel, HomeMediator, BridgedERC20).

Upgradeability and Contract Interoperability

If any part of the governance system is upgradeable, there's a risk associated with the upgrade process itself, including potential mismatches or incompatibilities between different contract versions.

Denial of Service (DoS) Attacks

Certain governance mechanisms could be vulnerable to DoS attacks, where an attacker deliberately floods the system with transactions or proposals to halt or slow down the governance process.

Time Manipulation Risk

Functions dependent on block timestamps might be vulnerable to minor manipulations by miners, affecting processes that rely on specific timing conditions.

Transaction Ordering Manipulation

Malicious actors could attempt to exploit the public nature of pending transactions in the mempool to influence governance decisions, such as by front-running vote-related transactions.

Registries

Integration with External Systems (like IPFS)

  • Dependence on external systems for critical functionality (e.g., storing metadata on IPFS) introduces risks related to the availability and integrity of these systems.
  • Downtime or data loss in external systems could adversely affect the registries' operation.

Tokenomics

Scalability and Performance Issues

  • High transaction volumes, especially in liquidity pools and staking mechanisms, might strain the blockchain network, leading to high gas fees and slow transaction times.
  • Scalability issues could hinder user participation and affect the overall

Liquidity Pool Risks:

  • Liquidity pools, particularly those involving bonding mechanisms, are exposed to risks such as impermanent loss or pool imbalances.
  • These risks could deter liquidity providers or lead to losses, impacting the protocol’s liquidity.

Regulatory Compliance Risks:

  • Evolving regulatory standards in different jurisdictions can impact tokenomics, especially regarding DeFi practices and cross-chain transactions.
  • Non-compliance could lead to legal challenges or necessitate significant changes in the tokenomics model.

Integration risks

Integration risks in the context of the Autonolas protocol, particularly considering its multi-chain focus and interaction with various blockchain networks and external systems, involve a range of technical challenges.

Governance

Smart Contract Compatibility Risks

  • Risk Details: When integrating with contracts on different blockchains or systems (like Ethereum, Polygon, and Gnosis Chain), there's a risk of compatibility issues due to differing contract standards, gas models, or blockchain functionalities.
  • Technical Specifics: Differences in EVM compatibility, state size limits, or gas pricing mechanisms across chains can affect the behavior of integrated contracts.

Cross-Chain Communication Failures:

  • Risk Details: Reliance on cross-chain bridges (like FxPortal for Polygon and AMB for Gnosis Chain) for governance actions introduces risks of communication failures or discrepancies in data transmission.
  • Technical Specifics: Bridge mechanisms may face downtime, data throughput limitations, or inconsistencies in data validation and verification processes.

Network Congestion and Transaction Delays:

  • Risk Details: High network congestion on integrated blockchains can lead to delayed transaction confirmations, affecting time-sensitive operations.
  • Technical Specifics: Network overload, especially on chains with limited throughput, can result in increased transaction fees and delayed block confirmations.

User Interface and Interaction Issues:

  • Risk Details: Integrating various blockchain functionalities into a user-friendly interface poses challenges, where mismatches or errors can lead to user mistakes or misinterpretations.
  • Technical Specifics : Complex interactions required for cross-chain functionalities might not be intuitively presented in user interfaces, increasing the risk of errors.

Registries

Inter-Registry Dependencies

  • The AgentRegistry and ComponentRegistry rely on each other for validating dependencies. If one registry fails or has incorrect data, it could impact the integrity of the other.
  • Malfunctions or vulnerabilities in one registry could cascade, affecting the functionality of dependent registries.

Consistency Across Registries:

  • Ensuring consistent data across multiple registries is challenging. Discrepancies can arise due to synchronization issues or during updates.
  • Inconsistencies can lead to operational failures or incorrect behavior in contracts relying on registry data.

Tokenomics

External Data and Oracle Integration Risks

  • Risk Description: Dependence on external oracles for market data or other inputs could introduce inaccuracies.
  • Potential Impacts: Malfunctioning or manipulated oracles could skew incentive mechanisms, affecting staking rewards, liquidity incentives, and other crucial operations.

Admin abuse risks

function changeGovernor(address newGovernor) external { function changeGovernorCheckProposalId(uint256 proposalId) external { function changeOwner(address newOwner) external { function mint(address account, uint256 amount) external { function burn(uint256 amount) external { function pause() external virtual { function unpause() external virtual { function changeManager(address newManager) external virtual { function setBaseURI(string memory bURI) external virtual { function changeManagers(address _tokenomics, address _treasury) external { function changeBondCalculator(address _bondCalculator) external { function create(address token, uint256 priceLP, uint256 supply, uint256 vesting) external returns (uint256 productId) { function close(uint256[] memory productIds) external returns (uint256[] memory closedProductIds) { function changeTokenomicsImplementation(address implementation) external { function changeRegistries(address _componentRegistry, address _agentRegistry, address _serviceRegistry) external { function changeDonatorBlacklist(address _donatorBlacklist) external {

Owner Access (Functions like changeOwner, changeGovernor, mint, burn, etc.)

Impact
  • Governance Manipulation: Unauthorized changes to governance roles or rules could lead to detrimental protocol changes or malicious governance actions.
  • Token Supply Disruption: The ability to mint and burn tokens could be exploited to manipulate the token's supply, potentially destabilizing its value and the protocol's economy.
  • Unauthorized Contract Upgrades: Changing contract implementations or altering critical settings could introduce vulnerabilities or alter the protocol’s intended behavior.
  • Operational Disruption: Functions like pausing contracts can halt critical operations, directly impacting users and the protocol's functionality.
function reserveAmountForBondProgram(uint256 amount) external returns (bool success) {
function refundFromBondProgram(uint256 amount) external {

Depository Access (Functions like reserveAmountForBondProgram, refundFromBondProgram)

Impact
  • Financial Losses: Unauthorized access to reserve or refund functions could lead to misappropriation of funds, affecting the protocol's financial stability.
  • Bond Program Manipulation: The integrity of bond programs could be compromised, affecting investor confidence and the protocol's reputation.
function trackServiceDonations(
        address donator,
        uint256[] memory serviceIds,
        uint256[] memory amounts,
        uint256 donationETH
    ) external {
    

Treasury Access (Function like trackServiceDonations)

Impact
  • Misallocation of Funds: If the tracking or allocation of donations is manipulated, it could lead to improper use of funds, affecting the protocol's financial integrity.
  • Data Integrity Issues: Inaccurate tracking of donations could disrupt the protocol’s financial reporting and decision-making processes.
function accountOwnerIncentives(address account, uint256[] memory unitTypes, uint256[] memory unitIds) external
Impact
  • Incentive Manipulation: Unauthorized changes to incentive distributions could unfairly advantage certain users, disrupting the protocol's reward mechanisms and user trust.
  • Resource Drainage: Malicious distribution of incentives could drain resources, impacting the protocol's sustainability.

Architecture assessment of business logic

Governance Architecture

The governance architecture involves various contracts and mechanisms working together to ensure decentralized decision-making and management. Based on the information provided earlier about contracts like GovernorOLAS, FxGovernorTunnel, HomeMediator, and others, let's construct an outline of the likely governance architecture.

GovernanceArchi

Registries Architecture

Architecture assessment of the business logic based on the architecture of the Autonolas protocol's registries system (comprising UnitRegistry, ComponentRegistry, AgentRegistry, and RegistriesManager), we'll consider key architectural aspects and their alignment with business objectives

registrues

GuardCM Contract

Crucial component designed to oversee and possibly regulate actions taken by a community-owned multisig wallet (CM). This contract likely functions as a safeguard within the governance framework, ensuring that actions taken by the CM align with the broader intentions and rules of the decentralized autonomous organization (DAO).

Mind Map

GuardCM Mindmap

Intractions Flow

GuardCM

Software Engineering Considerations

Security Best Practices

  • Follow established security best practices, such as checks-effects-interactions pattern, to prevent reentrancy attacks, and use guard checks against overflows/underflows.
  • Regular security audits and incorporating static analysis tools in the development pipeline are essential.

Testing and Quality Assurance

  • Comprehensive testing, including unit tests, integration tests, and stress tests, should be conducted. This ensures that the contracts behave as expected under various scenarios.
  • Consideration should be given to test-driven development (TDD) practices to ensure robustness from the outset.

Modularity and Reusability

The contracts demonstrate modularity (e.g., inheriting from OpenZeppelin contracts). This approach should be maintained or enhanced to ensure code reusability and easier updates.

Scalability Considerations:

Design contracts with scalability in mind. This includes considering layer 2 solutions or sidechains for more efficient processing, especially for cross-chain functionalities.

Community Feedback and Governance:

Incorporate mechanisms for community feedback and governance decisions into the software development lifecycle, reflecting the decentralized ethos of the protocol.

Security First Approach:

Given the critical role of registries in managing on-chain representations of components and agents, a security-first approach is paramount. This includes following best practices like check-effects-interactions pattern, secure handling of external calls, and gas optimizations.

Disaster Recovery and Emergency Protocols:

Implement mechanisms for data recovery and emergency handling in case of critical failures. This could include pausing functionality in the event of a detected anomaly or breach.

Testing suite Analysis

High levels of coverage across all metrics, indicating thorough testing.

Governance

-------------------------|----------|----------|----------|----------|----------------|

File% Stmts% Branch% Funcs% LinesUncovered Lines
contracts\10093.8410098.81
GovernorOLAS.sol100100100100
OLAS.sol100100100100
Timelock.sol100100100100
veOLAS.sol10092.3710098.4249,253,283,286
wveOLAS.sol100100100100
contracts\bridges\961009597.62
BridgedERC20.sol100100100100
FxERC20ChildTunnel.sol100100100100
FxERC20RootTunnel.sol78.57100808574,77,79
FxGovernorTunnel.sol100100100100
HomeMediator.sol100100100100
contracts\bridges\test\100100100100
FxRootMock.sol100100100100
contracts\interfaces\100100100100
IERC20.sol100100100100
IErrors.sol100100100100
contracts\multisigs\100100100100
GuardCM.sol100100100100
contracts\test\751008075
BrokenERC20.sol75100807531
---------------------------------------------------------------------------------
All files98.7997.1998.3698.7
---------------------------------------------------------------------------------
Notable Insights

veOLAS.sol: Although most metrics are at 100%, there's a minor gap in branch coverage (92.37%) and line coverage (98.4%). Uncovered lines include 249, 253, 283, 286, suggesting potential edge cases or conditional branches not tested.

Recommendations

veOLAS.sol: Increase test cases to cover the branches and lines mentioned above, focusing on the conditional logic that might not be fully exercised in the current test suite.

Registries

------------------------------------|----------|----------|----------|----------|----------------|

File% Stmts% Branch% Funcs% LinesUncovered Lines
contracts\90.5478.1386.2187.34
AgentRegistry.sol10087.57586.6762,72
ComponentRegistry.sol100100100100
GenericManager.sol57.145066.6757.14... 27,28,31,32
GenericRegistry.sol69.2335.7171.4360... 6,98,99,100
RegistriesManager.sol100100100100
UnitRegistry.sol100100100100
contracts\interfaces\100100100100
IErrorsRegistries.sol100100100100
IRegistry.sol100100100100
contracts\multisigs\80.7781.8210083.72
GnosisSafeMultisig.sol10083.33100100
GnosisSafeSameAddressMultisig.sol72.2281.2510075... 118,119,120
--------------------------------------------------------------------------------------------
All files8879.0788.2486.57
--------------------------------------------------------------------------------------------
Notable Insights

GenericManager.sol and GenericRegistry.sol show significantly lower coverage across all metrics. Specifically, GenericRegistry.sol has only 69.23% statement coverage and 35.71% branch coverage. Uncovered lines in GenericRegistry.sol include 96, 98, 99, 100, and in GenericManager.sol, they include 25, 26, 27, 28, 31, 32.

Recommendations

GenericManager.sol & GenericRegistry.sol: Develop additional test cases to improve coverage, particularly targeting the conditional logic and functions that are currently under-tested. AgentRegistry.sol: Address uncovered lines (62, 72) to enhance branch coverage.

Tokenomics

-----------------------------|----------|----------|----------|----------|----------------|

File% Stmts% Branch% Funcs% LinesUncovered Lines
contracts\10095.2210098.56
Depository.sol1008010093.53... 312,385,440
Dispenser.sol100100100100
DonatorBlacklist.sol100100100100
GenericBondCalculator.sol10071.431009032,60
Tokenomics.sol100100100100
TokenomicsConstants.sol100100100100
TokenomicsProxy.sol100100100100
Treasury.sol100100100100
contracts\interfaces\100100100100
IDonatorBlacklist.sol100100100100
IErrorsTokenomics.sol100100100100
IGenericBondCalculator.sol100100100100
IOLAS.sol100100100100
IServiceRegistry.sol100100100100
IToken.sol100100100100
ITokenomics.sol100100100100
ITreasury.sol100100100100
IUniswapV2Pair.sol100100100100
IVotingEscrow.sol100100100100
-------------------------------------------------------------------------------------
All files10095.2210098.56
-------------------------------------------------------------------------------------
Notable Insights

Depository.sol shows lower branch coverage (80%) compared to other metrics. Uncovered lines are 312, 385, 440. GenericBondCalculator.sol has a lower branch coverage of 71.43% and line coverage of 90%, with uncovered lines 32 and 60.

Recommendations

Depository.sol & GenericBondCalculator.sol : Enhance test cases focusing on branches and lines not currently covered. Pay particular attention to complex conditional statements and scenarios that may not be adequately represented in the current test suite.

General Recommendations for Testiong

  • Consistent Test Standards: Ensure uniformity in test coverage standards across all contracts to maintain code quality and reliability.
  • Branch and Edge Case Testing: Prioritize testing for branches and edge cases, especially in contracts with lower branch coverage.
  • Continuous Integration of Tests: Implement continuous integration processes to automatically run tests and report coverage, aiding in identifying coverage gaps promptly.

Code Weak Points and Single point of failures

Centralized Control in BridgedERC20

  • Affected Code: mint and burn functions.
  • Weakness: Both functions are only callable by the owner. This centralization poses a risk, as the security of the entire token supply hinges on a single account.

function mint(address account, uint256 amount) external {
    if (msg.sender != owner) {
        revert OwnerOnly(msg.sender, owner);
    }
    _mint(account, amount);
}

function burn(uint256 amount) external {
    if (msg.sender != owner) {
        revert OwnerOnly(msg.sender, owner);
    }
    _burn(msg.sender, amount);
}

Data Validation in FxGovernorTunnel and HomeMediator

  • Affected Code: processMessageFromRoot and processMessageFromForeign.
  • Weakness: The processing functions unpack and execute transaction data from cross-chain messages. Inadequate validation of this data can lead to execution of harmful transactions. Code Snippet (FxGovernorTunnel):

function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes memory data) external override {
    // ... validation checks ...

    for (uint256 i = 0; i < dataLength;) {
        // ... data unpacking and transaction execution ...
    }
}

Governance Actions Timelock in GovernorOLAS

  • Affected Code: propose and _execute functions.
  • Weakness: The timelock delays the execution of governance decisions, which can be a limitation in urgent situations.

function _execute(
    uint256 proposalId,
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl)
{
    super._execute(proposalId, targets, values, calldatas, descriptionHash);
}

Potential for Front-Running

  • Affected Code: Public functions involved in governance decisions and token operations in all contracts.
  • Weakness: The visibility of transactions in the mempool before being confirmed can lead to front-running, especially in governance and token-related transactions.

Time spent:

15 hours

#0 - c4-pre-sort

2024-01-10T12:24:46Z

alex-ppg marked the issue as sufficient quality report

#1 - c4-judge

2024-01-18T21:52:57Z

dmvt marked the issue as grade-b

#2 - sathishpic22

2024-01-23T16:54:22Z

Hi @dmvt

Thank you for your prompt evaluation.

I believe that my report offers a more comprehensive and high-quality analysis compared to others. It merits a higher grade than currently assigned, as it introduces fresh insights without reiterating information already documented. Additionally, I have adhered to the report quality standards as per the C4 suggestions.

Olas(Autonolas) Risk Model

Systemic risks

Explained possible systemic risk more technical way from each sections Governance , Registries, Tokenomics.

Technical risks

Explained possible technical risks as per docs and implementations

Integration risks

Analyzed possible Integration risks based on implementations

Admin abuse risks ( Centralization risks)

demonstrated the risks related to single ownership control

Architecture assessment of business logic

In This sections explained the architecture explanation's using the diagrams .

governance
GovernanceArchi
Registries Architecture
registrues

Software Engineering Considerations

Explained possible SW considerations to improve the protocol quality

Testing suite Analysis

Code Weak Points and Single point of failures

I respectfully request a re-evaluation of my reports. I am confident that they deserve a higher grade than what has been assigned currently. After reviewing the reports that received Grade A, I am convinced that my analysis is more detailed and thorough.

I appreciate this opportunity to express my thoughts on this matter. Additionally, I have included architecture explanation diagrams to further substantiate the depth of my analysis.

Thank you for considering my request and for your attention to these details.

#3 - dmvt

2024-01-23T20:42:44Z

I appreciate your attention to detail on this report, but I have to disagree with it's value. I view analysis to be an add on which augments the real value a sponsor is looking for, high and medium risk issues. You have not found any, indicating that your understanding of the protocol is not in depth enough to provide information the sponsor is not already aware of. Your charts, while a nice addition, describe a system the sponsor designed and fully understands. The same is true of much of your analysis, including the test coverage.

The report contains a mixture of simplistic descriptions, automated tooling output, simple charts, and best practices that could apply to any codebase regardless of quality. You got a B for the effort involved in collating the information and an obvious desire to provide something of value, but this is not an A level analysis.

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