Burrowsource, exactly what is deployedall files

src/BurrowHook.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// -----------------------------------------------------------------------------------------------
// This file is self-contained on purpose: the repository vendors no v4-core, v4-periphery or
// OpenZeppelin, and the build runs offline. The pieces below are minimal, ABI-compatible ports of
// the Uniswap v4 types (PoolKey, SwapParams, BalanceDelta, Currency), the v4 Hooks permission
// flags, v4-periphery's BaseHook, and OpenZeppelin's SafeERC20.forceApprove, ReentrancyGuard and
// Ownable2Step. Names are prefixed so they never clash with the real libraries when the
// integrator adds them. Encodings, selectors and flag bits match Uniswap v4 exactly.
// -----------------------------------------------------------------------------------------------

/// @dev Uniswap v4 `Currency` (an address, native ETH is address(0)).
type BurrowCurrency is address;

/// @dev Uniswap v4 `BalanceDelta`: amount0 in the upper 128 bits, amount1 in the lower 128 bits.
type BurrowBalanceDelta is int256;

/// @dev Uniswap v4 `PoolKey`.
struct BurrowPoolKey {
    BurrowCurrency currency0;
    BurrowCurrency currency1;
    uint24 fee;
    int24 tickSpacing;
    address hooks;
}

/// @dev Uniswap v4 `SwapParams` (IPoolManager.SwapParams / PoolOperation.SwapParams).
struct BurrowSwapParams {
    bool zeroForOne;
    int256 amountSpecified;
    uint160 sqrtPriceLimitX96;
}

/// @dev Uniswap v4 `ModifyLiquidityParams`.
struct BurrowModifyLiquidityParams {
    int24 tickLower;
    int24 tickUpper;
    int256 liquidityDelta;
    bytes32 salt;
}

/// @dev Uniswap v4 `BeforeSwapDelta`.
type BurrowBeforeSwapDelta is int256;

library BurrowBalanceDeltaLib {
    function amount0(BurrowBalanceDelta d) internal pure returns (int128 a0) {
        assembly ("memory-safe") {
            a0 := sar(128, d)
        }
    }

    function amount1(BurrowBalanceDelta d) internal pure returns (int128 a1) {
        assembly ("memory-safe") {
            a1 := signextend(15, d)
        }
    }
}

library BurrowPoolIdLib {
    /// @dev Same as v4 PoolIdLibrary.toId: keccak256 of the 5-word PoolKey.
    function toId(BurrowPoolKey memory key) internal pure returns (bytes32 id) {
        assembly ("memory-safe") {
            id := keccak256(key, 0xa0)
        }
    }
}

/// @dev The subset of the v4 PoolManager this hook calls.
interface IBurrowPoolManager {
    function take(BurrowCurrency currency, address to, uint256 amount) external;
}

/// @dev Uniswap v4 `Hooks.Permissions` and flag bits (v4-core src/libraries/Hooks.sol).
library BurrowHooks {
    uint160 internal constant ALL_HOOK_MASK = uint160((1 << 14) - 1);

    uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 13;
    uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 12;
    uint160 internal constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 11;
    uint160 internal constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 10;
    uint160 internal constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 9;
    uint160 internal constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 8;
    uint160 internal constant BEFORE_SWAP_FLAG = 1 << 7;
    uint160 internal constant AFTER_SWAP_FLAG = 1 << 6;
    uint160 internal constant BEFORE_DONATE_FLAG = 1 << 5;
    uint160 internal constant AFTER_DONATE_FLAG = 1 << 4;
    uint160 internal constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 3;
    uint160 internal constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2;
    uint160 internal constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 1;
    uint160 internal constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 0;

    struct Permissions {
        bool beforeInitialize;
        bool afterInitialize;
        bool beforeAddLiquidity;
        bool afterAddLiquidity;
        bool beforeRemoveLiquidity;
        bool afterRemoveLiquidity;
        bool beforeSwap;
        bool afterSwap;
        bool beforeDonate;
        bool afterDonate;
        bool beforeSwapReturnDelta;
        bool afterSwapReturnDelta;
        bool afterAddLiquidityReturnDelta;
        bool afterRemoveLiquidityReturnDelta;
    }

    error HookAddressNotValid(address hooks);

    function toFlags(Permissions memory p) internal pure returns (uint160 flags) {
        if (p.beforeInitialize) flags |= BEFORE_INITIALIZE_FLAG;
        if (p.afterInitialize) flags |= AFTER_INITIALIZE_FLAG;
        if (p.beforeAddLiquidity) flags |= BEFORE_ADD_LIQUIDITY_FLAG;
        if (p.afterAddLiquidity) flags |= AFTER_ADD_LIQUIDITY_FLAG;
        if (p.beforeRemoveLiquidity) flags |= BEFORE_REMOVE_LIQUIDITY_FLAG;
        if (p.afterRemoveLiquidity) flags |= AFTER_REMOVE_LIQUIDITY_FLAG;
        if (p.beforeSwap) flags |= BEFORE_SWAP_FLAG;
        if (p.afterSwap) flags |= AFTER_SWAP_FLAG;
        if (p.beforeDonate) flags |= BEFORE_DONATE_FLAG;
        if (p.afterDonate) flags |= AFTER_DONATE_FLAG;
        if (p.beforeSwapReturnDelta) flags |= BEFORE_SWAP_RETURNS_DELTA_FLAG;
        if (p.afterSwapReturnDelta) flags |= AFTER_SWAP_RETURNS_DELTA_FLAG;
        if (p.afterAddLiquidityReturnDelta) flags |= AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG;
        if (p.afterRemoveLiquidityReturnDelta) flags |= AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG;
    }

    /// @dev Equivalent to v4 Hooks.validateHookPermissions: every one of the 14 low address bits
    /// must equal the declared permission.
    function validateHookPermissions(address self, Permissions memory p) internal pure {
        if (uint160(self) & ALL_HOOK_MASK != toFlags(p)) revert HookAddressNotValid(self);
    }
}

