Building a Treasury Management Hook for Uniswap V4 — Part 1: Contract Architecture and Core Implementation | by Ferdi Kurt | Coinmonks | Jun, 2025

Building a Treasury Management Hook for Uniswap V4 — Part 1: Contract Architecture and Core Implementation | by Ferdi Kurt | Coinmonks | Jun, 2025

The TreasuryManagementHook contract is built on Uniswap V4’s hook system, leveraging several key components for efficient treasury management:

contract TreasuryManagementHook is BaseHook {
using PoolIdLibrary for PoolKey;
using CurrencyLibrary for Currency;

BaseHook Inheritance: Provides the foundational hook infrastructure, handling the complex lifecycle management and integration with Uniswap V4’s PoolManager. This eliminates the need to implement low-level hook mechanics manually.

Library Usage:

  • PoolIdLibrary: Enables type-safe pool identification through the toId() method, ensuring consistent pool referencing across the system
  • CurrencyLibrary: Handles currency operations and type conversions, abstracting away the complexity of different token standards

Core Type Imports: The contract utilizes essential Uniswap V4 types like BalanceDelta, PoolKey, and BeforeSwapDelta for handling swap data and pool configuration.

The contract’s storage design prioritizes gas efficiency while maintaining treasury functionality:

address public treasury; // Treasury controller address
uint24 public treasuryFeeRate; // Fee rate in basis points
uint24 public constant MAX_FEE_RATE = 1000; // 10% maximum fee cap
uint256 public constant BASIS_POINTS = 10000; // Precision denominator

Design Rationale:

  • uint24 for fee rates provides sufficient range (0- 16,777,215) while using minimal storage slots
  • The basis points system (10,000) enables precise fee calculations with 0.01% precision
  • Hard-coded maximum fee rate of 10% prevents excessive fee collection that could harm trading
mapping(PoolId => bool) public isPoolManaged;        // Pool participation tracking
mapping(Currency => uint256) public accumulatedFees; // Fee balances by token

Storage Efficiency:

  • Separate mappings optimize gas costs for different query patterns
  • isPoolManaged enables quick pool status checks during swaps
  • accumulatedFees tracks balances per token type for flexible withdrawals
constructor(
IPoolManager _poolManager,
address _treasury,
uint24 _treasuryFeeRate
) BaseHook(_poolManager) {
if (_treasury == address(0)) revert InvalidTreasuryAddress();
if (_treasuryFeeRate > MAX_FEE_RATE) revert FeeRateTooHigh();

treasury = _treasury;
treasuryFeeRate = _treasuryFeeRate;
}

Zero Address Protection: Prevents deployment with an invalid treasury address, which would lock the contract’s functionality permanently.

Fee Rate Validation: Ensures the initial fee rate complies with the 10% maximum limit, preventing deployment with excessive fees.

Early Validation Strategy: Performing all validation in the constructor prevents invalid contract states and saves gas on failed deployments.

The hook’s capabilities are precisely defined through the permissions structure:

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

Minimal Permissions Approach: Only enables necessary hooks to reduce gas costs and minimize attack surface. The contract specifically needs:

  • afterInitialize: Automatically registers new pools for fee collection
  • beforeSwap: Validates whether the pool should be processed for fees
  • afterSwap: Executes the actual fee collection logic

Gas Optimization: By avoiding unnecessary hook permissions, the contract reduces the overhead of hook processing for operations that don’t require treasury intervention.

The contract implements a straightforward but effective access control system:

function setTreasury(address _newTreasury) external {
if (msg.sender != treasury) revert OnlyTreasuryAllowed();
if (_newTreasury == address(0)) revert InvalidTreasuryAddress();

address oldTreasury = treasury;
treasury = _newTreasury;

emit TreasuryAddressChanged(oldTreasury, _newTreasury);
}

Direct Address Comparison: Uses simple msg.sender comparison for gas-efficient access control, avoiding complex role-based systems that could introduce vulnerabilities.

Input Validation: Prevents transition to invalid states by validating the new treasury address before making changes.

Event Emission: Provides complete transparency for governance changes, enabling off-chain monitoring and audit trails.

Atomic Updates: All changes occur within a single transaction, ensuring state consistency.

The contract defines custom errors for gas-efficient and user-friendly error handling:

error InvalidTreasuryAddress();
error FeeRateTooHigh();
error OnlyTreasuryAllowed();
error InsufficientFees();

Gas Efficiency: Custom errors consume significantly less gas than require statements with string messages, especially important for functions called frequently during swaps.

Type Safety: Enables specific error handling in client applications and testing frameworks.

Clear Communication: Descriptive names improve debugging experience and help developers understand failure conditions.

The contract provides secure methods to update treasury parameters during operation:

function setTreasuryFeeRate(uint24 _newFeeRate) external {
if (msg.sender != treasury) revert OnlyTreasuryAllowed();
if (_newFeeRate > MAX_FEE_RATE) revert FeeRateTooHigh();

uint24 oldRate = treasuryFeeRate;
treasuryFeeRate = _newFeeRate;

emit TreasuryFeeRateChanged(oldRate, _newFeeRate);
}

Access Control: Only the treasury address can modify fee rates, preventing unauthorized changes that could affect trading economics.

Rate Limiting: Enforces the maximum 10% fee rate even for runtime changes, maintaining system safety bounds.

Audit Trail: Event emission creates a permanent record of all fee rate changes for governance tracking.

Immediate Effect: Changes take effect immediately for all subsequent swaps, enabling responsive treasury management.

The contract includes specific features to support testing and development:

function validateHookAddress(BaseHook) internal pure override {
// Skip validation in tests
}

function setPoolManaged(PoolKey calldata key, bool managed) external {
isPoolManaged[key.toId()] = managed;
}

Validation Override: The validateHookAddress override simplifies testing by bypassing complex address validation requirements.

Manual Pool Control: setPoolManaged allows direct manipulation of pool management status for controlled testing scenarios.

Unrestricted Testing: Testing functions intentionally bypass normal access controls to enable test coverage.

This design provides several key advantages:

Modularity: Clear separation between configuration, fee collection, and withdrawal logic enables independent testing and upgrades.

Gas Efficiency: Optimized storage layout and minimal hook permissions reduce operational costs.

Security: Simple access control model reduces complexity while maintaining effective protection.

Flexibility: Configurable fee rates and selective pool management support diverse treasury strategies.

Transparency: Event logging enables complete auditability of treasury operations.

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like