HookFactory

Description:

Decentralized Finance (DeFi) protocol contract providing Swap, Factory functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/* =======================================================================
 *                           BoredStrategyHook
 *    (kept inline so HookFactory can use type(BoredStrategyHook).creationCode)
 * =======================================================================*/

/* ---------- Strategy fee intake interface (unchanged) ---------- */
interface IBoredStrategy {
    function addFees() external payable;
}

/* ---------- Minimal ETH transfer helper ---------- */
library SafeTransferLib {
    function forceSafeTransferETH(address to, uint256 amount) internal {
        (bool success, ) = to.call{value: amount, gas: 30_000}("");
        require(success, "ETH_TRANSFER_FAILED");
    }
}

/* ---------- Minimal v4 types (same as earlier hook) ---------- */

type PoolId is bytes32;

struct PoolKey {
    address currency0;
    address currency1;
    uint24 fee;
    int24 tickSpacing;
    IHooks hooks;
}

struct SwapParams {
    bool zeroForOne;
    int256 amountSpecified;
    uint160 sqrtPriceLimitX96;
}

type BalanceDelta is int256;
type BeforeSwapDelta is int256;

library BeforeSwapDeltaLibrary {
    BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);
}

interface IHooks {
    function beforeSwap(
        address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData
    ) external returns (bytes4, BeforeSwapDelta, uint24);

    function afterSwap(
        address sender, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata hookData
    ) external returns (bytes4, int128);
}

interface IPoolManager {}

abstract contract BaseHook is IHooks {
    IPoolManager public immutable poolManager;
    constructor(IPoolManager _poolManager) { poolManager = _poolManager; }
    modifier onlyPoolManager() {
        require(msg.sender == address(poolManager), "InvalidCaller");
        _;
    }
}

library PoolIdLibrary {
    function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
        assembly ("memory-safe") {
            // keccak256 over 0xa0 bytes: (currency0, currency1, fee, tickSpacing, hooks)
            poolId := keccak256(poolKey, 0xa0)
        }
    }
}

library Hooks {
    struct Permissions {
        bool beforeSwap;
        bool afterSwap;
        bool beforeInitialize;
        bool afterInitialize;
        bool beforeAddLiquidity;
        bool afterAddLiquidity;
        bool beforeRemoveLiquidity;
        bool afterRemoveLiquidity;
        bool beforeDonate;
        bool afterDonate;
        bool beforeSwapReturnsDelta;
        bool afterSwapReturnsDelta;
        bool afterAddLiquidityReturnsDelta;
        bool afterRemoveLiquidityReturnsDelta;
    }
}

/* ================================================================
 *                   BoredStrategyHook (adjustable Team/Spotlight)
 *                   Strategy share fixed at 80% by invariant
 * ================================================================ */
