Skip to main content
Case studiesDeFi

Building a Balancer hook: Part 1

By October 9, 2024November 18th, 2024No Comments9 min read

Introduction

In DeFi, flexibility is the key to staying ahead. Hooks are one of the powerful tools that allow developers to extend and customize the behavior of smart contracts in DeFi platforms. If you’re new to the concept of hooks, check out our previous article on DeFi hooks for a more comprehensive introduction.

In this two-part series, we’ll take a deep dive into building a custom hook for Balancer – a leading DeFi protocol known for its programmable liquidity and automated market making. Part 1 explores what hooks are in the context of Balancer, looks at the architecture of Balancer hooks and discusses the possibilities they open up for developers. In the second part, we’ll introduce the LoyaltyHook, a custom Balancer V3 hook designed to increase user engagement by rewarding loyal users.

What are hooks in Balancer?

Hooks in context of Balancer are a powerful extension mechanism that allows developers to inject custom logic at specific points in a pool’s lifecycle. They are standalone smart contracts with their own state and logic that can be attached to a pool during registration. By using hooks, developers can modify or extend the default behavior of pools without modifying the core Balancer protocol. Basically, it’s a new DeFi framework, and that’s why we’re interested in it.

Some of the Balancer hooks features:

  • Lifecycle integration: Hooks can execute custom code at various stages of a pool’s lifecycle, such as before and after swaps, or when liquidity is added or removed.
  • Stateful logic: Because hooks are separate contracts, they can maintain their own state, enabling complex functionality such as dynamic fee adjustments or user-specific incentives.
  • Reentrancy support: Hooks are designed to be re-entrant, allowing them to perform additional vault operations during execution, such as initiating a swap after adding liquidity.

Balancer v3 architecture

To fully understand how hooks integrate with the Balancer, it’s important to understand the core components of the Balancer protocol: the Router, the Vault, and the Pool. These components work together to facilitate transactions, manage liquidity, and maintain the overall functionality of the protocol.

Router: The Router serves as the user’s gateway to the Balancer protocol. It provides simple interfaces for executing operations such as swaps and liquidity management. By abstracting the complexity of interacting with the vault and pools, the Router makes it easier for users and developers to execute transactions without worrying about low-level details.

Vault: As the central hub, the Vault manages all token balances and liquidity operations. It centralizes accounting by recording credits and debits resulting from operations such as adding or removing liquidity and executing swaps. The Vault streamlines the handling of token balances across multiple pools, ensuring accurate and efficient asset management within the protocol.

Pool: Pools are smart contracts that hold assets and define the rules for trading between them. They expose precise mathematical formulas (invariants) that determine how tokens are exchanged and prices are calculated. Developers can create custom pools with specific behaviors without worrying about the complexities of accounting and asset management because the Vault handles those aspects.

Flow

When a user initiates an operation, such as a swap or adding liquidity, the following steps occur:

  1. User interaction via the router: The user interacts with the Balancer protocol through the router, which provides user-friendly functionality for the desired operation.
  2. Vault unlock: The router unlocks the vault to process the operation and record any associated credits or debits.
  3. Operation processing:
    • The Vault processes the operation, often by routing requests to the appropriate Pool.
    • For swap, the pool’s invariant calculations determine the exact amounts of tokens to be exchanged.
    • The Vault records the resulting credits (tokens owed to the user) and debits (tokens owed to the Vault).
  4. Settlement: The router settles all outstanding credits and debits by transferring tokens to or from the Vault. This ensures that all balances are updated correctly, maintaining the integrity of the protocol’s accounting.
  5. Verification and Closure: The Vault verifies that all credits and debits have been accurately settled. If everything is correct, the operation is completed successfully; if not, it is rolled back to prevent inconsistencies.

This is just a summary, for a real documentation go to https://docs-v3.balancer.fi.

balancer transaction flow
Transaction flow, source: https://docs-v3.balancer.fi/

Hooks enhance this workflow by allowing developers to inject custom logic at specific points in the transaction flow. During transaction processing, hooks can be called before or after specific actions, depending on the hook’s configuration. This enables the integration of features such as dynamic fees, loyalty rewards, or custom behavior without modifying the core protocol.

Balancer hook architecture

Understanding the architecture of Balancer hooks is critical to effectively implementing custom logic. The hook system in Balancer V3 is designed to be flexible and modular, allowing one hook contract to serve multiple pools, even of different types.

