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)
);
}
}
Submitted on: 2025-09-18 11:31:24
Comments
Log in to comment.
No comments yet.