contract BoredStrategyHook is BaseHook {
    using PoolIdLibrary for PoolKey;

    /* --- Monitoring (optional) --- */
    mapping(PoolId => uint256) public beforeSwapCount;
    mapping(PoolId => uint256) public afterSwapCount;

    /* --- Uniswap v4 dynamic swap fee (1,000,000-denom) --- */
    uint24 public constant swapFeeBips = 100_000; // 10%

    /* --- Allocation denominator (BPS) & fixed strategy share --- */
    uint16 private constant ALLOC_DENOM_BPS = 10_000; // 100%
    uint16 private constant STRATEGY_FIXED_BPS = 8_000; // 80% (unchangeable)

    /* --- Mutable allocation for Team & Spotlight (must sum to 2,000) --- */
    uint16 public teamAllocBps;       // e.g., 1_000 = 10%
    uint16 public spotlightAllocBps;  // e.g., 1_000 = 10%

    /* --- Wallets & roles --- */
    address public teamWallet;              // Team destination
    address public spotlightWallet;         // Spotlight destination
    address public immutable boredStrategy; // Strategy contract (receives addFees)
    address public immutable owner;         // <- explicit owner set by constructor

    /* --- Events --- */
    event TeamWalletUpdated(address indexed oldWallet, address indexed newWallet);
    event SpotlightWalletUpdated(address indexed oldWallet, address indexed newWallet);
    event AllocationBpsUpdated(uint16 oldTeamBps, uint16 oldSpotBps, uint16 newTeamBps, uint16 newSpotBps);

    /* --- Errors --- */
    error OnlyOwner();
    error BadAllocationSum(); // team+spotlight must be 2,000 to keep Strategy at 8,000

    /* --- Modifiers --- */
    modifier onlyOwner() {
        if (msg.sender != owner) revert OnlyOwner();
        _;
    }

    /* ---------------- Constructor (5 args; explicit owner) ----------------
       Args (in order):
         - IPoolManager _poolManager
         - address _boredStrategy
         - address _teamWallet
         - address _spotlightWallet
         - address _owner
    ----------------------------------------------------------------------*/
    constructor(
        IPoolManager _poolManager,
        address _boredStrategy,
        address _teamWallet,
        address _spotlightWallet,
        address _owner
    ) BaseHook(_poolManager) {
        owner = _owner;                     // explicit owner (EOA/multisig or strategy)
        boredStrategy = _boredStrategy;
        teamWallet = _teamWallet;
        spotlightWallet = _spotlightWallet;

        // default: 10% / 10% / 80%
        teamAllocBps = 1_000;
        spotlightAllocBps = 1_000;
    }

    /* ---------------- Admin ---------------- */
    function setTeamWallet(address _teamWallet) external onlyOwner {
        emit TeamWalletUpdated(teamWallet, _teamWallet);
        teamWallet = _teamWallet;
    }

    function setSpotlightWallet(address _spotlightWallet) external onlyOwner {
        emit SpotlightWalletUpdated(spotlightWallet, _spotlightWallet);
        spotlightWallet = _spotlightWallet;
    }

    /// @notice Adjust Team/Spotlight allocations. Strategy stays fixed at 80%.
    /// @dev Enforces team + spotlight == 2,000 bps so Strategy remains 8,000 bps.
    function setAllocationBps(uint16 _teamBps, uint16 _spotBps) external onlyOwner {
        if (uint256(_teamBps) + uint256(_spotBps) != (ALLOC_DENOM_BPS - STRATEGY_FIXED_BPS)) {
            revert BadAllocationSum();
        }
        emit AllocationBpsUpdated(teamAllocBps, spotlightAllocBps, _teamBps, _spotBps);
        teamAllocBps = _teamBps;
        spotlightAllocBps = _spotBps;
    }

    /* ---------------- Hook permissions ---------------- */
    function getHookPermissions() public pure returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeSwap: true,
            afterSwap: true,
            beforeInitialize: false,
            afterInitialize: false,
            beforeAddLiquidity: false,
            afterAddLiquidity: false,
            beforeRemoveLiquidity: false,
            afterRemoveLiquidity: false,
            beforeDonate: false,
            afterDonate: false,
            beforeSwapReturnsDelta: false,
            afterSwapReturnsDelta: false,
            afterAddLiquidityReturnsDelta: false,
            afterRemoveLiquidityReturnsDelta: false
        });
    }

    /* --------- Signals the strategy may ping (must not revert) -------- */
    function feeCooldown() external { /* no-op */ }
    function apesAreAccumulating() external { /* no-op */ }

    /* ---------------- Core hooks ---------------- */
    function beforeSwap(
        address,
        PoolKey calldata key,
        SwapParams calldata,
        bytes calldata
    ) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
        beforeSwapCount[key.toId()]++;
        return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, swapFeeBips);
    }

    function afterSwap(
        address,
        PoolKey calldata key,
        SwapParams calldata,
        BalanceDelta,
        bytes calldata
    ) external override onlyPoolManager returns (bytes4, int128) {
        afterSwapCount[key.toId()]++;

        uint256 bal = address(this).balance;
        if (bal > 0) {
            // Base cuts from current BPS settings
            uint256 teamCutBase = (bal * teamAllocBps) / ALLOC_DENOM_BPS;
            uint256 spotCutBase = (bal * spotlightAllocBps) / ALLOC_DENOM_BPS;

            // Strategy gets the remainder (ensures 100% distributed, absorbs rounding)
            uint256 stratCut = bal - teamCutBase - spotCutBase;

            // If either wallet is unset, fold that share into Strategy
            if (teamWallet == address(0)) {
                stratCut += teamCutBase;
                teamCutBase = 0;
            }
            if (spotlightWallet == address(0)) {
                stratCut += spotCutBase;
                spotCutBase = 0;
            }

            // Payouts
            if (teamCutBase > 0) {
                SafeTransferLib.forceSafeTransferETH(teamWallet, teamCutBase);
            }
            if (spotlightWallet != address(0) && spotCutBase > 0) {
                SafeTransferLib.forceSafeTransferETH(spotlightWallet, spotCutBase);
            }
            if (stratCut > 0) {
                IBoredStrategy(boredStrategy).addFees{value: stratCut}();
            }
        }

        return (IHooks.afterSwap.selector, 0);
    }

    receive() external payable {}
}