/// @dev Port of v4-periphery `BaseHook`: validates its own address against getHookPermissions at
/// construction, gates every callback to the PoolManager and reverts on any unimplemented hook.
abstract contract BurrowBaseHook {
    error NotPoolManager();
    error HookNotImplemented();

    IBurrowPoolManager public immutable poolManager;

    constructor(IBurrowPoolManager _poolManager) {
        poolManager = _poolManager;
        validateHookAddress(this);
    }

    modifier onlyPoolManager() {
        if (msg.sender != address(poolManager)) revert NotPoolManager();
        _;
    }

    function getHookPermissions() public pure virtual returns (BurrowHooks.Permissions memory);

    function validateHookAddress(BurrowBaseHook _this) internal pure virtual {
        BurrowHooks.validateHookPermissions(address(_this), getHookPermissions());
    }

    function beforeInitialize(address, BurrowPoolKey calldata, uint160) external virtual returns (bytes4) {
        revert HookNotImplemented();
    }

    function afterInitialize(address, BurrowPoolKey calldata, uint160, int24) external virtual returns (bytes4) {
        revert HookNotImplemented();
    }

    function beforeAddLiquidity(address, BurrowPoolKey calldata, BurrowModifyLiquidityParams calldata, bytes calldata)
        external
        virtual
        returns (bytes4)
    {
        revert HookNotImplemented();
    }

    function afterAddLiquidity(
        address,
        BurrowPoolKey calldata,
        BurrowModifyLiquidityParams calldata,
        BurrowBalanceDelta,
        BurrowBalanceDelta,
        bytes calldata
    ) external virtual returns (bytes4, BurrowBalanceDelta) {
        revert HookNotImplemented();
    }

    function beforeRemoveLiquidity(
        address,
        BurrowPoolKey calldata,
        BurrowModifyLiquidityParams calldata,
        bytes calldata
    ) external virtual returns (bytes4) {
        revert HookNotImplemented();
    }

    function afterRemoveLiquidity(
        address,
        BurrowPoolKey calldata,
        BurrowModifyLiquidityParams calldata,
        BurrowBalanceDelta,
        BurrowBalanceDelta,
        bytes calldata
    ) external virtual returns (bytes4, BurrowBalanceDelta) {
        revert HookNotImplemented();
    }

    function beforeSwap(address, BurrowPoolKey calldata, BurrowSwapParams calldata, bytes calldata)
        external
        virtual
        returns (bytes4, BurrowBeforeSwapDelta, uint24)
    {
        revert HookNotImplemented();
    }

    function afterSwap(
        address sender,
        BurrowPoolKey calldata key,
        BurrowSwapParams calldata params,
        BurrowBalanceDelta delta,
        bytes calldata hookData
    ) external onlyPoolManager returns (bytes4, int128) {
        return _afterSwap(sender, key, params, delta, hookData);
    }

    function beforeDonate(address, BurrowPoolKey calldata, uint256, uint256, bytes calldata)
        external
        virtual
        returns (bytes4)
    {
        revert HookNotImplemented();
    }

    function afterDonate(address, BurrowPoolKey calldata, uint256, uint256, bytes calldata)
        external
        virtual
        returns (bytes4)
    {
        revert HookNotImplemented();
    }

    function _afterSwap(address, BurrowPoolKey calldata, BurrowSwapParams calldata, BurrowBalanceDelta, bytes calldata)
        internal
        virtual
        returns (bytes4, int128)
    {
        revert HookNotImplemented();
    }
}