When a new pool is registered in Balancer, you have the option to link it to a hook contract by providing the hook’s address. This linkage is immutable—it cannot be changed once the pool is registered.

Here’s how the connection works:

function registerPool(
    address pool,
    ...
    address poolHooksContract,
) external;

After registering the pool with the hook’s address, it’s important to note that the association between the pool and the hook is immutable. This means that the hook configuration stored in the Balancer Vault, once set during pool registration, cannot be changed. This immutability ensures that the behavior of the pool remains consistent and protects users from potential malicious activity.

If you want your hook to be used, you must implement the onRegister function in your hook contract. The Balancer Vault calls this function during pool registration to verify that the pool is authorized to use the hook. This verification step allows the hook to perform the necessary checks and approve the binding.

Why is this immutability and verification critical? Consider a scenario where a pool attracts a significant amount of liquidity over time. If it were possible to connect or modify hooks during the pool’s lifecycle, a malicious actor could introduce a hook that imposes exorbitant exit fees or changes the pool’s behavior in a detrimental way. This would unfairly penalize liquidity providers and could result in significant financial loss. By enforcing an immutable linkage and requiring onRegister verification, Balancer ensures that such vulnerabilities are mitigated, maintaining trust and security within the ecosystem.

balancer hook and pool
Connection between pools and hooks, source: https://docs-v3.balancer.fi/

Lifecycle functions and hook flags

Each hook contract must implement the getHookFlags function, which returns a set of flags indicating which lifecycle hooks are supported:

function getHookFlags() external returns (HookFlags memory hookFlags);

The HookFlags struct specifies which hooks are enabled:

struct HookFlags {
    bool enableHookAdjustedAmounts;
    bool shouldCallBeforeInitialize;
    bool shouldCallAfterInitialize;
    bool shouldCallComputeDynamicSwapFee;
    bool shouldCallBeforeSwap;
    bool shouldCallAfterSwap;
    bool shouldCallBeforeAddLiquidity;
    bool shouldCallAfterAddLiquidity;
    bool shouldCallBeforeRemoveLiquidity;
    bool shouldCallAfterRemoveLiquidity;
}

Here are the key lifecycle functions you can implement:

  • onRegister: Called during pool registration.
  • onBeforeInitialize / onAfterInitialize: Function that run before and after pool initialization.
  • onBeforeAddLiquidity / onAfterAddLiquidity: Functions for liquidity provision events.
  • onBeforeRemoveLiquidity / onAfterRemoveLiquidity: Functions for liquidity removal events.
  • onBeforeSwap / onAfterSwap: Functions that execute around swap operations.
  • onComputeDynamicSwapFeePercentage: Function to compute dynamic swap fees.

By setting the appropriate flags, you tell the Balancer Vault which hooks your contract supports, and the Vault will invoke these hooks at the specified points in the pool’s lifecycle. If you set some of these flags to true, you must implement the lifecycle functions, otherwise the registration will fail.

Hooks can modify the amount calculated in after-hooks, allowing dynamic adjustments to operations such as swaps and liquidity changes. However, this capability comes with certain restrictions:

enableHookAdjustedAmounts: Must be set to true if your hook modifies the amountCalculated.

Let’s build a hook

After exploring the possibilities of Balancer hooks, we decided to put theory into practice by creating our own custom hook for the Balancer Hookathon. Introducing LoyaltyHook – a tokenized loyalty mechanism designed to increase user engagement within Balancer pools.

What is LoyaltyHook?

LoyaltyHook rewards users with loyalty tokens (LOYALTY) when they interact with a pool, such as adding liquidity or executing swaps. These tokens serve as both an incentive and a means to unlock benefits such as reduced swap and exit fees based on the user’s LOYALTY balance. The more a user engages with the pool, the more they are rewarded, encouraging consistent participation.

Exploring Lifecycle Functions

In building LoyaltyHook, we wanted to get the most out of Balancer’s hook system by exploring different lifecycle functions. This allowed us to add custom logic at various stages of pool operations:

  • Before and after swaps: Implement dynamic fee adjustments based on user loyalty. Mint LOYALTY tokens for users who exchange tokens in a pool.
  • After adding and removing liquidity: Reward users with LOYALTY tokens when they add liquidity. And reduce the exit fee when they want to remove it.

In Part 2, we’ll dive deeper into LoyaltyHook’s architecture, discuss its implementation, and explore the tangible benefits it offers to both users and pool creators.