/* =======================================================================
 *                               HookFactory
 *        Deterministic CREATE2 deployer with low-16-bit mask checking
 * =======================================================================*/
contract HookFactory {
    event Deployed(address hook, bytes32 salt);

    /// @notice Check the low 16 bits of an address against (mask, value)
    function addressHasMask(address a, uint16 mask, uint16 value) public pure returns (bool) {
        uint16 low16 = uint16(uint160(a));
        return (low16 & mask) == value;
    }

    /// @notice Predict CREATE2 address for:
    /// BoredStrategyHook(poolManager, strategy, teamWallet, spotlightWallet, owner).
    function predictAddress(
        address poolManager,
        address strategy,
        address teamWallet,
        address spotlightWallet,
        address owner,
        bytes32 salt
    ) public view returns (address predicted) {
        bytes memory init = _creationCode(poolManager, strategy, teamWallet, spotlightWallet, owner);
        bytes32 initCodeHash = keccak256(init);
        bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash));
        predicted = address(uint160(uint256(hash)));
    }

    /// @notice Deploy the hook using CREATE2 at the predicted address.
    function deploy(
        address poolManager,
        address strategy,
        address teamWallet,
        address spotlightWallet,
        address owner,
        bytes32 salt
    ) public returns (address hook) {
        bytes memory init = _creationCode(poolManager, strategy, teamWallet, spotlightWallet, owner);
        assembly {
            hook := create2(0, add(init, 0x20), mload(init), salt)
            if iszero(hook) { revert(0, 0) }
        }
        emit Deployed(hook, salt);
    }

    /// @notice Deploy and enforce the hook-permission bitmask (avoids a bad address).
    function deployChecked(
        address poolManager,
        address strategy,
        address teamWallet,
        address spotlightWallet,
        address owner,
        bytes32 salt,
        uint16 mask,
        uint16 value
    ) external returns (address hook) {
        address predicted = predictAddress(poolManager, strategy, teamWallet, spotlightWallet, owner, salt);
        require(addressHasMask(predicted, mask, value), "HookFactory: mask mismatch");
        hook = deploy(poolManager, strategy, teamWallet, spotlightWallet, owner, salt);
        require(hook == predicted, "HookFactory: unexpected address");
    }

    /// @dev Encodes constructor args in order:
    ///      (poolManager, boredStrategy, teamWallet, spotlightWallet, owner)
    function _creationCode(
        address poolManager,
        address strategy,
        address teamWallet,
        address spotlightWallet,
        address owner
    ) internal pure returns (bytes memory) {
        return bytes.concat(
            type(BoredStrategyHook).creationCode,
            abi.encode(IPoolManager(poolManager), strategy, teamWallet, spotlightWallet, owner)
        );
    }
}

Tags:
DeFi, Swap, Factory|addr:0x774182db60689c5596b129818a3def1585eb4bbb|verified:true|block:23386420|tx:0x76a81fb69d67bbf86fd5778e46f5b0dd5759395c5fb2d3b564dec5bbfab06879|first_check:1758187882

Submitted on: 2025-09-18 11:31:24

Comments

Log in to comment.

No comments yet.