/// @dev Minimal ERC20 surface the hook needs.
interface IBurrowHookERC20 {
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
}

/// @notice The single function of BurrowVault this hook relies on: pull SQK from the caller and
/// raise the bSQK share price without minting.
interface IBurrowVaultFeeSink {
    function donate(uint256 amount) external;
}

/// @dev Port of OpenZeppelin ReentrancyGuard (v5 semantics).
abstract contract BurrowHookReentrancyGuard {
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;
    uint256 private _status = NOT_ENTERED;

    error ReentrancyGuardReentrantCall();

    modifier nonReentrant() {
        if (_status == ENTERED) revert ReentrancyGuardReentrantCall();
        _status = ENTERED;
        _;
        _status = NOT_ENTERED;
    }
}

/// @dev Port of OpenZeppelin Ownable + Ownable2Step (v5 semantics, same errors and events).
abstract contract BurrowHookOwnable2Step {
    address private _owner;
    address private _pendingOwner;

    error OwnableUnauthorizedAccount(address account);
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor(address initialOwner) {
        if (initialOwner == address(0)) revert OwnableInvalidOwner(address(0));
        _transferOwnership(initialOwner);
    }

    modifier onlyOwner() {
        if (msg.sender != _owner) revert OwnableUnauthorizedAccount(msg.sender);
        _;
    }

    function owner() public view returns (address) {
        return _owner;
    }

    function pendingOwner() public view returns (address) {
        return _pendingOwner;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(_owner, newOwner);
    }

    function acceptOwnership() external {
        if (msg.sender != _pendingOwner) revert OwnableUnauthorizedAccount(msg.sender);
        _transferOwnership(msg.sender);
    }

    function _transferOwnership(address newOwner) private {
        delete _pendingOwner;
        address old = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(old, newOwner);
    }
}

/// @title BurrowHook
/// @notice Uniswap v4 hook for the SQK pool. After every exact-input swap whose output currency is
/// SQK it skims `hookFeeBps` of the output, takes it from the PoolManager and donates it to the
/// Burrow vault, raising the bSQK share price. Swaps that do not pay out SQK are left untouched.
/// @dev UNAUDITED, TESTNET ONLY. The hook address must carry exactly the AFTER_SWAP and
/// AFTER_SWAP_RETURNS_DELTA flags (0x44 in the low 14 bits); deploy it through CREATE2 with a salt
/// mined by HookMiner. A v4 afterSwap delta can only adjust the unspecified currency, so on
/// exact-output swaps (where the SQK output is the specified amount) no fee is taken.
contract BurrowHook is BurrowBaseHook, BurrowHookReentrancyGuard, BurrowHookOwnable2Step {
    using BurrowBalanceDeltaLib for BurrowBalanceDelta;
    using BurrowPoolIdLib for BurrowPoolKey;

    /// @notice Hard cap on the hook fee: 1 percent of the SQK output.
    uint16 public constant MAX_HOOK_FEE_BPS = 100;
    uint256 internal constant BPS = 10_000;

    /// @notice The SQK token, the only currency this hook ever skims.
    IBurrowHookERC20 public immutable sqk;
    /// @notice The Burrow vault every fee is donated to. Fixed at construction.
    IBurrowVaultFeeSink public immutable vault;

    /// @notice Fee skimmed from SQK output, in basis points, at most MAX_HOOK_FEE_BPS.
    uint16 public hookFeeBps;

    error ZeroAddress();
    error FeeTooHigh(uint16 feeBps, uint16 maxBps);
    error NothingToFlush();
    error ApproveFailed();

    event HookFeeSet(uint16 oldFeeBps, uint16 newFeeBps);
    event FeeDonated(bytes32 indexed poolId, uint256 amount);
    /// @notice Emitted when the vault refused a donation; the fee stays here until flush().
    event FeeHeld(bytes32 indexed poolId, uint256 amount);
    event Flushed(uint256 amount);

    /// @param poolManager_ Uniswap v4 PoolManager (Sepolia 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543).
    /// @param sqk_ SQK token (Sepolia 0x5501e1fa81be1c3f49225fa7668da0245d07db60).
    /// @param vault_ BurrowVault, the fee sink.
    /// @param hookFeeBps_ Initial fee, at most MAX_HOOK_FEE_BPS.
    /// @param owner_ Owner allowed to tune hookFeeBps within its cap.
    constructor(address poolManager_, address sqk_, address vault_, uint16 hookFeeBps_, address owner_)
        BurrowBaseHook(IBurrowPoolManager(poolManager_))
        BurrowHookOwnable2Step(owner_)
    {
        if (poolManager_ == address(0) || sqk_ == address(0) || vault_ == address(0)) revert ZeroAddress();
        if (hookFeeBps_ > MAX_HOOK_FEE_BPS) revert FeeTooHigh(hookFeeBps_, MAX_HOOK_FEE_BPS);
        sqk = IBurrowHookERC20(sqk_);
        vault = IBurrowVaultFeeSink(vault_);
        hookFeeBps = hookFeeBps_;
        emit HookFeeSet(0, hookFeeBps_);
    }

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

    /// @notice The exact low-14-bit flag set this hook's address must carry (AFTER_SWAP | AFTER_SWAP_RETURNS_DELTA).
    function hookFlags() external pure returns (uint160) {
        return BurrowHooks.AFTER_SWAP_FLAG | BurrowHooks.AFTER_SWAP_RETURNS_DELTA_FLAG;
    }

    /// @notice Set the hook fee, bounded by MAX_HOOK_FEE_BPS.
    function setHookFeeBps(uint16 newFeeBps) external onlyOwner {
        if (newFeeBps > MAX_HOOK_FEE_BPS) revert FeeTooHigh(newFeeBps, MAX_HOOK_FEE_BPS);
        emit HookFeeSet(hookFeeBps, newFeeBps);
        hookFeeBps = newFeeBps;
    }

    /// @notice Fee this hook would skim from an SQK output of `outputAmount`.
    function quoteFee(uint256 outputAmount) public view returns (uint256) {
        return outputAmount * hookFeeBps / BPS;
    }

    /// @notice Donate any SQK held here (fees the vault refused earlier) to the vault. Anyone may call.
    function flush() external nonReentrant {
        uint256 amount = sqk.balanceOf(address(this));
        if (amount == 0) revert NothingToFlush();
        _forceApprove(address(vault), amount);
        vault.donate(amount);
        emit Flushed(amount);
    }

    function _afterSwap(
        address,
        BurrowPoolKey calldata key,
        BurrowSwapParams calldata params,
        BurrowBalanceDelta delta,
        bytes calldata
    ) internal override nonReentrant returns (bytes4, int128) {
        // The returned delta applies to the unspecified currency, which is the output only on exact input.
        if (params.amountSpecified >= 0) return (BurrowBaseHook.afterSwap.selector, 0);

        BurrowCurrency output = params.zeroForOne ? key.currency1 : key.currency0;
        if (BurrowCurrency.unwrap(output) != address(sqk)) return (BurrowBaseHook.afterSwap.selector, 0);

        int128 outputAmount = params.zeroForOne ? delta.amount1() : delta.amount0();
        if (outputAmount <= 0) return (BurrowBaseHook.afterSwap.selector, 0);

        uint256 fee = quoteFee(uint256(uint128(outputAmount)));
        if (fee == 0) return (BurrowBaseHook.afterSwap.selector, 0);

        // Returning +fee credits this hook in SQK; take() settles that credit to this contract.
        poolManager.take(output, address(this), fee);

        bytes32 poolId = key.toId();
        _forceApprove(address(vault), fee);
        // A failing vault must not brick the pool: keep the fee for flush() instead.
        try vault.donate(fee) {
            emit FeeDonated(poolId, fee);
        } catch {
            _forceApprove(address(vault), 0);
            emit FeeHeld(poolId, fee);
        }

        // fee <= 1% of a positive int128, so the cast back is safe.
        return (BurrowBaseHook.afterSwap.selector, int128(int256(fee)));
    }

    /// @dev OpenZeppelin SafeERC20.forceApprove semantics: tolerate tokens returning nothing and
    /// tokens that require resetting the allowance to zero first.
    function _forceApprove(address spender, uint256 value) private {
        if (!_tryApprove(spender, value)) {
            if (!_tryApprove(spender, 0) || !_tryApprove(spender, value)) revert ApproveFailed();
        }
    }

    function _tryApprove(address spender, uint256 value) private returns (bool) {
        (bool ok, bytes memory ret) = address(sqk).call(abi.encodeCall(IBurrowHookERC20.approve, (spender, value)));
        return ok && (ret.length == 0 ? address(sqk).code.length > 0 : abi.decode(ret, (bool)));
    }
}