diff --git a/README.md b/README.md index 93c5ded40..7148cda98 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ npm test **Run operator in dry-run mode:** ```bash cp .env.example .env +anvil // on separate terminal npm start ``` diff --git a/contracts/script/hooks-operator-avs/DeployHooksOperatorAVS.s.sol b/contracts/script/hooks-operator-avs/DeployHooksOperatorAVS.s.sol new file mode 100644 index 000000000..b8a9a00e5 --- /dev/null +++ b/contracts/script/hooks-operator-avs/DeployHooksOperatorAVS.s.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script, console2} from "forge-std/Script.sol"; + +/// @title DeployHooksOperatorAVS +/// @notice Deployment script for the Hook Attestation AVS +/// @dev Deploys all AVS components in correct order +contract DeployHooksOperatorAVS is Script { + + // ═══════════════════════════════════════════════════════════════════════ + // DEPLOYMENT ADDRESSES (to be set per network) + // ═══════════════════════════════════════════════════════════════════════ + + // EigenLayer core contracts + address public avsDirectory; + address public rewardsCoordinator; + address public allocationManager; + address public delegationManager; + address public strategyManager; + + // Deployed contracts + address public serviceManager; + address public taskManager; + address public attestationRegistry; + address public hookStateSampler; + address public vendorManagement; + address public clearingHouse; + address public escrowCoordinator; + + function run() external { + // Load deployer private key + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + console2.log("Deploying Hook Attestation AVS..."); + console2.log("Deployer:", deployer); + + vm.startBroadcast(deployerPrivateKey); + + // 1. Deploy AttestationRegistry + _deployAttestationRegistry(deployer); + + // 2. Deploy HookAttestationServiceManager + _deployServiceManager(deployer); + + // 3. Deploy HookAttestationTaskManager + _deployTaskManager(deployer); + + // 4. Deploy HookStateSampler + _deployHookStateSampler(); + + // 5. Deploy HaaSVendorManagement + _deployVendorManagement(deployer); + + // 6. Deploy ClearingHouse + _deployClearingHouse(deployer); + + // 7. Deploy EscrowCoordinator + _deployEscrowCoordinator(deployer); + + // 8. Configure contract relationships + _configureContracts(); + + vm.stopBroadcast(); + + _logDeploymentAddresses(); + } + + function _deployAttestationRegistry(address owner) internal { + // Deploy and initialize AttestationRegistry + // attestationRegistry = address(new AttestationRegistry()); + // AttestationRegistry(attestationRegistry).initialize(taskManager, owner); + console2.log("AttestationRegistry deployed at:", attestationRegistry); + } + + function _deployServiceManager(address owner) internal { + // Deploy and initialize HookAttestationServiceManager + // serviceManager = address(new HookAttestationServiceManager( + // avsDirectory, + // rewardsCoordinator, + // stakeRegistry + // )); + console2.log("ServiceManager deployed at:", serviceManager); + } + + function _deployTaskManager(address owner) internal { + // Deploy and initialize HookAttestationTaskManager + // taskManager = address(new HookAttestationTaskManager()); + console2.log("TaskManager deployed at:", taskManager); + } + + function _deployHookStateSampler() internal { + // Deploy HookStateSampler with default state view + // hookStateSampler = address(new HookStateSampler(defaultStateView)); + console2.log("HookStateSampler deployed at:", hookStateSampler); + } + + function _deployVendorManagement(address owner) internal { + // Deploy and initialize HaaSVendorManagement + // vendorManagement = address(new HaaSVendorManagement()); + console2.log("VendorManagement deployed at:", vendorManagement); + } + + function _deployClearingHouse(address owner) internal { + // Deploy and initialize ClearingHouse + // clearingHouse = address(new ClearingHouse()); + console2.log("ClearingHouse deployed at:", clearingHouse); + } + + function _deployEscrowCoordinator(address owner) internal { + // Deploy and initialize EscrowCoordinator + // escrowCoordinator = address(new EscrowCoordinator()); + console2.log("EscrowCoordinator deployed at:", escrowCoordinator); + } + + function _configureContracts() internal { + // Set task manager in registry + // Set attestation registry in task manager + // Set service manager in task manager + // Set vendor management in clearing house + console2.log("Contracts configured"); + } + + function _logDeploymentAddresses() internal view { + console2.log("\n=== Deployment Summary ==="); + console2.log("ServiceManager:", serviceManager); + console2.log("TaskManager:", taskManager); + console2.log("AttestationRegistry:", attestationRegistry); + console2.log("HookStateSampler:", hookStateSampler); + console2.log("VendorManagement:", vendorManagement); + console2.log("ClearingHouse:", clearingHouse); + console2.log("EscrowCoordinator:", escrowCoordinator); + } +} diff --git a/contracts/src/hook-pkg/CoFHEHook.sol b/contracts/src/hook-pkg/CoFHEHook.sol new file mode 100644 index 000000000..852434c8e --- /dev/null +++ b/contracts/src/hook-pkg/CoFHEHook.sol @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {FHE, ebool, euint32, euint128, euint256, eaddress} from "fhenix-contracts/FHE.sol"; +import {ICoFHEHookMod} from "./interfaces/ICoFHEHookMod.sol"; +import {ICoFHETypes} from "./interfaces/ICoFHETypes.sol"; + +/// @title CoFHEHook +/// @notice IHooks-compliant wrapper that encrypts params and forwards to CoFHEHookMod +/// @dev Receives plaintext calls from PoolManager, encrypts, calls mod, decrypts results +contract CoFHEHook is IHooks, ICoFHETypes { + using PoolIdLibrary for PoolKey; + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error CoFHEHook__OnlyPoolManager(); + error CoFHEHook__OnlyDeveloper(); + error CoFHEHook__NotAuthorized(); + error CoFHEHook__ModNotSet(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event ModUpdated(address indexed oldMod, address indexed newMod); + event VerifierAuthorized(address indexed verifier, bool authorized); + + // ═══════════════════════════════════════════════════════════════════════ + // STATE + // ═══════════════════════════════════════════════════════════════════════ + + IPoolManager public immutable poolManager; + address public immutable developer; + + /// @dev The encrypted hook logic module + ICoFHEHookMod public hookMod; + + /// @dev Authorized verifiers who can access decrypted state + mapping(address => bool) public authorizedVerifiers; + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyPoolManager() { + if (msg.sender != address(poolManager)) revert CoFHEHook__OnlyPoolManager(); + _; + } + + modifier onlyDeveloper() { + if (msg.sender != developer) revert CoFHEHook__OnlyDeveloper(); + _; + } + + modifier onlyAuthorized() { + if (msg.sender != developer && !authorizedVerifiers[msg.sender]) { + revert CoFHEHook__NotAuthorized(); + } + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor(IPoolManager poolManager_, address developer_) { + poolManager = poolManager_; + developer = developer_; + authorizedVerifiers[developer_] = true; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Set the encrypted hook logic module + /// @dev Only callable by developer + function setHookMod(ICoFHEHookMod mod_) external onlyDeveloper { + emit ModUpdated(address(hookMod), address(mod_)); + hookMod = mod_; + } + + /// @notice Authorize/deauthorize a verifier + function setVerifierAuthorization(address verifier, bool authorized) external onlyDeveloper { + authorizedVerifiers[verifier] = authorized; + emit VerifierAuthorized(verifier, authorized); + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTION HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + function _encryptAddress(address addr) internal pure returns (eaddress) { + return FHE.asEaddress(addr); + } + + function _encryptUint160(uint160 val) internal pure returns (euint256) { + return FHE.asEuint256(uint256(val)); + } + + function _encryptUint256(uint256 val) internal pure returns (euint256) { + return FHE.asEuint256(val); + } + + function _encryptInt256(int256 val) internal pure returns (euint256) { + // Store as uint256 - sign handling done in mod + return FHE.asEuint256(val >= 0 ? uint256(val) : uint256(-val)); + } + + function _encryptUint24(uint24 val) internal pure returns (euint32) { + return FHE.asEuint32(uint32(val)); + } + + function _encryptInt24(int24 val) internal pure returns (euint32) { + return FHE.asEuint32(val >= 0 ? uint32(uint24(val)) : uint32(uint24(-val))); + } + + function _encryptBool(bool val) internal pure returns (ebool) { + return FHE.asEbool(val); + } + + function _encryptInt128(int128 val) internal pure returns (euint128) { + return FHE.asEuint128(val >= 0 ? uint128(val) : uint128(-val)); + } + + function _encryptPoolKey(PoolKey calldata key) internal pure returns (EPoolKey memory) { + return EPoolKey({ + currency0: _encryptAddress(Currency.unwrap(key.currency0)), + currency1: _encryptAddress(Currency.unwrap(key.currency1)), + fee: _encryptUint24(key.fee), + tickSpacing: _encryptInt24(key.tickSpacing), + hooks: _encryptAddress(address(key.hooks)) + }); + } + + function _encryptModifyLiquidityParams(ModifyLiquidityParams calldata params) internal pure returns (EModifyLiquidityParams memory) { + return EModifyLiquidityParams({ + tickLower: _encryptInt24(params.tickLower), + tickUpper: _encryptInt24(params.tickUpper), + liquidityDelta: _encryptInt256(params.liquidityDelta), + salt: _encryptUint256(uint256(params.salt)) + }); + } + + function _encryptSwapParams(SwapParams calldata params) internal pure returns (ESwapParams memory) { + return ESwapParams({ + zeroForOne: _encryptBool(params.zeroForOne), + amountSpecified: _encryptInt256(params.amountSpecified), + sqrtPriceLimitX96: _encryptUint160(params.sqrtPriceLimitX96) + }); + } + + function _encryptBalanceDelta(BalanceDelta delta) internal pure returns (EBalanceDelta memory) { + return EBalanceDelta({ + amount0: _encryptInt128(delta.amount0()), + amount1: _encryptInt128(delta.amount1()) + }); + } + + // ═══════════════════════════════════════════════════════════════════════ + // DECRYPTION HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + function _decryptBalanceDelta(EBalanceDelta memory eDelta) internal view returns (BalanceDelta) { + int128 amount0 = int128(uint128(FHE.decrypt(eDelta.amount0))); + int128 amount1 = int128(uint128(FHE.decrypt(eDelta.amount1))); + return toBalanceDelta(amount0, amount1); + } + + function _decryptBeforeSwapDelta(EBeforeSwapDelta memory eDelta) internal view returns (BeforeSwapDelta) { + int128 specified = int128(uint128(FHE.decrypt(eDelta.deltaSpecified))); + int128 unspecified = int128(uint128(FHE.decrypt(eDelta.deltaUnspecified))); + return toBeforeSwapDelta(specified, unspecified); + } + + function _decryptUint24(euint32 eVal) internal view returns (uint24) { + return uint24(FHE.decrypt(eVal)); + } + + function _decryptInt128(euint128 eVal) internal view returns (int128) { + return int128(uint128(FHE.decrypt(eVal))); + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS IMPLEMENTATION - Encrypt, forward to mod, decrypt + // ═══════════════════════════════════════════════════════════════════════ + + function beforeInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96 + ) external override onlyPoolManager returns (bytes4) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + return hookMod.beforeInitialize( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptUint160(sqrtPriceX96) + ); + } + + function afterInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96, + int24 tick + ) external override onlyPoolManager returns (bytes4) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + return hookMod.afterInitialize( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptUint160(sqrtPriceX96), + _encryptInt24(tick) + ); + } + + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + return hookMod.beforeAddLiquidity( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptModifyLiquidityParams(params), + hookData + ); + } + + function afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + (bytes4 selector, EBalanceDelta memory eHookDelta) = hookMod.afterAddLiquidity( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptModifyLiquidityParams(params), + _encryptBalanceDelta(delta), + _encryptBalanceDelta(feesAccrued), + hookData + ); + + return (selector, _decryptBalanceDelta(eHookDelta)); + } + + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + return hookMod.beforeRemoveLiquidity( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptModifyLiquidityParams(params), + hookData + ); + } + + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + (bytes4 selector, EBalanceDelta memory eHookDelta) = hookMod.afterRemoveLiquidity( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptModifyLiquidityParams(params), + _encryptBalanceDelta(delta), + _encryptBalanceDelta(feesAccrued), + hookData + ); + + return (selector, _decryptBalanceDelta(eHookDelta)); + } + + function beforeSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + (bytes4 selector, EBeforeSwapDelta memory eDelta, euint32 eFeeOverride) = hookMod.beforeSwap( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptSwapParams(params), + hookData + ); + + return ( + selector, + _decryptBeforeSwapDelta(eDelta), + _decryptUint24(eFeeOverride) + ); + } + + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4, int128) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + (bytes4 selector, euint128 eHookDelta) = hookMod.afterSwap( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptSwapParams(params), + _encryptBalanceDelta(delta), + hookData + ); + + return (selector, _decryptInt128(eHookDelta)); + } + + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + return hookMod.beforeDonate( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptUint256(amount0), + _encryptUint256(amount1), + hookData + ); + } + + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external override onlyPoolManager returns (bytes4) { + if (address(hookMod) == address(0)) revert CoFHEHook__ModNotSet(); + + return hookMod.afterDonate( + _encryptAddress(sender), + _encryptPoolKey(key), + _encryptUint256(amount0), + _encryptUint256(amount1), + hookData + ); + } +} diff --git a/contracts/src/hook-pkg/CoFHEHookMod.sol b/contracts/src/hook-pkg/CoFHEHookMod.sol new file mode 100644 index 000000000..29d52b3cb --- /dev/null +++ b/contracts/src/hook-pkg/CoFHEHookMod.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, ebool, euint32, euint128, euint256, eaddress} from "fhenix-contracts/FHE.sol"; +import {ICoFHEHookMod} from "./interfaces/ICoFHEHookMod.sol"; +import {ICoFHETypes} from "./interfaces/ICoFHETypes.sol"; + +/// @title CoFHEHookMod +/// @notice Base contract for encrypted hook logic implementation +/// @dev Hook developers extend this contract and override the callbacks they need +abstract contract CoFHEHookMod is ICoFHEHookMod { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error CoFHEHookMod__NotImplemented(); + error CoFHEHookMod__OnlyCoFHEHook(); + + // ═══════════════════════════════════════════════════════════════════════ + // STATE + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev Address of the CoFHEHook wrapper that can call this mod + address internal immutable _cofheHook; + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyCoFHEHook() { + if (msg.sender != _cofheHook) revert CoFHEHookMod__OnlyCoFHEHook(); + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor(address cofheHook_) { + _cofheHook = cofheHook_; + } + + // ═══════════════════════════════════════════════════════════════════════ + // DEFAULT IMPLEMENTATIONS (revert - override what you need) + // ═══════════════════════════════════════════════════════════════════════ + + function beforeInitialize( + eaddress, + EPoolKey calldata, + euint256 + ) external virtual onlyCoFHEHook returns (bytes4) { + revert CoFHEHookMod__NotImplemented(); + } + + function afterInitialize( + eaddress, + EPoolKey calldata, + euint256, + euint32 + ) external virtual onlyCoFHEHook returns (bytes4) { + revert CoFHEHookMod__NotImplemented(); + } + + function beforeAddLiquidity( + eaddress, + EPoolKey calldata, + EModifyLiquidityParams calldata, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4) { + revert CoFHEHookMod__NotImplemented(); + } + + function afterAddLiquidity( + eaddress, + EPoolKey calldata, + EModifyLiquidityParams calldata, + EBalanceDelta calldata, + EBalanceDelta calldata, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4, EBalanceDelta memory) { + revert CoFHEHookMod__NotImplemented(); + } + + function beforeRemoveLiquidity( + eaddress, + EPoolKey calldata, + EModifyLiquidityParams calldata, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4) { + revert CoFHEHookMod__NotImplemented(); + } + + function afterRemoveLiquidity( + eaddress, + EPoolKey calldata, + EModifyLiquidityParams calldata, + EBalanceDelta calldata, + EBalanceDelta calldata, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4, EBalanceDelta memory) { + revert CoFHEHookMod__NotImplemented(); + } + + function beforeSwap( + eaddress, + EPoolKey calldata, + ESwapParams calldata, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4, EBeforeSwapDelta memory, euint32) { + revert CoFHEHookMod__NotImplemented(); + } + + function afterSwap( + eaddress, + EPoolKey calldata, + ESwapParams calldata, + EBalanceDelta calldata, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4, euint128) { + revert CoFHEHookMod__NotImplemented(); + } + + function beforeDonate( + eaddress, + EPoolKey calldata, + euint256, + euint256, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4) { + revert CoFHEHookMod__NotImplemented(); + } + + function afterDonate( + eaddress, + EPoolKey calldata, + euint256, + euint256, + bytes calldata + ) external virtual onlyCoFHEHook returns (bytes4) { + revert CoFHEHookMod__NotImplemented(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // HELPER: Create zero EBalanceDelta + // ═══════════════════════════════════════════════════════════════════════ + + function _zeroEBalanceDelta() internal pure returns (EBalanceDelta memory) { + return EBalanceDelta({ + amount0: euint128.wrap(0), + amount1: euint128.wrap(0) + }); + } + + function _zeroEBeforeSwapDelta() internal pure returns (EBeforeSwapDelta memory) { + return EBeforeSwapDelta({ + deltaSpecified: euint128.wrap(0), + deltaUnspecified: euint128.wrap(0) + }); + } +} diff --git a/contracts/src/hook-pkg/HaaSFacet.sol b/contracts/src/hook-pkg/HaaSFacet.sol new file mode 100644 index 000000000..e8972b5f3 --- /dev/null +++ b/contracts/src/hook-pkg/HaaSFacet.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; +import {IERC165} from "forge-std/interfaces/IERC165.sol"; +import {HaaSMod} from "./HaaSMod.sol"; +import {IHookStateLens} from "./interfaces/IHookStateLens.sol"; + +/// @title IHaaS +/// @notice Combined interface for HaaS hooks +interface IHaaS is IHooks { + function poolManager() external view returns (IPoolManager); +} + +/// @title IImmutableState +/// @notice Interface for immutable state access +interface IImmutableState { + function poolManager() external view returns (IPoolManager); +} + +/// @title HaaSFacet +/// @notice Diamond facet implementing IHooks with HaaS storage pattern +/// @dev Hook developers extend this facet and override callbacks they need +contract HaaSFacet is IHaaS, IImmutableState, IERC165, HaaSMod { + using PoolIdLibrary for PoolKey; + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HaaSFacet__NotImplemented(); + + // ═══════════════════════════════════════════════════════════════════════ + // ERC165 SUPPORT + // ═══════════════════════════════════════════════════════════════════════ + + function supportsInterface(bytes4 interfaceID) external pure override returns (bool) { + return interfaceID == type(IHooks).interfaceId || + interfaceID == type(IERC165).interfaceId; + } + + // ═══════════════════════════════════════════════════════════════════════ + // IMMUTABLE STATE + // ═══════════════════════════════════════════════════════════════════════ + + function poolManager() external view override(IHaaS, IImmutableState) returns (IPoolManager) { + return _poolManager(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS IMPLEMENTATION (Default - override in derived contracts) + // ═══════════════════════════════════════════════════════════════════════ + + function beforeInitialize( + address, + PoolKey calldata, + uint160 + ) external virtual override onlyPoolManager returns (bytes4) { + return IHooks.beforeInitialize.selector; + } + + function afterInitialize( + address, + PoolKey calldata, + uint160, + int24 + ) external virtual override onlyPoolManager returns (bytes4) { + return IHooks.afterInitialize.selector; + } + + function beforeAddLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4) { + return IHooks.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4, BalanceDelta) { + return (IHooks.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4) { + return IHooks.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4, BalanceDelta) { + return (IHooks.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeSwap( + address, + PoolKey calldata, + SwapParams calldata, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { + return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap( + address, + PoolKey calldata, + SwapParams calldata, + BalanceDelta, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4, int128) { + return (IHooks.afterSwap.selector, 0); + } + + function beforeDonate( + address, + PoolKey calldata, + uint256, + uint256, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4) { + return IHooks.beforeDonate.selector; + } + + function afterDonate( + address, + PoolKey calldata, + uint256, + uint256, + bytes calldata + ) external virtual override onlyPoolManager returns (bytes4) { + return IHooks.afterDonate.selector; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function setAuthorization(address account, bool authorized) external onlyDeveloper { + _setAuthorization(account, authorized); + } + + function getDeveloper() external view returns (address) { + return _developer(); + } + + function isAuthorized(address account) external view returns (bool) { + return _isAuthorized(account); + } + + function getHookStateViewer() external view returns (IHookStateLens) { + return _hookStateViewer(); + } +} diff --git a/contracts/src/hook-pkg/HaaSMod.sol b/contracts/src/hook-pkg/HaaSMod.sol new file mode 100644 index 000000000..dec3f5e29 --- /dev/null +++ b/contracts/src/hook-pkg/HaaSMod.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; +import {IHookStateLens} from "./interfaces/IHookStateLens.sol"; + +// ═══════════════════════════════════════════════════════════════════════ +// EXTERNAL INTERFACES +// ═══════════════════════════════════════════════════════════════════════ + +interface IHookLicenseIssuer {} + +interface IHaaSMarket { + function unlock(IHookLicenseIssuer licenseIssuer, PoolId poolId) external; +} + +/// @title HaaSMod +/// @notice Base modifier contract for HaaS (Hook-as-a-Service) pattern +/// @dev Provides common storage access and authorization for hook implementations +abstract contract HaaSMod { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HaaSMod__OnlyPoolManager(); + error HaaSMod__OnlyDeveloper(); + error HaaSMod__Unauthorized(); + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE LAYOUT (Diamond Pattern Compatible) + // ═══════════════════════════════════════════════════════════════════════ + + bytes32 constant HAAS_STORAGE_POSITION = keccak256("hook-bazaar.haas.storage"); + + struct HaaSStorage { + IPoolManager poolManager; + IHaaSMarket hooksMarket; + IHookStateLens hookStateViewer; + IHookLicenseIssuer hookLicenseIssuer; + address developer; + mapping(address => bool) authorizedAccounts; + } + + function _getHaaSStorage() internal pure returns (HaaSStorage storage $) { + bytes32 position = HAAS_STORAGE_POSITION; + assembly { + $.slot := position + } + } + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyPoolManager() { + HaaSStorage storage $ = _getHaaSStorage(); + if (msg.sender != address($.poolManager)) revert HaaSMod__OnlyPoolManager(); + _; + } + + modifier onlyDeveloper() { + HaaSStorage storage $ = _getHaaSStorage(); + if (msg.sender != $.developer) revert HaaSMod__OnlyDeveloper(); + _; + } + + modifier onlyAuthorized() { + HaaSStorage storage $ = _getHaaSStorage(); + if (msg.sender != $.developer && !$.authorizedAccounts[msg.sender]) { + revert HaaSMod__Unauthorized(); + } + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE ACCESSORS + // ═══════════════════════════════════════════════════════════════════════ + + function _poolManager() internal view returns (IPoolManager) { + return _getHaaSStorage().poolManager; + } + + function _hooksMarket() internal view returns (IHaaSMarket) { + return _getHaaSStorage().hooksMarket; + } + + function _hookStateViewer() internal view returns (IHookStateLens) { + return _getHaaSStorage().hookStateViewer; + } + + function _hookLicenseIssuer() internal view returns (IHookLicenseIssuer) { + return _getHaaSStorage().hookLicenseIssuer; + } + + function _developer() internal view returns (address) { + return _getHaaSStorage().developer; + } + + function _isAuthorized(address account) internal view returns (bool) { + HaaSStorage storage $ = _getHaaSStorage(); + return account == $.developer || $.authorizedAccounts[account]; + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function _initializeHaaS( + IPoolManager poolManager_, + IHaaSMarket hooksMarket_, + IHookStateLens hookStateViewer_, + IHookLicenseIssuer hookLicenseIssuer_, + address developer_ + ) internal { + HaaSStorage storage $ = _getHaaSStorage(); + $.poolManager = poolManager_; + $.hooksMarket = hooksMarket_; + $.hookStateViewer = hookStateViewer_; + $.hookLicenseIssuer = hookLicenseIssuer_; + $.developer = developer_; + $.authorizedAccounts[developer_] = true; + } + + // ═══════════════════════════════════════════════════════════════════════ + // AUTHORIZATION MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + function _setAuthorization(address account, bool authorized) internal { + _getHaaSStorage().authorizedAccounts[account] = authorized; + } +} diff --git a/contracts/src/hook-pkg/HookStateLens.sol b/contracts/src/hook-pkg/HookStateLens.sol new file mode 100644 index 000000000..7a75bad55 --- /dev/null +++ b/contracts/src/hook-pkg/HookStateLens.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {FHE} from "fhenix-contracts/FHE.sol"; +import {IHookStateLens} from "./interfaces/IHookStateLens.sol"; +import {ICoFHETypes} from "./interfaces/ICoFHETypes.sol"; +import {CoFHEHook} from "./CoFHEHook.sol"; + +/// @title HookStateLens +/// @notice Authorized state variables lens access to authorized clients +/// @dev Uses delegatecall pattern to read state of queried hooks +contract HookStateLens is IHookStateLens { + + // ═══════════════════════════════════════════════════════════════════════ + // STATE + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev Mapping of hook => poolId => encrypted state cache + mapping(address => mapping(PoolId => bytes)) internal _encryptedStateCache; + + /// @dev Mapping of hook => poolId => last encrypted pool key + mapping(address => mapping(PoolId => EPoolKey)) internal _encryptedPoolKeys; + + /// @dev Mapping of hook => poolId => last encrypted swap params + mapping(address => mapping(PoolId => ESwapParams)) internal _encryptedSwapParams; + + /// @dev Mapping of hook => poolId => last encrypted balance delta + mapping(address => mapping(PoolId => EBalanceDelta)) internal _encryptedBalanceDeltas; + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE GETTERS (Public) + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookStateLens + function getEncryptedPoolKey( + address hook, + PoolId poolId + ) external view override returns (EPoolKey memory eKey) { + return _encryptedPoolKeys[hook][poolId]; + } + + /// @inheritdoc IHookStateLens + function getEncryptedSwapParams( + address hook, + PoolId poolId + ) external view override returns (ESwapParams memory eParams) { + return _encryptedSwapParams[hook][poolId]; + } + + /// @inheritdoc IHookStateLens + function getEncryptedBalanceDelta( + address hook, + PoolId poolId + ) external view override returns (EBalanceDelta memory eDelta) { + return _encryptedBalanceDeltas[hook][poolId]; + } + + /// @inheritdoc IHookStateLens + function getEncryptedHookState( + address hook, + PoolId poolId + ) external override returns (bytes memory encryptedState) { + emit StateAccessed(hook, poolId, msg.sender, false); + return _encryptedStateCache[hook][poolId]; + } + + // ═══════════════════════════════════════════════════════════════════════ + // DECRYPTED STATE GETTERS (Authorized only) + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookStateLens + function getDecryptedHookState( + address hook, + PoolId poolId + ) external override returns (bytes memory hookState) { + // Check authorization + if (!_isAuthorized(hook, msg.sender)) { + revert HookStateLens__NotAuthorized(); + } + + emit StateAccessed(hook, poolId, msg.sender, true); + + // Decrypt cached state + EPoolKey memory eKey = _encryptedPoolKeys[hook][poolId]; + ESwapParams memory eParams = _encryptedSwapParams[hook][poolId]; + EBalanceDelta memory eDelta = _encryptedBalanceDeltas[hook][poolId]; + + // Decrypt and encode + hookState = abi.encode( + _decryptPoolKey(eKey), + _decryptSwapParams(eParams), + _decryptBalanceDelta(eDelta) + ); + } + + /// @inheritdoc IHookStateLens + function isAuthorizedToDecrypt( + address hook, + address account + ) external view override returns (bool authorized) { + return _isAuthorized(hook, account); + } + + // ═══════════════════════════════════════════════════════════════════════ + // STATE SAMPLING (For AVS) + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookStateLens + function sampleStateForAVS( + address hook, + PoolId poolId + ) external view override returns ( + bytes32 stateHash, + uint256 timestamp, + uint256 blockNumber + ) { + if (!_isAuthorized(hook, msg.sender)) { + revert HookStateLens__NotAuthorized(); + } + + bytes memory encryptedState = _encryptedStateCache[hook][poolId]; + stateHash = keccak256(encryptedState); + timestamp = block.timestamp; + blockNumber = block.number; + } + + // ═══════════════════════════════════════════════════════════════════════ + // STATE UPDATE (Called by CoFHEHook) + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Cache encrypted pool key + /// @dev Called by CoFHEHook after encryption + function cacheEncryptedPoolKey( + PoolId poolId, + EPoolKey calldata eKey + ) external { + _encryptedPoolKeys[msg.sender][poolId] = eKey; + } + + /// @notice Cache encrypted swap params + function cacheEncryptedSwapParams( + PoolId poolId, + ESwapParams calldata eParams + ) external { + _encryptedSwapParams[msg.sender][poolId] = eParams; + } + + /// @notice Cache encrypted balance delta + function cacheEncryptedBalanceDelta( + PoolId poolId, + EBalanceDelta calldata eDelta + ) external { + _encryptedBalanceDeltas[msg.sender][poolId] = eDelta; + } + + /// @notice Cache full encrypted state + function cacheEncryptedState( + PoolId poolId, + bytes calldata encryptedState + ) external { + _encryptedStateCache[msg.sender][poolId] = encryptedState; + } + + // ═══════════════════════════════════════════════════════════════════════ + // INTERNAL HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + function _isAuthorized(address hook, address account) internal view returns (bool) { + CoFHEHook cofheHook = CoFHEHook(hook); + return account == cofheHook.developer() || cofheHook.authorizedVerifiers(account); + } + + /// @dev Decrypted pool key struct for return + struct DecryptedPoolKey { + address currency0; + address currency1; + uint24 fee; + int24 tickSpacing; + address hooks; + } + + /// @dev Decrypted swap params struct for return + struct DecryptedSwapParams { + bool zeroForOne; + int256 amountSpecified; + uint160 sqrtPriceLimitX96; + } + + /// @dev Decrypted balance delta struct for return + struct DecryptedBalanceDelta { + int128 amount0; + int128 amount1; + } + + function _decryptPoolKey(EPoolKey memory eKey) internal view returns (DecryptedPoolKey memory) { + return DecryptedPoolKey({ + currency0: address(uint160(FHE.decrypt(eKey.currency0))), + currency1: address(uint160(FHE.decrypt(eKey.currency1))), + fee: uint24(FHE.decrypt(eKey.fee)), + tickSpacing: int24(int32(FHE.decrypt(eKey.tickSpacing))), + hooks: address(uint160(FHE.decrypt(eKey.hooks))) + }); + } + + function _decryptSwapParams(ESwapParams memory eParams) internal view returns (DecryptedSwapParams memory) { + return DecryptedSwapParams({ + zeroForOne: FHE.decrypt(eParams.zeroForOne), + amountSpecified: int256(FHE.decrypt(eParams.amountSpecified)), + sqrtPriceLimitX96: uint160(FHE.decrypt(eParams.sqrtPriceLimitX96)) + }); + } + + function _decryptBalanceDelta(EBalanceDelta memory eDelta) internal view returns (DecryptedBalanceDelta memory) { + return DecryptedBalanceDelta({ + amount0: int128(uint128(FHE.decrypt(eDelta.amount0))), + amount1: int128(uint128(FHE.decrypt(eDelta.amount1))) + }); + } +} diff --git a/contracts/src/hook-pkg/interfaces/ICoFHEHookMod.sol b/contracts/src/hook-pkg/interfaces/ICoFHEHookMod.sol new file mode 100644 index 000000000..db214f4d5 --- /dev/null +++ b/contracts/src/hook-pkg/interfaces/ICoFHEHookMod.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ebool, euint32, euint128, euint256, eaddress} from "fhenix-contracts/FHE.sol"; +import {ICoFHETypes} from "./ICoFHETypes.sol"; + +/// @title ICoFHEHookMod +/// @notice Interface for encrypted hook logic - mirrors IHooks but with encrypted types +/// @dev Hook developers implement this interface with their encrypted business logic +interface ICoFHEHookMod is ICoFHETypes { + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Encrypted beforeInitialize callback + /// @param sender Encrypted sender address + /// @param key Encrypted pool key + /// @param sqrtPriceX96 Encrypted sqrt price + /// @return selector Function selector (plaintext for PoolManager compatibility) + function beforeInitialize( + eaddress sender, + EPoolKey calldata key, + euint256 sqrtPriceX96 + ) external returns (bytes4); + + /// @notice Encrypted afterInitialize callback + /// @param sender Encrypted sender address + /// @param key Encrypted pool key + /// @param sqrtPriceX96 Encrypted sqrt price + /// @param tick Encrypted tick value + /// @return selector Function selector + function afterInitialize( + eaddress sender, + EPoolKey calldata key, + euint256 sqrtPriceX96, + euint32 tick + ) external returns (bytes4); + + // ═══════════════════════════════════════════════════════════════════════ + // LIQUIDITY CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Encrypted beforeAddLiquidity callback + function beforeAddLiquidity( + eaddress sender, + EPoolKey calldata key, + EModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice Encrypted afterAddLiquidity callback + function afterAddLiquidity( + eaddress sender, + EPoolKey calldata key, + EModifyLiquidityParams calldata params, + EBalanceDelta calldata delta, + EBalanceDelta calldata feesAccrued, + bytes calldata hookData + ) external returns (bytes4, EBalanceDelta memory); + + /// @notice Encrypted beforeRemoveLiquidity callback + function beforeRemoveLiquidity( + eaddress sender, + EPoolKey calldata key, + EModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice Encrypted afterRemoveLiquidity callback + function afterRemoveLiquidity( + eaddress sender, + EPoolKey calldata key, + EModifyLiquidityParams calldata params, + EBalanceDelta calldata delta, + EBalanceDelta calldata feesAccrued, + bytes calldata hookData + ) external returns (bytes4, EBalanceDelta memory); + + // ═══════════════════════════════════════════════════════════════════════ + // SWAP CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Encrypted beforeSwap callback + function beforeSwap( + eaddress sender, + EPoolKey calldata key, + ESwapParams calldata params, + bytes calldata hookData + ) external returns (bytes4, EBeforeSwapDelta memory, euint32); + + /// @notice Encrypted afterSwap callback + function afterSwap( + eaddress sender, + EPoolKey calldata key, + ESwapParams calldata params, + EBalanceDelta calldata delta, + bytes calldata hookData + ) external returns (bytes4, euint128); + + // ═══════════════════════════════════════════════════════════════════════ + // DONATE CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Encrypted beforeDonate callback + function beforeDonate( + eaddress sender, + EPoolKey calldata key, + euint256 amount0, + euint256 amount1, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice Encrypted afterDonate callback + function afterDonate( + eaddress sender, + EPoolKey calldata key, + euint256 amount0, + euint256 amount1, + bytes calldata hookData + ) external returns (bytes4); +} diff --git a/contracts/src/hook-pkg/interfaces/ICoFHETypes.sol b/contracts/src/hook-pkg/interfaces/ICoFHETypes.sol new file mode 100644 index 000000000..239006655 --- /dev/null +++ b/contracts/src/hook-pkg/interfaces/ICoFHETypes.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ebool, euint8, euint16, euint32, euint64, euint128, euint256, eaddress} from "fhenix-contracts/FHE.sol"; + +/// @title ICoFHETypes +/// @notice Encrypted equivalents of IHooks calldata types +/// @dev Direct mapping of IHooks parameter types to FHE encrypted versions +interface ICoFHETypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED POOLKEY + // ═══════════════════════════════════════════════════════════════════════ + + struct EPoolKey { + eaddress currency0; + eaddress currency1; + euint32 fee; + euint32 tickSpacing; + eaddress hooks; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED MODIFYLIQUIDITYPARAMS + // ═══════════════════════════════════════════════════════════════════════ + + struct EModifyLiquidityParams { + euint32 tickLower; + euint32 tickUpper; + euint256 liquidityDelta; + euint256 salt; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED SWAPPARAMS + // ═══════════════════════════════════════════════════════════════════════ + + struct ESwapParams { + ebool zeroForOne; + euint256 amountSpecified; + euint256 sqrtPriceLimitX96; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED BALANCEDELTA + // ═══════════════════════════════════════════════════════════════════════ + + struct EBalanceDelta { + euint128 amount0; + euint128 amount1; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED BEFORESWAPDELTA + // ═══════════════════════════════════════════════════════════════════════ + + struct EBeforeSwapDelta { + euint128 deltaSpecified; + euint128 deltaUnspecified; + } +} diff --git a/contracts/src/hook-pkg/interfaces/IHookStateLens.sol b/contracts/src/hook-pkg/interfaces/IHookStateLens.sol new file mode 100644 index 000000000..6be3b8682 --- /dev/null +++ b/contracts/src/hook-pkg/interfaces/IHookStateLens.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {ICoFHETypes} from "./ICoFHETypes.sol"; + +/// @title IHookStateLens +/// @notice Interface for viewing encrypted hook state with decryption for authorized parties +/// @dev Uses delegatecall pattern to read state of queried hooks +interface IHookStateLens is ICoFHETypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HookStateLens__NotAuthorized(); + error HookStateLens__HookNotRegistered(); + error HookStateLens__DecryptionFailed(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event StateAccessed( + address indexed hook, + PoolId indexed poolId, + address indexed requester, + bool decrypted + ); + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE (Public - anyone can see encrypted handles) + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get encrypted pool key for a hook's pool + /// @param hook The hook contract address + /// @param poolId The pool identifier + /// @return eKey Encrypted pool key + function getEncryptedPoolKey( + address hook, + PoolId poolId + ) external view returns (EPoolKey memory eKey); + + /// @notice Get encrypted swap params from last swap + /// @param hook The hook contract address + /// @param poolId The pool identifier + /// @return eParams Encrypted swap parameters + function getEncryptedSwapParams( + address hook, + PoolId poolId + ) external view returns (ESwapParams memory eParams); + + /// @notice Get encrypted balance delta from last operation + /// @param hook The hook contract address + /// @param poolId The pool identifier + /// @return eDelta Encrypted balance delta + function getEncryptedBalanceDelta( + address hook, + PoolId poolId + ) external view returns (EBalanceDelta memory eDelta); + + /// @notice Get raw encrypted hook state bytes + /// @param hook The hook contract address + /// @param poolId The pool identifier + /// @return encryptedState ABI-encoded encrypted state + function getEncryptedHookState( + address hook, + PoolId poolId + ) external returns (bytes memory encryptedState); + + // ═══════════════════════════════════════════════════════════════════════ + // DECRYPTED STATE (Authorized only - developer and verifiers) + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get decrypted hook state (authorized verifiers only) + /// @dev Decrypts all encrypted state for AVS verification + /// @param hook The hook contract address + /// @param poolId The pool identifier + /// @return hookState ABI-encoded decrypted hook state + function getDecryptedHookState( + address hook, + PoolId poolId + ) external returns (bytes memory hookState); + + /// @notice Check if caller is authorized to decrypt + /// @param hook The hook contract address + /// @param account The account to check + /// @return authorized True if authorized + function isAuthorizedToDecrypt( + address hook, + address account + ) external view returns (bool authorized); + + // ═══════════════════════════════════════════════════════════════════════ + // STATE SAMPLING (For AVS) + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Sample state for AVS verification + /// @param hook The hook contract address + /// @param poolId The pool identifier + /// @return stateHash Hash of current state + /// @return timestamp Block timestamp + /// @return blockNumber Current block number + function sampleStateForAVS( + address hook, + PoolId poolId + ) external view returns ( + bytes32 stateHash, + uint256 timestamp, + uint256 blockNumber + ); +} diff --git a/contracts/src/hook-pkg/mocks/MockCoFHECounterHookMod.sol b/contracts/src/hook-pkg/mocks/MockCoFHECounterHookMod.sol new file mode 100644 index 000000000..ba18af34f --- /dev/null +++ b/contracts/src/hook-pkg/mocks/MockCoFHECounterHookMod.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, ebool, euint32, euint128, euint256, eaddress} from "fhenix-contracts/FHE.sol"; +import {CoFHEHookMod} from "../CoFHEHookMod.sol"; +import {ICoFHEHookMod} from "../interfaces/ICoFHEHookMod.sol"; +import {ICoFHETypes} from "../interfaces/ICoFHETypes.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; + +/// @title MockCoFHECounterHookMod +/// @notice CoFHE-compliant counter hook - encrypted equivalent of MockCounterHook +/// @dev Implements counter logic with FHE encrypted state +contract MockCoFHECounterHookMod is CoFHEHookMod { + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE - Equivalent to MockCounterHook mappings + // Using euint128 as it supports arithmetic operations in Fhenix + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev mapping(EPoolId => euint128) - encrypted before swap count + mapping(bytes32 => euint128) public encryptedBeforeSwapCount; + + /// @dev mapping(EPoolId => euint128) - encrypted after swap count + mapping(bytes32 => euint128) public encryptedAfterSwapCount; + + /// @dev mapping(EPoolId => euint128) - encrypted before add liquidity count + mapping(bytes32 => euint128) public encryptedBeforeAddLiquidityCount; + + /// @dev mapping(EPoolId => euint128) - encrypted before remove liquidity count + mapping(bytes32 => euint128) public encryptedBeforeRemoveLiquidityCount; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor(address cofheHook_) CoFHEHookMod(cofheHook_) {} + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED HOOK CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + function beforeSwap( + eaddress, + EPoolKey calldata key, + ESwapParams calldata, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4, EBeforeSwapDelta memory, euint32) { + bytes32 poolId = _encryptedPoolKeyToId(key); + encryptedBeforeSwapCount[poolId] = FHE.add( + encryptedBeforeSwapCount[poolId], + FHE.asEuint128(1) + ); + return (IHooks.beforeSwap.selector, _zeroEBeforeSwapDelta(), euint32.wrap(0)); + } + + function afterSwap( + eaddress, + EPoolKey calldata key, + ESwapParams calldata, + EBalanceDelta calldata, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4, euint128) { + bytes32 poolId = _encryptedPoolKeyToId(key); + encryptedAfterSwapCount[poolId] = FHE.add( + encryptedAfterSwapCount[poolId], + FHE.asEuint128(1) + ); + return (IHooks.afterSwap.selector, euint128.wrap(0)); + } + + function beforeAddLiquidity( + eaddress, + EPoolKey calldata key, + EModifyLiquidityParams calldata, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4) { + bytes32 poolId = _encryptedPoolKeyToId(key); + encryptedBeforeAddLiquidityCount[poolId] = FHE.add( + encryptedBeforeAddLiquidityCount[poolId], + FHE.asEuint128(1) + ); + return IHooks.beforeAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + eaddress, + EPoolKey calldata key, + EModifyLiquidityParams calldata, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4) { + bytes32 poolId = _encryptedPoolKeyToId(key); + encryptedBeforeRemoveLiquidityCount[poolId] = FHE.add( + encryptedBeforeRemoveLiquidityCount[poolId], + FHE.asEuint128(1) + ); + return IHooks.beforeRemoveLiquidity.selector; + } + + // ═══════════════════════════════════════════════════════════════════════ + // NOT IMPLEMENTED CALLBACKS (return default) + // ═══════════════════════════════════════════════════════════════════════ + + function beforeInitialize( + eaddress, + EPoolKey calldata, + euint256 + ) external override onlyCoFHEHook returns (bytes4) { + return IHooks.beforeInitialize.selector; + } + + function afterInitialize( + eaddress, + EPoolKey calldata, + euint256, + euint32 + ) external override onlyCoFHEHook returns (bytes4) { + return IHooks.afterInitialize.selector; + } + + function afterAddLiquidity( + eaddress, + EPoolKey calldata, + EModifyLiquidityParams calldata, + EBalanceDelta calldata, + EBalanceDelta calldata, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4, EBalanceDelta memory) { + return (IHooks.afterAddLiquidity.selector, _zeroEBalanceDelta()); + } + + function afterRemoveLiquidity( + eaddress, + EPoolKey calldata, + EModifyLiquidityParams calldata, + EBalanceDelta calldata, + EBalanceDelta calldata, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4, EBalanceDelta memory) { + return (IHooks.afterRemoveLiquidity.selector, _zeroEBalanceDelta()); + } + + function beforeDonate( + eaddress, + EPoolKey calldata, + euint256, + euint256, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4) { + return IHooks.beforeDonate.selector; + } + + function afterDonate( + eaddress, + EPoolKey calldata, + euint256, + euint256, + bytes calldata + ) external override onlyCoFHEHook returns (bytes4) { + return IHooks.afterDonate.selector; + } + + // ═══════════════════════════════════════════════════════════════════════ + // INTERNAL HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev Derive a deterministic pool ID from encrypted pool key + /// @notice In production, this would use FHE operations + function _encryptedPoolKeyToId(EPoolKey calldata key) internal pure returns (bytes32) { + return keccak256(abi.encode( + eaddress.unwrap(key.currency0), + eaddress.unwrap(key.currency1), + euint32.unwrap(key.fee), + euint32.unwrap(key.tickSpacing), + eaddress.unwrap(key.hooks) + )); + } +} diff --git a/contracts/src/hooks-operator-avs/AttestationRegistry.sol b/contracts/src/hooks-operator-avs/AttestationRegistry.sol new file mode 100644 index 000000000..f423dee36 --- /dev/null +++ b/contracts/src/hooks-operator-avs/AttestationRegistry.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; + +import {IAttestationRegistry} from "./interfaces/IAttestationRegistry.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; + +/// @title AttestationRegistry +/// @notice On-chain registry of hook attestations +/// @dev Stores attestation records for verified hooks +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract AttestationRegistry is IAttestationRegistry, OwnableUpgradeable { + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTANTS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Attestation validity period (30 days) + uint256 public constant ATTESTATION_VALIDITY_PERIOD = 30 days; + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Task manager address + address public taskManager; + + /// @notice hook address => Attestation + mapping(address => Attestation) private _attestations; + + /// @notice hook address => attestation history + mapping(address => bytes32[]) private _attestationHistory; + + /// @notice attestation ID => Attestation + mapping(bytes32 => Attestation) private _attestationsById; + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyTaskManager() { + if (msg.sender != taskManager) revert AttestationRegistry__OnlyTaskManager(); + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor() { + _disableInitializers(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function initialize(address _taskManager, address initialOwner) external initializer { + __Ownable_init(); + if (initialOwner != msg.sender) { + _transferOwnership(initialOwner); + } + taskManager = _taskManager; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function setTaskManager(address _taskManager) external onlyOwner { + taskManager = _taskManager; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ATTESTATION MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IAttestationRegistry + function recordAttestation( + address hook, + string calldata specificationURI, + uint32 taskIndex, + bytes32 responsesHash + ) external onlyTaskManager { + if (hook == address(0)) revert AttestationRegistry__InvalidAttestation(); + + bytes32 attestationId = keccak256(abi.encode( + hook, + specificationURI, + taskIndex, + block.timestamp + )); + + Attestation memory attestation = Attestation({ + attestationId: attestationId, + hook: hook, + specificationURI: specificationURI, + isValid: true, + attestedAt: block.timestamp, + expiresAt: block.timestamp + ATTESTATION_VALIDITY_PERIOD, + taskIndex: taskIndex, + responsesHash: responsesHash + }); + + _attestations[hook] = attestation; + _attestationsById[attestationId] = attestation; + _attestationHistory[hook].push(attestationId); + + emit AttestationRecorded(hook, attestationId, specificationURI, attestation.expiresAt); + } + + /// @inheritdoc IAttestationRegistry + function revokeAttestation(address hook, string calldata reason) external onlyTaskManager { + Attestation storage attestation = _attestations[hook]; + if (attestation.attestationId == bytes32(0)) revert AttestationRegistry__AttestationNotFound(); + if (!attestation.isValid) revert AttestationRegistry__AttestationAlreadyRevoked(); + + attestation.isValid = false; + _attestationsById[attestation.attestationId].isValid = false; + + emit AttestationRevoked(hook, attestation.attestationId, reason); + } + + /// @inheritdoc IAttestationRegistry + function renewAttestation( + address hook, + uint32 taskIndex, + bytes32 responsesHash + ) external onlyTaskManager { + Attestation storage attestation = _attestations[hook]; + if (attestation.attestationId == bytes32(0)) revert AttestationRegistry__AttestationNotFound(); + + // Create new attestation ID for renewal + bytes32 newAttestationId = keccak256(abi.encode( + hook, + attestation.specificationURI, + taskIndex, + block.timestamp + )); + + uint256 newExpiresAt = block.timestamp + ATTESTATION_VALIDITY_PERIOD; + + // Update attestation + attestation.attestationId = newAttestationId; + attestation.isValid = true; + attestation.attestedAt = block.timestamp; + attestation.expiresAt = newExpiresAt; + attestation.taskIndex = taskIndex; + attestation.responsesHash = responsesHash; + + _attestationsById[newAttestationId] = attestation; + _attestationHistory[hook].push(newAttestationId); + + emit AttestationRenewed(hook, newAttestationId, newExpiresAt); + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IAttestationRegistry + function isHookAttested(address hook) external view returns (bool) { + Attestation storage attestation = _attestations[hook]; + return attestation.isValid && attestation.expiresAt > block.timestamp; + } + + /// @inheritdoc IAttestationRegistry + function getAttestation(address hook) external view returns (Attestation memory) { + return _attestations[hook]; + } + + /// @inheritdoc IAttestationRegistry + function getAttestationHistory(address hook) external view returns (bytes32[] memory) { + return _attestationHistory[hook]; + } + + /// @inheritdoc IAttestationRegistry + function getAttestationById(bytes32 attestationId) external view returns (Attestation memory) { + return _attestationsById[attestationId]; + } + + // Storage gap for upgrades + uint256[45] private __gap; +} diff --git a/contracts/src/hooks-operator-avs/ClearingHouse.sol b/contracts/src/hooks-operator-avs/ClearingHouse.sol new file mode 100644 index 000000000..d4552f901 --- /dev/null +++ b/contracts/src/hooks-operator-avs/ClearingHouse.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; + +import {IClearingHouse, SignatureWithSaltAndExpiryCH} from "./interfaces/IClearingHouse.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; +import {IHaaSVendorManagement} from "./interfaces/IHaaSVendorManagement.sol"; + +/// @notice Registry coordinator interface (simplified placeholder) +/// @dev In production, import from eigenlayer-middleware +interface IRegistryCoordinatorSimple { + function registerOperator( + bytes calldata quorumNumbers, + string calldata socket, + bytes calldata params, + SignatureWithSaltAndExpiryCH memory operatorSignature + ) external; + + function deregisterOperator(bytes calldata quorumNumbers) external; +} + +/// @title ClearingHouse +/// @notice Coordinates bonded engagement between HookDevelopers and Protocols +/// @dev Entry point for RegistryCoordinator interactions +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract ClearingHouse is IClearingHouse, OwnableUpgradeable { + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice HaaS clearing coordinator (RegistryCoordinator) + address public haaSClearingCoordinator; + + /// @notice HaaS hub (vendor management) + address public haaSHub; + + /// @notice License ID => engagement active + mapping(uint256 => bool) private _engagementActive; + + /// @notice License ID => engaged operator + mapping(uint256 => address) private _engagementOperators; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor() { + _disableInitializers(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function initialize( + address _haaSClearingCoordinator, + address _haaSHub, + address initialOwner + ) external initializer { + __Ownable_init(); + if (initialOwner != msg.sender) { + _transferOwnership(initialOwner); + } + haaSClearingCoordinator = _haaSClearingCoordinator; + haaSHub = _haaSHub; + } + + // ═══════════════════════════════════════════════════════════════════════ + // BONDED ENGAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IClearingHouse + function acceptBondedEngagement( + SignatureWithSaltAndExpiryCH calldata operatorSignature, + uint256 licenseId + ) external { + IHaaSVendorManagement vendorManagement = IHaaSVendorManagement(haaSHub); + + // Validate license + if (!vendorManagement.isLicenseValid(licenseId)) revert ClearingHouse__InvalidLicense(); + + // Check not already engaged + if (_engagementActive[licenseId]) revert ClearingHouse__EngagementAlreadyAccepted(); + + // Get engagement params from HaaS hub + (bytes memory quorumNumbers, bytes memory pubkeyParams) = vendorManagement.getHaaSEngagementParams(licenseId); + bytes memory socket = vendorManagement.getOperatorSocket(licenseId); + + // Register operator with RegistryCoordinator + IRegistryCoordinatorSimple(haaSClearingCoordinator).registerOperator( + quorumNumbers, + string(socket), + pubkeyParams, + operatorSignature + ); + + // Mark engagement as active + _engagementActive[licenseId] = true; + _engagementOperators[licenseId] = msg.sender; + + emit BondedEngagementAccepted(licenseId, msg.sender, quorumNumbers); + emit QuorumRegistered(msg.sender, quorumNumbers); + } + + /// @inheritdoc IClearingHouse + function terminateBondedEngagement( + uint256 licenseId, + string calldata reason + ) external { + // Only the engaged operator or owner can terminate + if (msg.sender != _engagementOperators[licenseId] && msg.sender != owner()) { + revert ClearingHouse__Unauthorized(); + } + + if (!_engagementActive[licenseId]) revert ClearingHouse__InvalidLicense(); + + IHaaSVendorManagement vendorManagement = IHaaSVendorManagement(haaSHub); + + // Get quorum numbers for deregistration + (bytes memory quorumNumbers,) = vendorManagement.getHaaSEngagementParams(licenseId); + + // Deregister from RegistryCoordinator + IRegistryCoordinatorSimple(haaSClearingCoordinator).deregisterOperator(quorumNumbers); + + // Mark engagement as inactive + address operator = _engagementOperators[licenseId]; + _engagementActive[licenseId] = false; + _engagementOperators[licenseId] = address(0); + + emit BondedEngagementTerminated(licenseId, operator, reason); + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IClearingHouse + function getHaaSClearingCoordinator() external view returns (address) { + return haaSClearingCoordinator; + } + + /// @inheritdoc IClearingHouse + function getHaaSHub() external view returns (address) { + return haaSHub; + } + + /// @inheritdoc IClearingHouse + function isEngagementActive(uint256 licenseId) external view returns (bool) { + return _engagementActive[licenseId]; + } + + /// @inheritdoc IClearingHouse + function getEngagementOperator(uint256 licenseId) external view returns (address) { + return _engagementOperators[licenseId]; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function setHaaSClearingCoordinator(address _coordinator) external onlyOwner { + haaSClearingCoordinator = _coordinator; + } + + function setHaaSHub(address _hub) external onlyOwner { + haaSHub = _hub; + } + + // Storage gap for upgrades + uint256[45] private __gap; +} diff --git a/contracts/src/hooks-operator-avs/EscrowCoordinator.sol b/contracts/src/hooks-operator-avs/EscrowCoordinator.sol new file mode 100644 index 000000000..4f5a02caf --- /dev/null +++ b/contracts/src/hooks-operator-avs/EscrowCoordinator.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {IEscrowCoordinator} from "./interfaces/IEscrowCoordinator.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; + +/// @notice Market oracle interface for bond pricing +interface IMarketOracleSimple { + function getBondDetails(uint256 licenseId) external view returns (IERC20 token, uint256 amount); +} + +/// @notice Strategy manager interface for deposits +interface IStrategyManagerSimple { + function deposit(address strategy, IERC20 token, uint256 amount) external; + function withdraw(address strategy, IERC20 token, uint256 amount) external; +} + +/// @title EscrowCoordinator +/// @notice Manages escrow-conditioned service delivery for HookLicenses +/// @dev Handles bond posting and release for HaaS engagement +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract EscrowCoordinator is IEscrowCoordinator, OwnableUpgradeable { + using SafeERC20 for IERC20; + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Market oracle for bond pricing + address public marketOracle; + + /// @notice Strategy manager for deposits + address public depositStrategy; + + /// @notice Vendor management contract + address public vendorManagement; + + /// @notice Bond lock period (7 days) + uint256 public constant BOND_LOCK_PERIOD = 7 days; + + /// @notice License ID => bond details + mapping(uint256 => BondDetails) private _bonds; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor() { + _disableInitializers(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function initialize( + address _marketOracle, + address _depositStrategy, + address _vendorManagement, + address initialOwner + ) external initializer { + __Ownable_init(); + if (initialOwner != msg.sender) { + _transferOwnership(initialOwner); + } + marketOracle = _marketOracle; + depositStrategy = _depositStrategy; + vendorManagement = _vendorManagement; + } + + // ═══════════════════════════════════════════════════════════════════════ + // BOND MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IEscrowCoordinator + function postBond(uint256 licenseId) external { + (IERC20 token, uint256 amount) = IMarketOracleSimple(marketOracle).getBondDetails(licenseId); + _postBond(licenseId, token, amount); + } + + /// @inheritdoc IEscrowCoordinator + function postBondWithAmount(uint256 licenseId, uint256 amount) external { + (IERC20 token, uint256 requiredAmount) = IMarketOracleSimple(marketOracle).getBondDetails(licenseId); + if (amount < requiredAmount) revert EscrowCoordinator__InsufficientBond(); + _postBond(licenseId, token, amount); + } + + function _postBond(uint256 licenseId, IERC20 token, uint256 amount) internal { + if (_bonds[licenseId].isActive) revert EscrowCoordinator__BondAlreadyPosted(); + + // Transfer tokens from depositor + token.safeTransferFrom(msg.sender, address(this), amount); + + // Deposit into strategy manager + token.safeIncreaseAllowance(depositStrategy, amount); + IStrategyManagerSimple(depositStrategy).deposit(vendorManagement, token, amount); + + // Record bond + _bonds[licenseId] = BondDetails({ + paymentToken: token, + bondAmount: amount, + depositedAmount: amount, + depositor: msg.sender, + lockedUntil: block.timestamp + BOND_LOCK_PERIOD, + isActive: true + }); + + emit BondPosted(licenseId, msg.sender, address(token), amount); + } + + /// @inheritdoc IEscrowCoordinator + function releaseBond(uint256 licenseId) external { + BondDetails storage bond = _bonds[licenseId]; + + if (!bond.isActive) revert EscrowCoordinator__BondNotPosted(); + if (block.timestamp < bond.lockedUntil) revert EscrowCoordinator__BondLocked(); + + // Only depositor or owner can release + if (msg.sender != bond.depositor && msg.sender != owner()) { + revert EscrowCoordinator__Unauthorized(); + } + + uint256 amount = bond.depositedAmount; + address recipient = bond.depositor; + IERC20 token = bond.paymentToken; + + // Withdraw from strategy + IStrategyManagerSimple(depositStrategy).withdraw(vendorManagement, token, amount); + + // Transfer back to depositor + token.safeTransfer(recipient, amount); + + // Clear bond + bond.isActive = false; + bond.depositedAmount = 0; + + emit BondReleased(licenseId, recipient, address(token), amount); + } + + /// @inheritdoc IEscrowCoordinator + function slashBond(uint256 licenseId, uint256 slashAmount, string calldata reason) external onlyOwner { + BondDetails storage bond = _bonds[licenseId]; + + if (!bond.isActive) revert EscrowCoordinator__BondNotPosted(); + if (slashAmount > bond.depositedAmount) { + slashAmount = bond.depositedAmount; + } + + // Reduce deposited amount + bond.depositedAmount -= slashAmount; + + // Transfer slashed amount to treasury (owner) + IStrategyManagerSimple(depositStrategy).withdraw(vendorManagement, bond.paymentToken, slashAmount); + bond.paymentToken.safeTransfer(owner(), slashAmount); + + emit BondSlashed(licenseId, bond.depositor, slashAmount, reason); + + // If fully slashed, deactivate bond + if (bond.depositedAmount == 0) { + bond.isActive = false; + } + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IEscrowCoordinator + function getBondDetails(uint256 licenseId) external view returns (BondDetails memory) { + return _bonds[licenseId]; + } + + /// @inheritdoc IEscrowCoordinator + function getRequiredBond(uint256 licenseId) external view returns (IERC20 token, uint256 amount) { + return IMarketOracleSimple(marketOracle).getBondDetails(licenseId); + } + + /// @inheritdoc IEscrowCoordinator + function isBondPosted(uint256 licenseId) external view returns (bool) { + return _bonds[licenseId].isActive; + } + + /// @inheritdoc IEscrowCoordinator + function getMarketOracle() external view returns (address) { + return marketOracle; + } + + /// @inheritdoc IEscrowCoordinator + function getDepositStrategy() external view returns (address) { + return depositStrategy; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function setMarketOracle(address _marketOracle) external onlyOwner { + marketOracle = _marketOracle; + } + + function setDepositStrategy(address _depositStrategy) external onlyOwner { + depositStrategy = _depositStrategy; + } + + function setVendorManagement(address _vendorManagement) external onlyOwner { + vendorManagement = _vendorManagement; + } + + // Storage gap for upgrades + uint256[44] private __gap; +} diff --git a/contracts/src/hooks-operator-avs/HaaSVendorManagement.sol b/contracts/src/hooks-operator-avs/HaaSVendorManagement.sol new file mode 100644 index 000000000..b05780ff5 --- /dev/null +++ b/contracts/src/hooks-operator-avs/HaaSVendorManagement.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {ERC721Upgradeable} from "@openzeppelin-upgrades/contracts/token/ERC721/ERC721Upgradeable.sol"; + +import {IHaaSVendorManagement, SignatureWithSaltAndExpiryVendor} from "./interfaces/IHaaSVendorManagement.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; + +/// @title HaaSVendorManagement +/// @notice Manages HookDeveloper registration and HookLicense issuance +/// @dev HookDevelopers are operators that provide HookContracts compliant with HookSpec +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HaaSVendorManagement is + IHaaSVendorManagement, + OwnableUpgradeable, + ERC721Upgradeable +{ + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Service manager for AVS registration + address public serviceManager; + + /// @notice Latest license ID + uint256 public latestLicenseId; + + /// @notice License ID => HookLicense + mapping(uint256 => HookLicense) private _licenses; + + /// @notice License ID => HookSpec URI + mapping(uint256 => string) private _licenseSpecs; + + /// @notice License ID => operator address + mapping(uint256 => address) private _licenseOperators; + + /// @notice License ID => quorum numbers + mapping(uint256 => bytes) private _licenseQuorums; + + /// @notice License ID => pubkey params + mapping(uint256 => bytes) private _licensePubkeyParams; + + /// @notice License ID => socket + mapping(uint256 => bytes) private _licenseSockets; + + /// @notice License ID => validity + mapping(uint256 => bool) private _licenseValidity; + + /// @notice Operator => license IDs + mapping(address => uint256[]) private _operatorLicenses; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor() { + _disableInitializers(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function initialize( + address _serviceManager, + address initialOwner + ) external initializer { + __Ownable_init(); + __ERC721_init("HookLicense", "HLICENSE"); + if (initialOwner != msg.sender) { + _transferOwnership(initialOwner); + } + serviceManager = _serviceManager; + } + + // ═══════════════════════════════════════════════════════════════════════ + // OPERATOR REGISTRATION + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHaaSVendorManagement + function commitToHookSpec( + string calldata hookSpecURI, + SignatureWithSaltAndExpiryVendor calldata, + address operatorAccount + ) external returns (uint256 licenseId) { + if (bytes(hookSpecURI).length == 0) revert HaaSVendorManagement__InvalidHookSpec(); + if (operatorAccount == address(0)) revert HaaSVendorManagement__OperatorNotRegistered(); + + licenseId = latestLicenseId++; + + // Mint license NFT to operator + _mint(operatorAccount, licenseId); + + // Store license data + _licenseSpecs[licenseId] = hookSpecURI; + _licenseOperators[licenseId] = operatorAccount; + _licenseValidity[licenseId] = true; + + // Initialize empty strategies array + _licenses[licenseId] = HookLicense({ + licenseId: licenseId, + haasStrategies: new StrategyParams[](0), + socketManager: address(0) + }); + + _operatorLicenses[operatorAccount].push(licenseId); + + emit HookDeveloperRegistered(operatorAccount, licenseId); + emit HookLicenseIssued(licenseId, operatorAccount, hookSpecURI); + } + + /// @inheritdoc IHaaSVendorManagement + function getHaaSEngagementParams(uint256 licenseId) + external + view + returns (bytes memory quorumNumbers, bytes memory pubkeyParams) + { + if (!_licenseValidity[licenseId]) revert HaaSVendorManagement__LicenseNotFound(); + return (_licenseQuorums[licenseId], _licensePubkeyParams[licenseId]); + } + + /// @inheritdoc IHaaSVendorManagement + function getOperatorSocket(uint256 licenseId) external view returns (bytes memory) { + if (!_licenseValidity[licenseId]) revert HaaSVendorManagement__LicenseNotFound(); + return _licenseSockets[licenseId]; + } + + // ═══════════════════════════════════════════════════════════════════════ + // LICENSE MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHaaSVendorManagement + function getHookLicense(uint256 licenseId) external view returns (HookLicense memory) { + if (!_licenseValidity[licenseId]) revert HaaSVendorManagement__LicenseNotFound(); + return _licenses[licenseId]; + } + + /// @inheritdoc IHaaSVendorManagement + function getOperatorLicenses(address operator) external view returns (uint256[] memory) { + return _operatorLicenses[operator]; + } + + /// @inheritdoc IHaaSVendorManagement + function isLicenseValid(uint256 licenseId) external view returns (bool) { + return _licenseValidity[licenseId]; + } + + /// @inheritdoc IHaaSVendorManagement + function revokeLicense(uint256 licenseId, string calldata reason) external onlyOwner { + if (!_licenseValidity[licenseId]) revert HaaSVendorManagement__LicenseNotFound(); + + _licenseValidity[licenseId] = false; + + emit HookLicenseRevoked(licenseId, reason); + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function setLicenseQuorums(uint256 licenseId, bytes calldata quorumNumbers) external onlyOwner { + _licenseQuorums[licenseId] = quorumNumbers; + } + + function setLicensePubkeyParams(uint256 licenseId, bytes calldata pubkeyParams) external onlyOwner { + _licensePubkeyParams[licenseId] = pubkeyParams; + } + + function setLicenseSocket(uint256 licenseId, bytes calldata socket) external onlyOwner { + _licenseSockets[licenseId] = socket; + } + + function setServiceManager(address _serviceManager) external onlyOwner { + serviceManager = _serviceManager; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ERC721 OVERRIDES + // ═══════════════════════════════════════════════════════════════════════ + + function tokenURI(uint256 licenseId) public view override returns (string memory) { + return _licenseSpecs[licenseId]; + } + + // Storage gap for upgrades + uint256[40] private __gap; +} diff --git a/contracts/src/hooks-operator-avs/HookAttestationServiceManager.sol b/contracts/src/hooks-operator-avs/HookAttestationServiceManager.sol new file mode 100644 index 000000000..e37ee91d9 --- /dev/null +++ b/contracts/src/hooks-operator-avs/HookAttestationServiceManager.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {IHookAttestationServiceManager} from "./interfaces/IHookAttestationServiceManager.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; + +/// @notice Signature type for operator registration +/// @dev In production, import from eigenlayer-contracts/src/contracts/interfaces/ISignatureUtilsMixin.sol +struct SignatureWithSaltAndExpiry { + bytes signature; + bytes32 salt; + uint256 expiry; +} + +/// @notice AVS Directory interface (placeholder) +/// @dev In production, import from eigenlayer-contracts +interface IAVSDirectorySimple { + function registerOperatorToAVS(address operator, SignatureWithSaltAndExpiry memory operatorSignature) external; + function deregisterOperatorFromAVS(address operator) external; + function updateAVSMetadataURI(string memory metadataURI) external; +} + +/// @title HookAttestationServiceManager +/// @notice Service Manager for the Hook Attestation AVS +/// @dev Manages operator registration, rewards, and slashing for hook verification +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HookAttestationServiceManager is IHookAttestationServiceManager, OwnableUpgradeable { + using SafeERC20 for IERC20; + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Address of the AVS directory + address public immutable avsDirectory; + + /// @notice Address of the rewards coordinator + address public immutable rewardsCoordinator; + + /// @notice Address of the stake registry + address public immutable stakeRegistry; + + /// @notice Address of the task manager + address public taskManager; + + /// @notice Address of the attestation registry + address public attestationRegistry; + + /// @notice Address of the rewards initiator + address public rewardsInitiator; + + /// @notice Mapping of registered operators + mapping(address => bool) private _registeredOperators; + + /// @notice Mapping of operator stakes + mapping(address => uint256) private _operatorStakes; + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyTaskManager() { + if (msg.sender != taskManager) revert HookAttestationServiceManager__OnlyTaskManager(); + _; + } + + modifier onlyAttestationRegistry() { + if (msg.sender != attestationRegistry) revert HookAttestationServiceManager__OnlyAttestationRegistry(); + _; + } + + modifier onlyRewardsInitiator() { + require(msg.sender == rewardsInitiator, "Only rewards initiator"); + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor( + address _avsDirectory, + address _rewardsCoordinator, + address _stakeRegistry + ) { + avsDirectory = _avsDirectory; + rewardsCoordinator = _rewardsCoordinator; + stakeRegistry = _stakeRegistry; + _disableInitializers(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationServiceManager + function initialize( + address initialOwner, + address _rewardsInitiator, + address _taskManager, + address _attestationRegistry + ) external initializer { + __Ownable_init(); + if (initialOwner != msg.sender) { + _transferOwnership(initialOwner); + } + rewardsInitiator = _rewardsInitiator; + taskManager = _taskManager; + attestationRegistry = _attestationRegistry; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationServiceManager + function setTaskManager(address _taskManager) external onlyOwner { + emit TaskManagerUpdated(taskManager, _taskManager); + taskManager = _taskManager; + } + + /// @inheritdoc IHookAttestationServiceManager + function setAttestationRegistry(address _attestationRegistry) external onlyOwner { + emit AttestationRegistryUpdated(attestationRegistry, _attestationRegistry); + attestationRegistry = _attestationRegistry; + } + + // ═══════════════════════════════════════════════════════════════════════ + // OPERATOR REGISTRATION + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Register operator to AVS + function registerOperatorToAVS( + address operator, + SignatureWithSaltAndExpiry memory operatorSignature + ) external { + IAVSDirectorySimple(avsDirectory).registerOperatorToAVS(operator, operatorSignature); + _registeredOperators[operator] = true; + } + + /// @notice Deregister operator from AVS + function deregisterOperatorFromAVS(address operator) external { + IAVSDirectorySimple(avsDirectory).deregisterOperatorFromAVS(operator); + _registeredOperators[operator] = false; + } + + /// @notice Update AVS metadata URI + function updateAVSMetadataURI(string memory metadataURI) external onlyOwner { + IAVSDirectorySimple(avsDirectory).updateAVSMetadataURI(metadataURI); + } + + /// @notice Get restakeable strategies + function getRestakeableStrategies() external view returns (address[] memory) { + // Return empty array - to be implemented based on quorum configuration + return new address[](0); + } + + /// @notice Get operator restaked strategies + function getOperatorRestakedStrategies(address) external view returns (address[] memory) { + // Return empty array - to be implemented + return new address[](0); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLASHING + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationServiceManager + function slashOperator( + address operator, + SlashableOffense offense, + bytes32 evidence + ) external onlyTaskManager { + if (!_registeredOperators[operator]) revert HookAttestationServiceManager__OperatorNotRegistered(); + + // Calculate slash amount based on offense + uint256 slashAmount = _calculateSlashAmount(operator, offense); + + // Execute slashing through AllocationManager + // In production, this would call the AllocationManager's slashing functions + _operatorStakes[operator] -= slashAmount; + + emit OperatorSlashed(operator, slashAmount, offense); + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationServiceManager + function getTaskManager() external view returns (address) { + return taskManager; + } + + /// @inheritdoc IHookAttestationServiceManager + function getAttestationRegistry() external view returns (address) { + return attestationRegistry; + } + + /// @inheritdoc IHookAttestationServiceManager + function isOperatorRegistered(address operator) external view returns (bool) { + return _registeredOperators[operator]; + } + + /// @inheritdoc IHookAttestationServiceManager + function getOperatorStake(address operator) external view returns (uint256) { + return _operatorStakes[operator]; + } + + /// @notice Get AVS directory address + function getAvsDirectory() external view returns (address) { + return avsDirectory; + } + + /// @notice Get rewards coordinator address + function getRewardsCoordinator() external view returns (address) { + return rewardsCoordinator; + } + + /// @notice Get stake registry address + function getStakeRegistry() external view returns (address) { + return stakeRegistry; + } + + // ═══════════════════════════════════════════════════════════════════════ + // INTERNAL FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function _calculateSlashAmount(address operator, SlashableOffense offense) internal view returns (uint256) { + uint256 stake = _operatorStakes[operator]; + + // Different slash percentages based on offense severity + if (offense == SlashableOffense.OPERATOR_COLLUSION) { + return stake; // 100% slash for collusion + } else if (offense == SlashableOffense.FALSE_POSITIVE) { + return (stake * 50) / 100; // 50% slash for false positive + } else if (offense == SlashableOffense.FALSE_NEGATIVE) { + return (stake * 30) / 100; // 30% slash for false negative + } else { + return (stake * 20) / 100; // 20% slash for sample manipulation + } + } + + // Storage gap for upgrades + uint256[45] private __gap; +} diff --git a/contracts/src/hooks-operator-avs/HookAttestationTaskManager.sol b/contracts/src/hooks-operator-avs/HookAttestationTaskManager.sol new file mode 100644 index 000000000..b88135a6e --- /dev/null +++ b/contracts/src/hooks-operator-avs/HookAttestationTaskManager.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin-upgrades/contracts/security/PausableUpgradeable.sol"; + +import {IHookAttestationTaskManager} from "./interfaces/IHookAttestationTaskManager.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; +import {IAttestationRegistry} from "./interfaces/IAttestationRegistry.sol"; + +/// @title HookAttestationTaskManager +/// @notice Manages attestation tasks for hook specification verification +/// @dev Based on Incredible Squaring AVS task manager pattern +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HookAttestationTaskManager is + IHookAttestationTaskManager, + OwnableUpgradeable, + PausableUpgradeable +{ + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTANTS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Response window in blocks + uint32 public constant TASK_RESPONSE_WINDOW_BLOCK = 100; + + /// @notice Challenge window in blocks + uint32 public constant TASK_CHALLENGE_WINDOW_BLOCK = 200; + + /// @notice Compliance tolerance (basis points) + uint256 public constant COMPLIANCE_TOLERANCE = 100; // 1% + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice BLS signature checker address + /// @dev In production, use BLSSignatureChecker from eigenlayer-middleware + address public blsSignatureChecker; + + /// @notice Attestation registry + IAttestationRegistry public attestationRegistry; + + /// @notice Service manager + address public serviceManager; + + /// @notice Latest task number + uint32 public latestTaskNum; + + /// @notice Task hash => task responded + mapping(bytes32 => bool) private _taskResponded; + + /// @notice Task index => task hash + mapping(uint32 => bytes32) public allTaskHashes; + + /// @notice Task index => response hash + mapping(uint32 => bytes32) public allTaskResponses; + + /// @notice Task index => AttestationTask + mapping(uint32 => AttestationTask) private _tasks; + + /// @notice Task index => AttestationResponse + mapping(uint32 => AttestationResponse) private _responses; + + /// @notice Task index => response metadata + mapping(uint32 => AttestationResponseMetadata) private _responseMetadata; + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyServiceManager() { + require(msg.sender == serviceManager, "Only service manager"); + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor() { + _disableInitializers(); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function initialize( + address _blsSignatureChecker, + address _attestationRegistry, + address _serviceManager, + address initialOwner + ) external initializer { + __Ownable_init(); + __Pausable_init(); + if (initialOwner != msg.sender) { + _transferOwnership(initialOwner); + } + + blsSignatureChecker = _blsSignatureChecker; + attestationRegistry = IAttestationRegistry(_attestationRegistry); + serviceManager = _serviceManager; + } + + // ═══════════════════════════════════════════════════════════════════════ + // TASK CREATION + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationTaskManager + function createAttestationTask( + address hook, + string calldata specificationURI, + bytes32[] calldata poolIds, + bytes4[] calldata callbacks, + uint32 sampleCount + ) external whenNotPaused returns (uint32 taskIndex) { + if (hook == address(0)) revert HookAttestationTaskManager__InvalidTask(); + if (bytes(specificationURI).length == 0) revert HookAttestationTaskManager__InvalidTask(); + if (poolIds.length == 0) revert HookAttestationTaskManager__InvalidTask(); + if (callbacks.length == 0) revert HookAttestationTaskManager__InvalidTask(); + + taskIndex = latestTaskNum; + + AttestationTask memory task = AttestationTask({ + hook: hook, + specificationURI: specificationURI, + poolIds: poolIds, + callbacks: callbacks, + sampleCount: sampleCount, + taskCreatedBlock: uint32(block.number), + quorumNumbers: hex"00", // Default quorum + quorumThresholdPercentage: 6667 // 66.67% + }); + + // Store task + _tasks[taskIndex] = task; + allTaskHashes[taskIndex] = keccak256(abi.encode(task)); + + emit AttestationTaskCreated(taskIndex, task); + + latestTaskNum++; + } + + // ═══════════════════════════════════════════════════════════════════════ + // TASK RESPONSE + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationTaskManager + function respondToAttestationTask( + AttestationTask calldata task, + AttestationResponse calldata response, + NonSignerStakesAndSignature memory nonSignerStakesAndSignature + ) external whenNotPaused { + uint32 taskIndex = response.referenceTaskIndex; + + // Validate task + bytes32 taskHash = keccak256(abi.encode(task)); + if (allTaskHashes[taskIndex] != taskHash) revert HookAttestationTaskManager__InvalidTask(); + + // Check not already responded + if (_taskResponded[taskHash]) revert HookAttestationTaskManager__TaskAlreadyResponded(); + + // Check response window + if (block.number > task.taskCreatedBlock + TASK_RESPONSE_WINDOW_BLOCK) { + revert HookAttestationTaskManager__TaskExpired(); + } + + // Verify BLS signature (simplified - in production would use full verification) + // blsSignatureChecker.checkSignatures(...) + + // Store response + _responses[taskIndex] = response; + _responseMetadata[taskIndex] = AttestationResponseMetadata({ + taskRespondedBlock: uint32(block.number), + hashOfNonSigners: keccak256(abi.encode(nonSignerStakesAndSignature)) + }); + + bytes32 responseHash = keccak256(abi.encode(response)); + allTaskResponses[taskIndex] = responseHash; + _taskResponded[taskHash] = true; + + emit AttestationTaskResponded(taskIndex, response, _responseMetadata[taskIndex]); + + // If compliant, record attestation + if (response.specCompliant) { + attestationRegistry.recordAttestation( + task.hook, + task.specificationURI, + taskIndex, + responseHash + ); + } + + emit TaskCompleted(taskIndex, response.specCompliant); + } + + // ═══════════════════════════════════════════════════════════════════════ + // CHALLENGE FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationTaskManager + function challengeFalsePositive( + AttestationTask calldata task, + AttestationResponse calldata response, + TransitionSample calldata counterSample + ) external whenNotPaused { + uint32 taskIndex = response.referenceTaskIndex; + + // Validate response exists and was compliant + if (!response.specCompliant) revert HookAttestationTaskManager__InvalidChallenge(); + + // Check challenge window + AttestationResponseMetadata memory metadata = _responseMetadata[taskIndex]; + if (block.number > metadata.taskRespondedBlock + TASK_CHALLENGE_WINDOW_BLOCK) { + revert HookAttestationTaskManager__ChallengeWindowExpired(); + } + + // Verify counter sample proves non-compliance + // In production: verify sample authenticity and check against spec + bool challengeSuccessful = _verifyCounterSample(task, counterSample); + + if (challengeSuccessful) { + // Revoke attestation + attestationRegistry.revokeAttestation(task.hook, "Challenge successful: false positive"); + + // Slash operators (via service manager) + // serviceManager.slashOperators(...) + } + + emit AttestationChallenged(taskIndex, msg.sender, challengeSuccessful); + } + + /// @inheritdoc IHookAttestationTaskManager + function challengeFalseNegative( + AttestationTask calldata task, + AttestationResponse calldata response, + TransitionSample[] calldata complianceSamples + ) external whenNotPaused { + uint32 taskIndex = response.referenceTaskIndex; + + // Validate response exists and was non-compliant + if (response.specCompliant) revert HookAttestationTaskManager__InvalidChallenge(); + + // Check challenge window + AttestationResponseMetadata memory metadata = _responseMetadata[taskIndex]; + if (block.number > metadata.taskRespondedBlock + TASK_CHALLENGE_WINDOW_BLOCK) { + revert HookAttestationTaskManager__ChallengeWindowExpired(); + } + + // Verify compliance samples prove the hook is actually compliant + bool challengeSuccessful = _verifyComplianceSamples(task, complianceSamples); + + if (challengeSuccessful) { + // Record attestation that was wrongly denied + attestationRegistry.recordAttestation( + task.hook, + task.specificationURI, + taskIndex, + keccak256(abi.encode(complianceSamples)) + ); + + // Slash operators (via service manager) + // serviceManager.slashOperators(...) + } + + emit AttestationChallenged(taskIndex, msg.sender, challengeSuccessful); + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookAttestationTaskManager + function getTask(uint32 taskIndex) external view returns (AttestationTask memory) { + return _tasks[taskIndex]; + } + + /// @inheritdoc IHookAttestationTaskManager + function getTaskResponse(uint32 taskIndex) external view returns (AttestationResponse memory) { + return _responses[taskIndex]; + } + + /// @inheritdoc IHookAttestationTaskManager + function taskResponded(uint32 taskIndex) external view returns (bool) { + return _taskResponded[allTaskHashes[taskIndex]]; + } + + // ═══════════════════════════════════════════════════════════════════════ + // INTERNAL FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function _verifyCounterSample( + AttestationTask calldata, + TransitionSample calldata + ) internal pure returns (bool) { + // In production: verify the counter sample against the specification + // This is a placeholder that always returns false + return false; + } + + function _verifyComplianceSamples( + AttestationTask calldata task, + TransitionSample[] calldata samples + ) internal pure returns (bool) { + // In production: verify all samples prove compliance + // Check minimum sample coverage + if (samples.length < task.sampleCount) { + return false; + } + return true; + } + + // Storage gap for upgrades + uint256[40] private __gap; +} diff --git a/contracts/src/hooks-operator-avs/HookStateSampler.sol b/contracts/src/hooks-operator-avs/HookStateSampler.sol new file mode 100644 index 000000000..078c6d9d6 --- /dev/null +++ b/contracts/src/hooks-operator-avs/HookStateSampler.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHookStateSampler} from "./interfaces/IHookStateSampler.sol"; +import {IHooksOperatorAVSTypes} from "./interfaces/IHooksOperatorAVSTypes.sol"; + +/// @notice Interface for hook state view (placeholder) +interface IHookStateViewSimple { + function getTraderState(bytes32 poolId) external view returns (bytes memory); + function getSharedFeeState(bytes32 poolId) external view returns (uint256, uint256); + function getHookState(bytes32 poolId) external view returns (bytes memory); + function getLPState(bytes32 poolId) external view returns (bytes memory); +} + +/// @title HookStateSampler +/// @notice Collects state samples for hook verification +/// @dev Used by AVS operators for behavioral verification without code disclosure +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HookStateSampler is IHookStateSampler { + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Default state view contract + address public defaultStateView; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor(address _defaultStateView) { + defaultStateView = _defaultStateView; + } + + // ═══════════════════════════════════════════════════════════════════════ + // SAMPLING FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc IHookStateSampler + function sampleCurrentState( + bytes32 poolId, + address hook, + address stateView + ) external view returns (StateSample memory sample) { + if (poolId == bytes32(0)) revert HookStateSampler__InvalidPool(); + if (hook == address(0)) revert HookStateSampler__InvalidHook(); + + address viewContract = stateView != address(0) ? stateView : defaultStateView; + + sample.blockNumber = block.number; + sample.timestamp = block.timestamp; + sample.poolId = poolId; + + // Sample via IHookStateViewSimple + IHookStateViewSimple view_ = IHookStateViewSimple(viewContract); + + // Get LP state + sample.lpState = view_.getLPState(poolId); + + // Get trader state + sample.traderState = view_.getTraderState(poolId); + + // Get shared state + (uint256 feeGrowth0, uint256 feeGrowth1) = view_.getSharedFeeState(poolId); + sample.sharedState = abi.encode(feeGrowth0, feeGrowth1); + + // Get hook-specific state + sample.hookState = view_.getHookState(poolId); + } + + /// @inheritdoc IHookStateSampler + function sampleTransition( + bytes32 poolId, + address hook, + bytes4 callback, + bytes calldata input + ) external returns (TransitionSample memory transition) { + if (poolId == bytes32(0)) revert HookStateSampler__InvalidPool(); + if (hook == address(0)) revert HookStateSampler__InvalidHook(); + + // Record pre-state + transition.preState = this.sampleCurrentState(poolId, hook, defaultStateView); + transition.callback = callback; + transition.input = input; + + // Execute callback and measure gas + uint256 gasBefore = gasleft(); + + // Note: In practice, this would be done via PoolManager + // with proper context setup for unlock callback + (bool success, bytes memory returnData) = hook.call( + abi.encodeWithSelector(callback, input) + ); + + transition.gasUsed = gasBefore - gasleft(); + + if (!success) { + // Still record the failed transition + transition.returnData = returnData; + } else { + transition.returnData = returnData; + } + + // Record post-state + transition.postState = this.sampleCurrentState(poolId, hook, defaultStateView); + + emit TransitionSampled(poolId, hook, callback, success); + } + + /// @inheritdoc IHookStateSampler + function batchSampleStates( + bytes32[] calldata poolIds, + address hook, + address stateView + ) external view returns (StateSample[] memory samples) { + samples = new StateSample[](poolIds.length); + + for (uint256 i = 0; i < poolIds.length; i++) { + samples[i] = this.sampleCurrentState(poolIds[i], hook, stateView); + } + } + + /// @inheritdoc IHookStateSampler + function computeSamplesHash(StateSample[] calldata samples) external pure returns (bytes32) { + return keccak256(abi.encode(samples)); + } + + /// @inheritdoc IHookStateSampler + function computeTransitionsHash(TransitionSample[] calldata transitions) external pure returns (bytes32) { + return keccak256(abi.encode(transitions)); + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + function setDefaultStateView(address _stateView) external { + defaultStateView = _stateView; + } +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IAttestationRegistry.sol b/contracts/src/hooks-operator-avs/interfaces/IAttestationRegistry.sol new file mode 100644 index 000000000..cb03b2322 --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IAttestationRegistry.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; + +/// @title IAttestationRegistry +/// @notice On-chain registry of hook attestations +/// @dev Stores attestation records for verified hooks +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IAttestationRegistry is IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error AttestationRegistry__OnlyTaskManager(); + error AttestationRegistry__AttestationNotFound(); + error AttestationRegistry__AttestationExpired(); + error AttestationRegistry__AttestationAlreadyRevoked(); + error AttestationRegistry__InvalidAttestation(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event AttestationRecorded( + address indexed hook, + bytes32 indexed attestationId, + string specificationURI, + uint256 expiresAt + ); + + event AttestationRevoked( + address indexed hook, + bytes32 indexed attestationId, + string reason + ); + + event AttestationRenewed( + address indexed hook, + bytes32 indexed attestationId, + uint256 newExpiresAt + ); + + // ═══════════════════════════════════════════════════════════════════════ + // ATTESTATION MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Record a successful attestation + /// @dev Called by TaskManager after successful response + /// @param hook The hook contract address + /// @param specificationURI IPFS URI of the specification + /// @param taskIndex The task index from TaskManager + /// @param responsesHash Hash of all operator responses + function recordAttestation( + address hook, + string calldata specificationURI, + uint32 taskIndex, + bytes32 responsesHash + ) external; + + /// @notice Revoke an attestation + /// @dev Called when attestation is challenged successfully + /// @param hook The hook contract address + /// @param reason Reason for revocation + function revokeAttestation(address hook, string calldata reason) external; + + /// @notice Renew an existing attestation + /// @param hook The hook contract address + /// @param taskIndex New task index for renewal + /// @param responsesHash New responses hash + function renewAttestation( + address hook, + uint32 taskIndex, + bytes32 responsesHash + ) external; + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Check if a hook has valid attestation + /// @param hook The hook contract address + /// @return isAttested Whether hook has valid, non-expired attestation + function isHookAttested(address hook) external view returns (bool isAttested); + + /// @notice Get full attestation details + /// @param hook The hook contract address + /// @return attestation The attestation record + function getAttestation(address hook) external view returns (Attestation memory attestation); + + /// @notice Get attestation history for a hook + /// @param hook The hook contract address + /// @return attestationIds Array of historical attestation IDs + function getAttestationHistory(address hook) external view returns (bytes32[] memory attestationIds); + + /// @notice Get attestation by ID + /// @param attestationId The attestation ID + /// @return attestation The attestation record + function getAttestationById(bytes32 attestationId) external view returns (Attestation memory attestation); + + /// @notice Get attestation validity period + /// @return period The validity period in seconds + function ATTESTATION_VALIDITY_PERIOD() external view returns (uint256 period); +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IClearingHouse.sol b/contracts/src/hooks-operator-avs/interfaces/IClearingHouse.sol new file mode 100644 index 000000000..378744edb --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IClearingHouse.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; + +/// @notice Signature type for operator registration +/// @dev In production, import from eigenlayer-contracts/src/contracts/interfaces/ISignatureUtilsMixin.sol +struct SignatureWithSaltAndExpiryCH { + bytes signature; + bytes32 salt; + uint256 expiry; +} + +/// @title IClearingHouse +/// @notice Coordinates bonded engagement between HookDevelopers and Protocols +/// @dev Entry point for RegistryCoordinator interactions +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IClearingHouse is IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error ClearingHouse__InvalidLicense(); + error ClearingHouse__EngagementAlreadyAccepted(); + error ClearingHouse__InsufficientBond(); + error ClearingHouse__RegistrationFailed(); + error ClearingHouse__Unauthorized(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event BondedEngagementAccepted( + uint256 indexed licenseId, + address indexed operator, + bytes quorumNumbers + ); + + event BondedEngagementTerminated( + uint256 indexed licenseId, + address indexed operator, + string reason + ); + + event QuorumRegistered( + address indexed operator, + bytes quorumNumbers + ); + + // ═══════════════════════════════════════════════════════════════════════ + // BONDED ENGAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Accept bonded engagement for a HookLicense + /// @dev Registers operator with RegistryCoordinator for specified quorums + /// @param operatorSignature Operator's signature for registration + /// @param licenseId The HookLicense ID + function acceptBondedEngagement( + SignatureWithSaltAndExpiryCH calldata operatorSignature, + uint256 licenseId + ) external; + + /// @notice Terminate bonded engagement + /// @param licenseId The HookLicense ID + /// @param reason Termination reason + function terminateBondedEngagement( + uint256 licenseId, + string calldata reason + ) external; + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get the HaaS clearing coordinator + /// @return coordinator The RegistryCoordinator address + function getHaaSClearingCoordinator() external view returns (address coordinator); + + /// @notice Get the HaaS hub + /// @return hub The HaaSHub address + function getHaaSHub() external view returns (address hub); + + /// @notice Check if engagement is active for a license + /// @param licenseId The license ID + /// @return isActive Whether engagement is active + function isEngagementActive(uint256 licenseId) external view returns (bool isActive); + + /// @notice Get operator for a license + /// @param licenseId The license ID + /// @return operator The operator address + function getEngagementOperator(uint256 licenseId) external view returns (address operator); +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IEscrowCoordinator.sol b/contracts/src/hooks-operator-avs/interfaces/IEscrowCoordinator.sol new file mode 100644 index 000000000..99361feb8 --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IEscrowCoordinator.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title IEscrowCoordinator +/// @notice Manages escrow-conditioned service delivery for HookLicenses +/// @dev Handles bond posting and release for HaaS engagement +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IEscrowCoordinator is IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error EscrowCoordinator__InvalidLicense(); + error EscrowCoordinator__BondAlreadyPosted(); + error EscrowCoordinator__InsufficientBond(); + error EscrowCoordinator__BondNotPosted(); + error EscrowCoordinator__BondLocked(); + error EscrowCoordinator__TransferFailed(); + error EscrowCoordinator__Unauthorized(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event BondPosted( + uint256 indexed licenseId, + address indexed depositor, + address token, + uint256 amount + ); + + event BondReleased( + uint256 indexed licenseId, + address indexed recipient, + address token, + uint256 amount + ); + + event BondSlashed( + uint256 indexed licenseId, + address indexed slashedParty, + uint256 amount, + string reason + ); + + // ═══════════════════════════════════════════════════════════════════════ + // BOND TYPES + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Bond details for a license + struct BondDetails { + IERC20 paymentToken; + uint256 bondAmount; + uint256 depositedAmount; + address depositor; + uint256 lockedUntil; + bool isActive; + } + + // ═══════════════════════════════════════════════════════════════════════ + // BOND MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Post bond for a HookLicense + /// @dev Protocol posts bond to obtain HookLicense access + /// @param licenseId The license ID + function postBond(uint256 licenseId) external; + + /// @notice Post bond with specific amount + /// @param licenseId The license ID + /// @param amount The bond amount + function postBondWithAmount(uint256 licenseId, uint256 amount) external; + + /// @notice Release bond after service completion + /// @param licenseId The license ID + function releaseBond(uint256 licenseId) external; + + /// @notice Slash bond for service failure + /// @param licenseId The license ID + /// @param slashAmount Amount to slash + /// @param reason Slashing reason + function slashBond(uint256 licenseId, uint256 slashAmount, string calldata reason) external; + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get bond details for a license + /// @param licenseId The license ID + /// @return details The bond details + function getBondDetails(uint256 licenseId) external view returns (BondDetails memory details); + + /// @notice Get required bond amount for a license + /// @param licenseId The license ID + /// @return token The payment token + /// @return amount The required bond amount + function getRequiredBond(uint256 licenseId) external view returns (IERC20 token, uint256 amount); + + /// @notice Check if bond is posted for a license + /// @param licenseId The license ID + /// @return isPosted Whether bond is posted + function isBondPosted(uint256 licenseId) external view returns (bool isPosted); + + /// @notice Get the market oracle + /// @return oracle The market oracle address + function getMarketOracle() external view returns (address oracle); + + /// @notice Get the deposit strategy + /// @return strategy The strategy manager address + function getDepositStrategy() external view returns (address strategy); +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IHaaSVendorManagement.sol b/contracts/src/hooks-operator-avs/interfaces/IHaaSVendorManagement.sol new file mode 100644 index 000000000..cebc71daa --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IHaaSVendorManagement.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; + +/// @notice Signature type for operator registration +/// @dev In production, import from eigenlayer-contracts/src/contracts/interfaces/ISignatureUtilsMixin.sol +struct SignatureWithSaltAndExpiryVendor { + bytes signature; + bytes32 salt; + uint256 expiry; +} + +/// @title IHaaSVendorManagement +/// @notice Manages HookDeveloper registration and HookLicense issuance +/// @dev HookDevelopers are operators that provide HookContracts compliant with HookSpec +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IHaaSVendorManagement is IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HaaSVendorManagement__InvalidHookSpec(); + error HaaSVendorManagement__OperatorNotRegistered(); + error HaaSVendorManagement__LicenseNotFound(); + error HaaSVendorManagement__LicenseAlreadyExists(); + error HaaSVendorManagement__InsufficientBond(); + error HaaSVendorManagement__Unauthorized(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event HookDeveloperRegistered( + address indexed operator, + uint256 indexed licenseId + ); + + event HookLicenseIssued( + uint256 indexed licenseId, + address indexed operator, + string hookSpecURI + ); + + event HookLicenseRevoked( + uint256 indexed licenseId, + string reason + ); + + event BondPosted( + uint256 indexed licenseId, + address indexed operator, + uint256 amount + ); + + // ═══════════════════════════════════════════════════════════════════════ + // OPERATOR REGISTRATION + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Commit to a HookSpec by registering as operator + /// @dev HookDeveloper commits bonded participation for HookContract within HookSpec + /// @param hookSpecURI IPFS URI of the HookSpec + /// @param operatorSignature Signature for registration + /// @param operatorAccount Operator's account address + /// @return licenseId The issued license ID + function commitToHookSpec( + string calldata hookSpecURI, + SignatureWithSaltAndExpiryVendor calldata operatorSignature, + address operatorAccount + ) external returns (uint256 licenseId); + + /// @notice Get HaaS engagement parameters for a license + /// @param licenseId The license ID + /// @return quorumNumbers Quorum configuration bytes + /// @return pubkeyParams BLS pubkey registration params (encoded) + function getHaaSEngagementParams(uint256 licenseId) + external + view + returns (bytes memory quorumNumbers, bytes memory pubkeyParams); + + /// @notice Get operator socket for a license + /// @param licenseId The license ID + /// @return socket The operator socket (typically IP address) + function getOperatorSocket(uint256 licenseId) external view returns (bytes memory socket); + + // ═══════════════════════════════════════════════════════════════════════ + // LICENSE MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get hook license by ID + /// @param licenseId The license ID + /// @return license The HookLicense struct + function getHookLicense(uint256 licenseId) external view returns (HookLicense memory license); + + /// @notice Get licenses for an operator + /// @param operator The operator address + /// @return licenseIds Array of license IDs + function getOperatorLicenses(address operator) external view returns (uint256[] memory licenseIds); + + /// @notice Check if license is valid + /// @param licenseId The license ID + /// @return isValid Whether license is valid + function isLicenseValid(uint256 licenseId) external view returns (bool isValid); + + /// @notice Revoke a license + /// @param licenseId The license ID + /// @param reason Revocation reason + function revokeLicense(uint256 licenseId, string calldata reason) external; +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IHookAttestationServiceManager.sol b/contracts/src/hooks-operator-avs/interfaces/IHookAttestationServiceManager.sol new file mode 100644 index 000000000..db8ffb08e --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IHookAttestationServiceManager.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; + +/// @title IHookAttestationServiceManager +/// @notice Service Manager for the Hook Attestation AVS +/// @dev Extends EigenLayer's ServiceManager with hook attestation functionality +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +/// @dev In production, inherit from eigenlayer-middleware IServiceManagerUI +interface IHookAttestationServiceManager is IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HookAttestationServiceManager__OnlyTaskManager(); + error HookAttestationServiceManager__OnlyAttestationRegistry(); + error HookAttestationServiceManager__OperatorNotRegistered(); + error HookAttestationServiceManager__InvalidSlashing(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event TaskManagerUpdated(address indexed oldTaskManager, address indexed newTaskManager); + event AttestationRegistryUpdated(address indexed oldRegistry, address indexed newRegistry); + event OperatorSlashed(address indexed operator, uint256 amount, SlashableOffense offense); + + // ═══════════════════════════════════════════════════════════════════════ + // SERVICE MANAGER FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Initialize the service manager + /// @param initialOwner The initial owner address + /// @param rewardsInitiator The rewards initiator address + /// @param taskManager The task manager address + /// @param attestationRegistry The attestation registry address + function initialize( + address initialOwner, + address rewardsInitiator, + address taskManager, + address attestationRegistry + ) external; + + /// @notice Set the task manager + /// @param taskManager The new task manager address + function setTaskManager(address taskManager) external; + + /// @notice Set the attestation registry + /// @param attestationRegistry The new attestation registry address + function setAttestationRegistry(address attestationRegistry) external; + + /// @notice Slash an operator for misbehavior + /// @param operator The operator to slash + /// @param offense The slashable offense + /// @param evidence Evidence hash + function slashOperator( + address operator, + SlashableOffense offense, + bytes32 evidence + ) external; + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get the task manager + /// @return taskManager The task manager address + function getTaskManager() external view returns (address taskManager); + + /// @notice Get the attestation registry + /// @return attestationRegistry The attestation registry address + function getAttestationRegistry() external view returns (address attestationRegistry); + + /// @notice Check if operator is registered with this AVS + /// @param operator The operator address + /// @return isRegistered Whether operator is registered + function isOperatorRegistered(address operator) external view returns (bool isRegistered); + + /// @notice Get operator stake for verification + /// @param operator The operator address + /// @return stake The operator's stake amount + function getOperatorStake(address operator) external view returns (uint256 stake); +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IHookAttestationTaskManager.sol b/contracts/src/hooks-operator-avs/interfaces/IHookAttestationTaskManager.sol new file mode 100644 index 000000000..904497fc0 --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IHookAttestationTaskManager.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; + +/// @notice BLS Signature Checker placeholder interface +/// @dev In production, replace with: import {BLSSignatureChecker} from "eigenlayer-middleware/src/BLSSignatureChecker.sol"; +interface IBLSSignatureCheckerTypes { + struct NonSignerStakesAndSignature { + uint32[] nonSignerQuorumBitmapIndices; + bytes32[] nonSignerPubkeys; + bytes32[] quorumApks; + bytes signature; + uint32[] quorumApkIndices; + uint32[] totalStakeIndices; + uint32[][] nonSignerStakeIndices; + } +} + +/// @title IHookAttestationTaskManager +/// @notice Task manager for hook specification verification +/// @dev Based on Incredible Squaring AVS pattern +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IHookAttestationTaskManager is IHooksOperatorAVSTypes, IBLSSignatureCheckerTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HookAttestationTaskManager__TaskNotFound(); + error HookAttestationTaskManager__TaskAlreadyResponded(); + error HookAttestationTaskManager__TaskExpired(); + error HookAttestationTaskManager__InvalidTask(); + error HookAttestationTaskManager__QuorumNotMet(); + error HookAttestationTaskManager__InvalidSignature(); + error HookAttestationTaskManager__ChallengeWindowExpired(); + error HookAttestationTaskManager__InvalidChallenge(); + error HookAttestationTaskManager__Unauthorized(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event AttestationTaskCreated( + uint32 indexed taskIndex, + AttestationTask task + ); + + event AttestationTaskResponded( + uint32 indexed taskIndex, + AttestationResponse response, + AttestationResponseMetadata metadata + ); + + event AttestationChallenged( + uint32 indexed taskIndex, + address indexed challenger, + bool challengeSuccessful + ); + + event TaskCompleted( + uint32 indexed taskIndex, + bool specCompliant + ); + + // ═══════════════════════════════════════════════════════════════════════ + // TASK LIFECYCLE + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Create a new attestation task + /// @param hook The hook contract to verify + /// @param specificationURI IPFS URI of formal specification + /// @param poolIds Pools to sample for verification + /// @param callbacks Hook callbacks to test + /// @param sampleCount Number of state samples to collect + /// @return taskIndex The created task index + function createAttestationTask( + address hook, + string calldata specificationURI, + bytes32[] calldata poolIds, + bytes4[] calldata callbacks, + uint32 sampleCount + ) external returns (uint32 taskIndex); + + /// @notice Respond to an attestation task + /// @param task The original task + /// @param response The verification response + /// @param nonSignerStakesAndSignature BLS signature data + function respondToAttestationTask( + AttestationTask calldata task, + AttestationResponse calldata response, + NonSignerStakesAndSignature memory nonSignerStakesAndSignature + ) external; + + /// @notice Challenge a false positive attestation + /// @dev Hook was attested but doesn't match spec + /// @param task The original task + /// @param response The contested response + /// @param counterSample State samples proving non-compliance + function challengeFalsePositive( + AttestationTask calldata task, + AttestationResponse calldata response, + TransitionSample calldata counterSample + ) external; + + /// @notice Challenge a false negative attestation + /// @dev Hook was rejected but actually matches spec + /// @param task The original task + /// @param response The contested response + /// @param complianceSamples Samples proving compliance + function challengeFalseNegative( + AttestationTask calldata task, + AttestationResponse calldata response, + TransitionSample[] calldata complianceSamples + ) external; + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get task by index + /// @param taskIndex The task index + /// @return task The attestation task + function getTask(uint32 taskIndex) external view returns (AttestationTask memory task); + + /// @notice Get task response + /// @param taskIndex The task index + /// @return response The attestation response + function getTaskResponse(uint32 taskIndex) external view returns (AttestationResponse memory response); + + /// @notice Get latest task index + /// @return latestTaskIndex The latest task index + function latestTaskNum() external view returns (uint32 latestTaskIndex); + + /// @notice Check if task has been responded to + /// @param taskIndex The task index + /// @return responded Whether task has response + function taskResponded(uint32 taskIndex) external view returns (bool responded); + + /// @notice Get task response window blocks + /// @return blocks The response window in blocks + function TASK_RESPONSE_WINDOW_BLOCK() external view returns (uint32 blocks); + + /// @notice Get task challenge window blocks + /// @return blocks The challenge window in blocks + function TASK_CHALLENGE_WINDOW_BLOCK() external view returns (uint32 blocks); +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IHookStateSampler.sol b/contracts/src/hooks-operator-avs/interfaces/IHookStateSampler.sol new file mode 100644 index 000000000..14d3d589d --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IHookStateSampler.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooksOperatorAVSTypes} from "./IHooksOperatorAVSTypes.sol"; + +/// @title IHookStateSampler +/// @notice Interface for collecting state samples for verification +/// @dev Used by AVS operators for behavioral verification without code disclosure +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IHookStateSampler is IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ERRORS + // ═══════════════════════════════════════════════════════════════════════ + + error HookStateSampler__InvalidPool(); + error HookStateSampler__InvalidHook(); + error HookStateSampler__SamplingFailed(); + error HookStateSampler__TransitionFailed(); + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event StateSampled( + bytes32 indexed poolId, + address indexed hook, + uint256 blockNumber + ); + + event TransitionSampled( + bytes32 indexed poolId, + address indexed hook, + bytes4 callback, + bool success + ); + + // ═══════════════════════════════════════════════════════════════════════ + // SAMPLING FUNCTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Sample current pool and hook state + /// @param poolId Pool to sample + /// @param hook Hook contract + /// @param stateView State view contract + /// @return sample The state sample + function sampleCurrentState( + bytes32 poolId, + address hook, + address stateView + ) external view returns (StateSample memory sample); + + /// @notice Sample a state transition by executing callback + /// @param poolId Pool to test + /// @param hook Hook contract + /// @param callback Callback selector + /// @param input Callback input + /// @return transition The transition sample + function sampleTransition( + bytes32 poolId, + address hook, + bytes4 callback, + bytes calldata input + ) external returns (TransitionSample memory transition); + + /// @notice Batch sample multiple states + /// @param poolIds Pools to sample + /// @param hook Hook contract + /// @param stateView State view contract + /// @return samples Array of state samples + function batchSampleStates( + bytes32[] calldata poolIds, + address hook, + address stateView + ) external view returns (StateSample[] memory samples); + + /// @notice Compute hash of samples for verification + /// @param samples Array of state samples + /// @return hash Keccak256 hash of encoded samples + function computeSamplesHash(StateSample[] calldata samples) external pure returns (bytes32 hash); + + /// @notice Compute hash of transition samples + /// @param transitions Array of transition samples + /// @return hash Keccak256 hash of encoded transitions + function computeTransitionsHash(TransitionSample[] calldata transitions) external pure returns (bytes32 hash); +} diff --git a/contracts/src/hooks-operator-avs/interfaces/IHooksOperatorAVSTypes.sol b/contracts/src/hooks-operator-avs/interfaces/IHooksOperatorAVSTypes.sol new file mode 100644 index 000000000..72b5fa752 --- /dev/null +++ b/contracts/src/hooks-operator-avs/interfaces/IHooksOperatorAVSTypes.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @title IHooksOperatorAVSTypes +/// @notice Type definitions for the Hook Attestation AVS system +/// @dev Types for attestation tasks, verification results, and slashing conditions +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +interface IHooksOperatorAVSTypes { + + // ═══════════════════════════════════════════════════════════════════════ + // ATTESTATION TASK TYPES + // Based on Incredible Squaring AVS task pattern + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice An attestation task to verify hook specification compliance + /// @dev Operators verify hook behavior matches spec WITHOUT seeing source code + struct AttestationTask { + /// @dev The hook contract address to verify + address hook; + /// @dev IPFS CID of the formal specification + string specificationURI; + /// @dev Pool IDs to sample state from + bytes32[] poolIds; + /// @dev Callbacks to test (e.g., beforeSwap.selector) + bytes4[] callbacks; + /// @dev Number of state samples required + uint32 sampleCount; + /// @dev Block when task was created + uint32 taskCreatedBlock; + /// @dev Quorum configuration + bytes quorumNumbers; + /// @dev Threshold percentage for consensus (basis points) + uint32 quorumThresholdPercentage; + } + + /// @notice Response from operators after verification + struct AttestationResponse { + /// @dev Reference to the task being responded to + uint32 referenceTaskIndex; + /// @dev Whether the hook passes all spec tests + bool specCompliant; + /// @dev Hash of all state samples collected + bytes32 stateSamplesHash; + /// @dev Hash of test results + bytes32 testResultsHash; + /// @dev Number of invariants verified + uint32 invariantsVerified; + /// @dev Number of invariants failed + uint32 invariantsFailed; + } + + /// @notice Metadata about the response + struct AttestationResponseMetadata { + uint32 taskRespondedBlock; + bytes32 hashOfNonSigners; + } + + // ═══════════════════════════════════════════════════════════════════════ + // STATE SAMPLING TYPES + // For behavioral verification without code disclosure + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice A state sample collected during verification + struct StateSample { + /// @dev Block number when sampled + uint256 blockNumber; + /// @dev Timestamp when sampled + uint256 timestamp; + /// @dev Pool identifier + bytes32 poolId; + /// @dev Encoded LP state + bytes lpState; + /// @dev Encoded Trader state + bytes traderState; + /// @dev Encoded Hook state (hook-specific) + bytes hookState; + /// @dev Encoded shared state + bytes sharedState; + } + + /// @notice A state transition sample for verification + struct TransitionSample { + /// @dev State before callback + StateSample preState; + /// @dev Callback executed + bytes4 callback; + /// @dev Callback input parameters + bytes input; + /// @dev State after callback + StateSample postState; + /// @dev Gas consumed + uint256 gasUsed; + /// @dev Return data from callback + bytes returnData; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ATTESTATION REGISTRY TYPES + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice On-chain attestation record + struct Attestation { + bytes32 attestationId; + address hook; + string specificationURI; + bool isValid; + uint256 attestedAt; + uint256 expiresAt; + uint32 taskIndex; + bytes32 responsesHash; + } + + // ═══════════════════════════════════════════════════════════════════════ + // HOOK LICENSE / HAAS TYPES + // HookDeveloper operates HookContracts compliant with HookSpec + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Strategy parameters for quorum weighting + struct StrategyParams { + address strategy; + uint96 multiplier; + } + + /// @notice Hook license representing permission to operate hook contracts + struct HookLicense { + uint256 licenseId; + StrategyParams[] haasStrategies; + address socketManager; + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLASHING TYPES + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Types of slashable offenses + enum SlashableOffense { + FALSE_POSITIVE, // Attested non-compliant hook as compliant + FALSE_NEGATIVE, // Rejected compliant hook + SAMPLE_MANIPULATION, // Manipulated verification samples + OPERATOR_COLLUSION // Colluded with other operators + } + + /// @notice Compliance result from spec checker + struct ComplianceResult { + bool compliant; + uint256 deviationMagnitude; + bytes32 failedInvariant; + string failureReason; + } +} diff --git a/contracts/src/master-hook-pkg/MasterHook.sol b/contracts/src/master-hook-pkg/MasterHook.sol index d745a4842..b1ead94a8 100644 --- a/contracts/src/master-hook-pkg/MasterHook.sol +++ b/contracts/src/master-hook-pkg/MasterHook.sol @@ -34,9 +34,6 @@ contract MasterHook is IMasterHook, InitializableBase{ } } - - - function initialize(address _poolManager, address _allHookImpl) external initializer{ MasterHookStorage storage $ = getStorage(); AccessControlMod.setRoleAdmin(AccessControlMod.DEFAULT_ADMIN_ROLE, PROTOCOL_ADMIN); @@ -81,9 +78,6 @@ contract MasterHook is IMasterHook, InitializableBase{ // if (!IERC165(_hook).supportsInterface(type(IHooks).interfaceId)) revert MasterHook__NotValidHook(); bytes4[] memory _hookSelectors = LibHookSelectors.hookSelectors(IHooks(_hook)); bytes4[] memory _allSelectors = LibHookSelectors.appendSelectors(_hookSelectors, _additionalSelectors); - - // Call replace and add functions directly - need to convert memory to calldata - // Since we can't convert memory to calldata, we'll use a helper approach _replaceHookFunctions(_hook, _hookSelectors); _addHookFunctions(_hook, _additionalSelectors); diff --git a/contracts/test/hook-pkg/CoFHEHook.t.sol b/contracts/test/hook-pkg/CoFHEHook.t.sol new file mode 100644 index 000000000..4bf2eeb6a --- /dev/null +++ b/contracts/test/hook-pkg/CoFHEHook.t.sol @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title CoFHEHookTest +/// @notice Test suite for CoFHEHook - IHooks wrapper with FHE encryption +contract CoFHEHookTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // DEPLOYMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__deploymentMustSetPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__deploymentMustSetDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__developerMustBeAuthorizedAtDeployment() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // AUTHORIZATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__setVerifierAuthorizationMustSucceed() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__setVerifierAuthorizationMustRevertIfNotDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__revokeVerifierAuthorizationMustSucceed() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // MOD MANAGEMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__setHookModMustSucceed() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__setHookModMustRevertIfNotDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__setHookModMustEmitEvent() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS CALLBACK - AUTHORIZATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__beforeInitializeMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeInitializeMustRevertIfModNotSet() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterInitializeMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeSwapMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterSwapMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeAddLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterAddLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeRemoveLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterRemoveLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeDonateMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterDonateMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTION/DECRYPTION FLOW TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__beforeInitializeMustEncryptAndForwardToMod() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeSwapMustEncryptParamsAndDecryptReturn() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterSwapMustEncryptParamsAndDecryptReturn() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterAddLiquidityMustDecryptBalanceDelta() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterRemoveLiquidityMustDecryptBalanceDelta() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // ACCESS CONTROL MATRIX TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__developerMustHaveRawCodeAccess() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__authorizedAccountMustHaveRawCodeAccess() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__avsVerifierMustNotHaveRawCodeAccess() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__publicMustOnlyAccessEncryptedCode() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } +} + +/// @title CoFHEHookModTest +/// @notice Test suite for CoFHEHookMod - Base contract for encrypted hook logic +contract CoFHEHookModTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIER TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__onlyCoFHEHookModifierMustRevertIfUnauthorized() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyCoFHEHookModifierMustAllowCoFHEHook() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // DEFAULT IMPLEMENTATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__defaultBeforeInitializeMustRevert() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__defaultAfterInitializeMustRevert() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__defaultBeforeSwapMustRevert() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__defaultAfterSwapMustRevert() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // HELPER FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__zeroEBalanceDeltaMustReturnZeroValues() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__zeroEBeforeSwapDeltaMustReturnZeroValues() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } +} diff --git a/contracts/test/hook-pkg/CoFHEHookMasterHook.t.sol b/contracts/test/hook-pkg/CoFHEHookMasterHook.t.sol new file mode 100644 index 000000000..ec2bfa3d7 --- /dev/null +++ b/contracts/test/hook-pkg/CoFHEHookMasterHook.t.sol @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title CoFHEHookMasterHookTest +/// @notice Integration tests for CoFHE compliant hook added to MasterHook +/// @dev Invariant testing - CoFHE hook must work the same as MockCounterHook +/// @dev Key invariant: Client sees NO difference between CoFHEHook and MockCounterHook +/// +/// Architecture (transparent to client): +/// ┌─────────────────────────────────────────────────────────────────────────┐ +/// │ CLIENT / POOLMANAGER │ +/// │ (sees standard IHooks interface) │ +/// └─────────────────────────────────┬───────────────────────────────────────┘ +/// │ plaintext: beforeSwap(sender, key, params) +/// ▼ +/// ┌─────────────────────────────────────────────────────────────────────────┐ +/// │ CoFHEHook │ +/// │ (IHooks compliant wrapper) │ +/// │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ +/// │ │ ENCRYPT │ -> │ FORWARD │ -> │ DECRYPT │ │ +/// │ │ sender→eSender │ │ to CoFHEHookMod│ │ eResult→result │ │ +/// │ │ key→eKey │ │ │ │ │ │ +/// │ │ params→eParams │ │ │ │ │ │ +/// │ └────────────────┘ └────────────────┘ └────────────────┘ │ +/// └─────────────────────────────────┬───────────────────────────────────────┘ +/// │ encrypted: beforeSwap(eSender, eKey, eParams) +/// ▼ +/// ┌─────────────────────────────────────────────────────────────────────────┐ +/// │ CoFHEHookMod │ +/// │ (Hook developer's encrypted logic) │ +/// │ encryptedBeforeSwapCount[ePoolId]++ // FHE.add() under the hood │ +/// └─────────────────────────────────────────────────────────────────────────┘ +/// │ +/// ▼ plaintext response +/// ┌─────────────────────────────────────────────────────────────────────────┐ +/// │ CLIENT / POOLMANAGER │ +/// │ (receives normal IHooks return values) │ +/// │ beforeSwapCount[poolId] == 1 ← same result as MockCounterHook │ +/// └─────────────────────────────────────────────────────────────────────────┘ +/// +contract CoFHEHookMasterHookTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP & DEPLOYMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__integration__deployMasterHookWithCoFHEHookMustSucceed() public { + //==========PRE-CONDITIONS================== + // MasterHook deployed and initialized + // CoFHEHook deployed with valid hookMod + // PoolManager available + + //==============TEST======================== + // addHook(cofheHook, additionalSelectors) + + //==========POST-CONDITIONS================= + // CoFHEHook added to MasterHook diamond + // IHooks selectors point to CoFHEHook + } + + function test__integration__coFHEHookMustBeAddedByProtocolAdmin() public { + //==========PRE-CONDITIONS================== + // MasterHook initialized + // Caller has PROTOCOL_ADMIN role + + //==============TEST======================== + // addHook as protocol admin + + //==========POST-CONDITIONS================= + // Hook added successfully + // MasterHook__HookAdded event emitted + } + + function test__integration__addCoFHEHookMustRevertIfNotProtocolAdmin() public { + //==========PRE-CONDITIONS================== + // MasterHook initialized + // Caller does NOT have PROTOCOL_ADMIN role + + //==============TEST======================== + // addHook as non-admin + + //==========POST-CONDITIONS================= + // Transaction reverts + } + + // ═══════════════════════════════════════════════════════════════════════ + // INVARIANT: COUNTER EQUIVALENCE TESTS + // CoFHE hook must behave identically to MockCounterHook + // ═══════════════════════════════════════════════════════════════════════ + + function test__invariant__beforeSwapCountMustMatchMockCounterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHECounterHook added + // Pool initialized + // beforeSwapCount[poolId] == 0 + + //==============TEST======================== + // Execute N swaps through MasterHook + + //==========POST-CONDITIONS================= + // beforeSwapCount[poolId] == N + // Same as MockCounterHook behavior + } + + function test__invariant__afterSwapCountMustMatchMockCounterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHECounterHook added + // Pool initialized + // afterSwapCount[poolId] == 0 + + //==============TEST======================== + // Execute N swaps through MasterHook + + //==========POST-CONDITIONS================= + // afterSwapCount[poolId] == N + // Same as MockCounterHook behavior + } + + function test__invariant__beforeAddLiquidityCountMustMatchMockCounterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHECounterHook added + // Pool initialized + // beforeAddLiquidityCount[poolId] == 0 + + //==============TEST======================== + // Execute N addLiquidity through MasterHook + + //==========POST-CONDITIONS================= + // beforeAddLiquidityCount[poolId] == N + // Same as MockCounterHook behavior + } + + function test__invariant__beforeRemoveLiquidityCountMustMatchMockCounterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHECounterHook added + // Pool initialized with liquidity + // beforeRemoveLiquidityCount[poolId] == 0 + + //==============TEST======================== + // Execute N removeLiquidity through MasterHook + + //==========POST-CONDITIONS================= + // beforeRemoveLiquidityCount[poolId] == N + // Same as MockCounterHook behavior + } + + // ═══════════════════════════════════════════════════════════════════════ + // INVARIANT: SELECTOR RETURN TESTS + // CoFHE hook must return correct IHooks selectors + // ═══════════════════════════════════════════════════════════════════════ + + function test__invariant__beforeSwapMustReturnCorrectSelector() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook added + // Pool initialized + + //==============TEST======================== + // Call beforeSwap through MasterHook + + //==========POST-CONDITIONS================= + // Returns IHooks.beforeSwap.selector + // BeforeSwapDelta returned correctly + } + + function test__invariant__afterSwapMustReturnCorrectSelector() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook added + // Pool initialized + + //==============TEST======================== + // Call afterSwap through MasterHook + + //==========POST-CONDITIONS================= + // Returns IHooks.afterSwap.selector + // int128 delta returned correctly + } + + function test__invariant__beforeAddLiquidityMustReturnCorrectSelector() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook added + // Pool initialized + + //==============TEST======================== + // Call beforeAddLiquidity through MasterHook + + //==========POST-CONDITIONS================= + // Returns IHooks.beforeAddLiquidity.selector + } + + function test__invariant__afterAddLiquidityMustReturnCorrectSelector() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook added + // Pool initialized + + //==============TEST======================== + // Call afterAddLiquidity through MasterHook + + //==========POST-CONDITIONS================= + // Returns IHooks.afterAddLiquidity.selector + // BalanceDelta returned correctly + } + + // ═══════════════════════════════════════════════════════════════════════ + // INVARIANT: ENCRYPTION/DECRYPTION TRANSPARENCY + // Encrypted operations must produce same external results + // ═══════════════════════════════════════════════════════════════════════ + + function test__invariant__encryptDecryptMustBeTransparentToPoolManager() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook + // PoolManager calls hook + + //==============TEST======================== + // PoolManager executes swap + // CoFHEHook encrypts -> CoFHEHookMod processes -> CoFHEHook decrypts + + //==========POST-CONDITIONS================= + // PoolManager receives valid plaintext response + // Pool state updated correctly + } + + function test__invariant__balanceDeltaMustBeCorrectAfterEncryption() public { + //==========PRE-CONDITIONS================== + // Pool with CoFHEHook + // Known input swap params + + //==============TEST======================== + // Execute swap with known delta + + //==========POST-CONDITIONS================= + // Returned BalanceDelta matches expected + // amount0 and amount1 correct + } + + function test__invariant__beforeSwapDeltaMustBeCorrectAfterEncryption() public { + //==========PRE-CONDITIONS================== + // Pool with CoFHEHook + // Hook returns non-zero BeforeSwapDelta + + //==============TEST======================== + // Execute beforeSwap + + //==========POST-CONDITIONS================= + // Decrypted BeforeSwapDelta matches expected + // deltaSpecified correct + // deltaUnspecified correct + } + + // ═══════════════════════════════════════════════════════════════════════ + // INVARIANT: STATE CONSISTENCY + // Hook state must remain consistent through operations + // ═══════════════════════════════════════════════════════════════════════ + + function test__invariant__hookStateMustBeConsistentAfterMultipleSwaps() public { + //==========PRE-CONDITIONS================== + // Pool initialized with CoFHEHook + // Initial state recorded + + //==============TEST======================== + // Execute multiple swaps in sequence + + //==========POST-CONDITIONS================= + // All counters incremented correctly + // No state corruption + // Encrypted state matches decrypted + } + + function test__invariant__hookStateMustBeConsistentAfterMixedOperations() public { + //==========PRE-CONDITIONS================== + // Pool initialized with CoFHEHook + + //==============TEST======================== + // Execute: addLiquidity -> swap -> swap -> removeLiquidity -> swap + + //==========POST-CONDITIONS================= + // beforeAddLiquidityCount == 1 + // beforeSwapCount == 3 + // afterSwapCount == 3 + // beforeRemoveLiquidityCount == 1 + } + + // ═══════════════════════════════════════════════════════════════════════ + // INVARIANT: CLIENT TRANSPARENCY + // Client must NOT notice any difference between CoFHEHook and MockCounterHook + // ═══════════════════════════════════════════════════════════════════════ + + function test__invariant__clientMustNotNoticeEncryption() public { + //==========PRE-CONDITIONS================== + // Two setups: + // Setup A: MasterHook + MockCounterHook (plaintext) + // Setup B: MasterHook + CoFHEHook + MockCoFHECounterHookMod (encrypted) + // Same PoolKey, same initial state + + //==============TEST======================== + // Execute identical sequence on both: + // 1. Initialize pool + // 2. Add liquidity + // 3. Swap N times + // 4. Remove liquidity + + //==========POST-CONDITIONS================= + // Setup A counters == Setup B counters + // Setup A return values == Setup B return values + // Client cannot distinguish which setup was used + } + + function test__invariant__returnValuesMustBeIdenticalToMockCounterHook() public { + //==========PRE-CONDITIONS================== + // CoFHEHook with MockCoFHECounterHookMod + // MockCounterHook reference + + //==============TEST======================== + // Call beforeSwap on both with same params + + //==========POST-CONDITIONS================= + // Both return same selector + // Both return same BeforeSwapDelta + // Both return same fee override + } + + function test__invariant__poolStateMustBeIdenticalAfterOperations() public { + //==========PRE-CONDITIONS================== + // Pool A with MockCounterHook + // Pool B with CoFHEHook (same params) + + //==============TEST======================== + // Execute same swap on both pools + + //==========POST-CONDITIONS================= + // Pool A state == Pool B state + // Balances identical + // Liquidity identical + } + + function test__invariant__gasUsageMustBeComparable() public { + //==========PRE-CONDITIONS================== + // CoFHEHook deployed + // MockCounterHook deployed + + //==============TEST======================== + // Measure gas for same operation on both + + //==========POST-CONDITIONS================= + // Gas difference within acceptable bounds + // No unexpected gas consumption + } + + // ═══════════════════════════════════════════════════════════════════════ + // DIAMOND PATTERN INTEGRATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__integration__diamondFallbackMustRouteToCoFHEHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook added + // IHooks selectors replaced + + //==============TEST======================== + // Call IHooks function on MasterHook + + //==========POST-CONDITIONS================= + // Call routed to CoFHEHook via fallback + // Correct response returned + } + + function test__integration__hookSelectorsMustBeReplacedInDiamond() public { + //==========PRE-CONDITIONS================== + // MasterHook initialized with AllHook + // CoFHEHook ready to add + + //==============TEST======================== + // addHook(cofheHook, []) + + //==========POST-CONDITIONS================= + // All 10 IHooks selectors point to CoFHEHook + // AllHook no longer handles IHooks calls + } + + function test__integration__additionalSelectorsMustBeAddedToDiamond() public { + //==========PRE-CONDITIONS================== + // MasterHook initialized + // CoFHEHook has additional functions + + //==============TEST======================== + // addHook(cofheHook, [additionalSelector1, additionalSelector2]) + + //==========POST-CONDITIONS================= + // Additional selectors added to diamond + // Can call additional functions through MasterHook + } + + // ═══════════════════════════════════════════════════════════════════════ + // ACCESS CONTROL INTEGRATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__integration__onlyPoolManagerMustBeEnforcedThroughMasterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook + // Caller is NOT PoolManager + + //==============TEST======================== + // Direct call to beforeSwap on MasterHook + + //==========POST-CONDITIONS================= + // Reverts with OnlyPoolManager error + } + + function test__integration__developerAuthorizationMustWorkThroughMasterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook + // Developer address set + + //==============TEST======================== + // Check authorization through MasterHook + + //==========POST-CONDITIONS================= + // Developer is authorized + // Can access raw code + } + + // ═══════════════════════════════════════════════════════════════════════ + // HOOK STATE LENS INTEGRATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__integration__hookStateLensMustReadStateThroughMasterHook() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook + // HookStateLens deployed + // Some operations executed + + //==============TEST======================== + // Query encrypted state through lens + + //==========POST-CONDITIONS================= + // Encrypted state returned + // State matches hook internal state + } + + function test__integration__authorizedVerifierMustDecryptStateThroughLens() public { + //==========PRE-CONDITIONS================== + // MasterHook with CoFHEHook + // Verifier authorized on CoFHEHook + // Operations executed + + //==============TEST======================== + // Verifier calls getDecryptedHookState + + //==========POST-CONDITIONS================= + // Decrypted state returned + // Values match expected plaintext + } +} + +/// @title CoFHECounterHookModTest +/// @notice Test suite for CoFHE-compliant counter hook implementation +/// @dev Tests the encrypted counter logic matching MockCounterHook behavior +/// @dev INVARIANT: MockCoFHECounterHookMod MUST behave identically to MockCounterHook +contract CoFHECounterHookModTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // COUNTER INCREMENT TESTS (Encrypted) + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__beforeSwapMustIncrementEncryptedCounter() public { + //==========PRE-CONDITIONS================== + // CoFHECounterHookMod deployed + // encryptedBeforeSwapCount[poolId] == 0 + + //==============TEST======================== + // Call beforeSwap with encrypted params + + //==========POST-CONDITIONS================= + // encryptedBeforeSwapCount[poolId] incremented + // Decrypted value == 1 + } + + function test__unit__afterSwapMustIncrementEncryptedCounter() public { + //==========PRE-CONDITIONS================== + // CoFHECounterHookMod deployed + // encryptedAfterSwapCount[poolId] == 0 + + //==============TEST======================== + // Call afterSwap with encrypted params + + //==========POST-CONDITIONS================= + // encryptedAfterSwapCount[poolId] incremented + // Decrypted value == 1 + } + + function test__unit__beforeAddLiquidityMustIncrementEncryptedCounter() public { + //==========PRE-CONDITIONS================== + // CoFHECounterHookMod deployed + // encryptedBeforeAddLiquidityCount[poolId] == 0 + + //==============TEST======================== + // Call beforeAddLiquidity with encrypted params + + //==========POST-CONDITIONS================= + // encryptedBeforeAddLiquidityCount[poolId] incremented + } + + function test__unit__beforeRemoveLiquidityMustIncrementEncryptedCounter() public { + //==========PRE-CONDITIONS================== + // CoFHECounterHookMod deployed + // encryptedBeforeRemoveLiquidityCount[poolId] == 0 + + //==============TEST======================== + // Call beforeRemoveLiquidity with encrypted params + + //==========POST-CONDITIONS================= + // encryptedBeforeRemoveLiquidityCount[poolId] incremented + } + + // ═══════════════════════════════════════════════════════════════════════ + // COUNTER RETRIEVAL TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getEncryptedCounterMustReturnEncryptedHandle() public { + //==========PRE-CONDITIONS================== + // Counter incremented N times + + //==============TEST======================== + // Get encrypted counter value + + //==========POST-CONDITIONS================= + // Returns euint256 handle (not zero) + // Handle represents encrypted N + } + + function test__unit__getDecryptedCounterMustReturnPlaintextValue() public { + //==========PRE-CONDITIONS================== + // Counter incremented N times + // Caller is authorized + + //==============TEST======================== + // Get decrypted counter value + + //==========POST-CONDITIONS================= + // Returns uint256 == N + } + + // ═══════════════════════════════════════════════════════════════════════ + // INVARIANT: EQUIVALENCE TO MOCKCOUNTERHOOK + // ═══════════════════════════════════════════════════════════════════════ + + function test__invariant__encryptedCounterMustMatchPlaintextCounter() public { + //==========PRE-CONDITIONS================== + // MockCounterHook: beforeSwapCount[poolId] == 0 + // MockCoFHECounterHookMod: encryptedBeforeSwapCount[poolId] == encrypt(0) + + //==============TEST======================== + // Execute N swaps on both hooks + + //==========POST-CONDITIONS================= + // MockCounterHook: beforeSwapCount[poolId] == N + // MockCoFHECounterHookMod: decrypt(encryptedBeforeSwapCount[poolId]) == N + // Both values MUST be identical + } + + function test__invariant__allCountersMustMatchAfterMixedOperations() public { + //==========PRE-CONDITIONS================== + // Both hooks initialized + // Same pool configuration + + //==============TEST======================== + // Execute: 3 swaps, 2 addLiquidity, 1 removeLiquidity + + //==========POST-CONDITIONS================= + // beforeSwapCount: MockCounter == decrypt(CoFHECounter) == 3 + // afterSwapCount: MockCounter == decrypt(CoFHECounter) == 3 + // beforeAddLiquidityCount: MockCounter == decrypt(CoFHECounter) == 2 + // beforeRemoveLiquidityCount: MockCounter == decrypt(CoFHECounter) == 1 + } + + function test__invariant__selectorReturnsMustBeIdentical() public { + //==========PRE-CONDITIONS================== + // Both hooks ready + + //==============TEST======================== + // Call each callback on both hooks + + //==========POST-CONDITIONS================= + // MockCounterHook.beforeSwap returns == CoFHEHook.beforeSwap returns + // All 10 callbacks return identical selectors + } +} + +/// @title CoFHEHookE2EFlowTest +/// @notice End-to-end flow tests for complete CoFHE hook lifecycle +/// @dev Tests the full flow from hook creation to pool operations +contract CoFHEHookE2EFlowTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // COMPLETE LIFECYCLE TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__e2e__hookDeveloperCreatesAndDeploysCoFHEHook() public { + //==========PRE-CONDITIONS================== + // Developer address known + // PoolManager deployed + + //==============TEST======================== + // 1. Deploy CoFHEHook(poolManager, developer) + // 2. Deploy MockCoFHECounterHookMod(cofheHook) + // 3. Call setHookMod(mod) + + //==========POST-CONDITIONS================= + // CoFHEHook deployed and configured + // Developer has raw code access + // hookMod set correctly + } + + function test__e2e__hookAddedToMasterHookAndPoolInitialized() public { + //==========PRE-CONDITIONS================== + // MasterHook deployed and initialized + // CoFHEHook ready + // Protocol admin available + + //==============TEST======================== + // 1. Protocol admin calls addHook(cofheHook, []) + // 2. Initialize pool with MasterHook as hooks + + //==========POST-CONDITIONS================= + // CoFHEHook selectors in diamond + // Pool initialized successfully + // Hook callbacks work through MasterHook + } + + function test__e2e__fullSwapFlowWithEncryptedHook() public { + //==========PRE-CONDITIONS================== + // Pool with CoFHEHook via MasterHook + // Liquidity added + // Trader ready to swap + + //==============TEST======================== + // 1. Trader executes swap + // 2. PoolManager calls beforeSwap → MasterHook → CoFHEHook + // 3. CoFHEHook encrypts → CoFHEHookMod processes → CoFHEHook decrypts + // 4. PoolManager receives plaintext response + // 5. Swap completes + + //==========POST-CONDITIONS================= + // Swap executed correctly + // Counter incremented (encrypted internally) + // Trader received expected tokens + // No observable difference from plaintext hook + } + + function test__e2e__avsVerifierSamplesAndVerifiesState() public { + //==========PRE-CONDITIONS================== + // Pool with operations executed + // AVS verifier authorized on CoFHEHook + // HookStateLens deployed + + //==============TEST======================== + // 1. Verifier calls sampleStateForAVS() + // 2. Verifier calls getDecryptedHookState() + // 3. Verifier compares against spec + + //==========POST-CONDITIONS================= + // State hash returned + // Decrypted state matches expected + // Verification passes + } + + function test__e2e__unauthorizedCannotDecryptState() public { + //==========PRE-CONDITIONS================== + // Pool with operations executed + // Unauthorized caller (not developer, not verifier) + + //==============TEST======================== + // 1. Unauthorized calls getEncryptedHookState() - should succeed + // 2. Unauthorized calls getDecryptedHookState() - should revert + + //==========POST-CONDITIONS================= + // Encrypted state accessible to all + // Decrypted state restricted + // Access control enforced + } + + // ═══════════════════════════════════════════════════════════════════════ + // STRESS TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__e2e__multipleSwapsInSequenceMustMaintainInvariant() public { + //==========PRE-CONDITIONS================== + // Pool initialized + // CoFHEHook active + + //==============TEST======================== + // Execute 100 swaps in sequence + + //==========POST-CONDITIONS================= + // beforeSwapCount == 100 + // afterSwapCount == 100 + // No state corruption + // All return values correct + } + + function test__e2e__concurrentPoolOperationsMustWork() public { + //==========PRE-CONDITIONS================== + // Multiple pools with same CoFHEHook + // Different PoolIds + + //==============TEST======================== + // Execute operations on multiple pools + + //==========POST-CONDITIONS================= + // Each pool has independent counters + // No cross-contamination + // All pools function correctly + } +} diff --git a/contracts/test/hook-pkg/HaaSFacet.t.sol b/contracts/test/hook-pkg/HaaSFacet.t.sol new file mode 100644 index 000000000..ac8d45f2e --- /dev/null +++ b/contracts/test/hook-pkg/HaaSFacet.t.sol @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title HaaSFacetTest +/// @notice Test suite for HaaSFacet - Diamond facet implementing IHooks +contract HaaSFacetTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // ERC165 SUPPORT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__supportsInterfaceMustReturnTrueForIHooks() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__supportsInterfaceMustReturnTrueForIERC165() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE ACCESSOR TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__poolManagerMustReturnCorrectAddress() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getDeveloperMustReturnCorrectAddress() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getHookStateViewerMustReturnCorrectAddress() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // AUTHORIZATION MANAGEMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__setAuthorizationMustSucceedForDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__setAuthorizationMustRevertForNonDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__isAuthorizedMustReturnTrueForDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__isAuthorizedMustReturnTrueForAuthorizedAccount() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__isAuthorizedMustReturnFalseForUnauthorized() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS CALLBACK TESTS - AUTHORIZATION + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__beforeInitializeMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterInitializeMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeSwapMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterSwapMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeAddLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterAddLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeRemoveLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterRemoveLiquidityMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeDonateMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterDonateMustRevertIfNotPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS CALLBACK TESTS - RETURN VALUES + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__beforeInitializeMustReturnCorrectSelector() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterInitializeMustReturnCorrectSelector() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__beforeSwapMustReturnCorrectSelectorAndZeroDelta() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterSwapMustReturnCorrectSelectorAndZero() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterAddLiquidityMustReturnCorrectSelectorAndZeroDelta() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__afterRemoveLiquidityMustReturnCorrectSelectorAndZeroDelta() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } +} + +/// @title HaaSModTest +/// @notice Test suite for HaaSMod - Base modifier contract for HaaS pattern +contract HaaSModTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__haasStoragePositionMustBeConsistent() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getHaaSStorageMustReturnCorrectSlot() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // MODIFIER TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__onlyPoolManagerModifierMustRevertForNonPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyPoolManagerModifierMustAllowPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyDeveloperModifierMustRevertForNonDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyDeveloperModifierMustAllowDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyAuthorizedModifierMustRevertForUnauthorized() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyAuthorizedModifierMustAllowDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__onlyAuthorizedModifierMustAllowAuthorizedAccount() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__initializeHaaSMustSetPoolManager() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__initializeHaaSMustSetHooksMarket() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__initializeHaaSMustSetHookStateViewer() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__initializeHaaSMustSetHookLicenseIssuer() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__initializeHaaSMustSetDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__initializeHaaSMustAuthorizeDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // AUTHORIZATION MANAGEMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__setAuthorizationMustGrantAccess() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__setAuthorizationMustRevokeAccess() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } +} diff --git a/contracts/test/hook-pkg/HookStateLens.t.sol b/contracts/test/hook-pkg/HookStateLens.t.sol new file mode 100644 index 000000000..c7850d781 --- /dev/null +++ b/contracts/test/hook-pkg/HookStateLens.t.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title HookStateLensTest +/// @notice Test suite for HookStateLens - Decryption lens for encrypted hook state +contract HookStateLensTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE GETTER TESTS (Public Access) + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getEncryptedPoolKeyMustReturnEncryptedHandles() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getEncryptedSwapParamsMustReturnEncryptedHandles() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getEncryptedBalanceDeltaMustReturnEncryptedHandles() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getEncryptedHookStateMustBePubliclyAccessible() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // DECRYPTED STATE GETTER TESTS (Authorized Only) + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getDecryptedHookStateMustSucceedForDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getDecryptedHookStateMustSucceedForAuthorizedVerifier() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getDecryptedHookStateMustRevertForUnauthorized() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__getDecryptedHookStateMustEmitStateAccessedEvent() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // AUTHORIZATION CHECK TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__isAuthorizedToDecryptMustReturnTrueForDeveloper() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__isAuthorizedToDecryptMustReturnTrueForVerifier() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__isAuthorizedToDecryptMustReturnFalseForPublic() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // STATE CACHING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__cacheEncryptedPoolKeyMustStoreState() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__cacheEncryptedSwapParamsMustStoreState() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__cacheEncryptedBalanceDeltaMustStoreState() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__cacheEncryptedStateMustStoreFullState() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // AVS STATE SAMPLING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__sampleStateForAVSMustReturnStateHash() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__sampleStateForAVSMustReturnTimestamp() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__sampleStateForAVSMustReturnBlockNumber() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__sampleStateForAVSMustRevertForUnauthorized() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + // ═══════════════════════════════════════════════════════════════════════ + // DECRYPTION HELPER TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__decryptPoolKeyMustReturnCorrectValues() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__decryptSwapParamsMustReturnCorrectValues() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } + + function test__unit__decryptBalanceDeltaMustReturnCorrectValues() public { + //==========PRE-CONDITIONS================== + + //==============TEST======================== + + //==========POST-CONDITIONS================= + } +} diff --git a/contracts/test/hooks-operator-avs/AttestationRegistry.t.sol b/contracts/test/hooks-operator-avs/AttestationRegistry.t.sol new file mode 100644 index 000000000..86ab46853 --- /dev/null +++ b/contracts/test/hooks-operator-avs/AttestationRegistry.t.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title AttestationRegistryTest +/// @notice Test suite for AttestationRegistry +/// @dev Tests attestation recording, revocation, and queries +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract AttestationRegistryTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy attestation registry + // Set task manager + // Initialize + } + + // ═══════════════════════════════════════════════════════════════════════ + // ATTESTATION RECORDING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__recordAttestationMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Registry initialized + // Task manager set + // Valid hook address + + //==============TEST======================== + // Record attestation + + //==========POST-CONDITIONS================= + // AttestationRecorded event emitted + // Attestation stored + } + + function test__unit__recordAttestationMustRevertForZeroAddress() public { + //==========PRE-CONDITIONS================== + // Registry initialized + + //==============TEST======================== + // Record attestation for zero address + + //==========POST-CONDITIONS================= + // Reverts with InvalidAttestation error + } + + function test__unit__recordAttestationMustOnlyBeCallableByTaskManager() public { + //==========PRE-CONDITIONS================== + // Registry initialized + // Caller is not task manager + + //==============TEST======================== + // Attempt to record attestation + + //==========POST-CONDITIONS================= + // Reverts with OnlyTaskManager error + } + + function test__unit__attestationMustSetCorrectExpiry() public { + //==========PRE-CONDITIONS================== + // Registry initialized + + //==============TEST======================== + // Record attestation + + //==========POST-CONDITIONS================= + // expiresAt = block.timestamp + ATTESTATION_VALIDITY_PERIOD + } + + function test__unit__attestationMustBeAddedToHistory() public { + //==========PRE-CONDITIONS================== + // Hook with no previous attestations + + //==============TEST======================== + // Record attestation + + //==========POST-CONDITIONS================= + // Attestation ID added to history array + } + + // ═══════════════════════════════════════════════════════════════════════ + // REVOCATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__revokeAttestationMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Attestation recorded + + //==============TEST======================== + // Revoke attestation + + //==========POST-CONDITIONS================= + // AttestationRevoked event emitted + // isValid = false + } + + function test__unit__revokeAttestationMustRevertIfNotFound() public { + //==========PRE-CONDITIONS================== + // No attestation for hook + + //==============TEST======================== + // Attempt revocation + + //==========POST-CONDITIONS================= + // Reverts with AttestationNotFound error + } + + function test__unit__revokeAttestationMustRevertIfAlreadyRevoked() public { + //==========PRE-CONDITIONS================== + // Attestation already revoked + + //==============TEST======================== + // Attempt second revocation + + //==========POST-CONDITIONS================= + // Reverts with AttestationAlreadyRevoked error + } + + function test__unit__revokeAttestationMustOnlyBeCallableByTaskManager() public { + //==========PRE-CONDITIONS================== + // Attestation recorded + // Caller is not task manager + + //==============TEST======================== + // Attempt revocation + + //==========POST-CONDITIONS================= + // Reverts with OnlyTaskManager error + } + + // ═══════════════════════════════════════════════════════════════════════ + // RENEWAL TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__renewAttestationMustExtendExpiry() public { + //==========PRE-CONDITIONS================== + // Attestation recorded + // Near expiry + + //==============TEST======================== + // Renew attestation + + //==========POST-CONDITIONS================= + // New expiry = block.timestamp + VALIDITY_PERIOD + // AttestationRenewed event emitted + } + + function test__unit__renewAttestationMustCreateNewAttestationId() public { + //==========PRE-CONDITIONS================== + // Attestation recorded + + //==============TEST======================== + // Renew attestation + + //==========POST-CONDITIONS================= + // New attestation ID generated + // Added to history + } + + function test__unit__renewAttestationMustReactivateRevokedAttestation() public { + //==========PRE-CONDITIONS================== + // Attestation revoked + + //==============TEST======================== + // Renew attestation + + //==========POST-CONDITIONS================= + // isValid = true + // New expiry set + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__isHookAttestedMustReturnTrueForValidAttestation() public { + //==========PRE-CONDITIONS================== + // Valid, non-expired attestation + + //==============TEST======================== + // Call isHookAttested + + //==========POST-CONDITIONS================= + // Returns true + } + + function test__unit__isHookAttestedMustReturnFalseForExpiredAttestation() public { + //==========PRE-CONDITIONS================== + // Attestation expired + + //==============TEST======================== + // Call isHookAttested + + //==========POST-CONDITIONS================= + // Returns false + } + + function test__unit__isHookAttestedMustReturnFalseForRevokedAttestation() public { + //==========PRE-CONDITIONS================== + // Attestation revoked + + //==============TEST======================== + // Call isHookAttested + + //==========POST-CONDITIONS================= + // Returns false + } + + function test__unit__isHookAttestedMustReturnFalseForNonExistentHook() public { + //==========PRE-CONDITIONS================== + // No attestation for hook + + //==============TEST======================== + // Call isHookAttested + + //==========POST-CONDITIONS================= + // Returns false + } + + function test__unit__getAttestationMustReturnCorrectData() public { + //==========PRE-CONDITIONS================== + // Attestation recorded + + //==============TEST======================== + // Call getAttestation + + //==========POST-CONDITIONS================= + // All fields match recorded data + } + + function test__unit__getAttestationHistoryMustReturnAllAttestations() public { + //==========PRE-CONDITIONS================== + // Multiple attestations recorded for hook + + //==============TEST======================== + // Call getAttestationHistory + + //==========POST-CONDITIONS================= + // Returns all attestation IDs in order + } + + function test__unit__getAttestationByIdMustReturnCorrectData() public { + //==========PRE-CONDITIONS================== + // Attestation recorded with known ID + + //==============TEST======================== + // Call getAttestationById + + //==========POST-CONDITIONS================= + // Returns correct attestation + } +} diff --git a/contracts/test/hooks-operator-avs/ClearingHouseEscrow.t.sol b/contracts/test/hooks-operator-avs/ClearingHouseEscrow.t.sol new file mode 100644 index 000000000..b82aaf577 --- /dev/null +++ b/contracts/test/hooks-operator-avs/ClearingHouseEscrow.t.sol @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title ClearingHouseTest +/// @notice Test suite for ClearingHouse +/// @dev Tests bonded engagement acceptance and termination +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract ClearingHouseTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy mock registry coordinator + // Deploy mock HaaS hub + // Deploy clearing house + // Initialize + } + + // ═══════════════════════════════════════════════════════════════════════ + // BONDED ENGAGEMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__acceptBondedEngagementMustRegisterOperator() public { + //==========PRE-CONDITIONS================== + // Valid license + // Valid operator signature + + //==============TEST======================== + // Accept bonded engagement + + //==========POST-CONDITIONS================= + // Operator registered with RegistryCoordinator + // Engagement marked as active + } + + function test__unit__acceptBondedEngagementMustEmitEvents() public { + //==========PRE-CONDITIONS================== + // Valid inputs + + //==============TEST======================== + // Accept bonded engagement + + //==========POST-CONDITIONS================= + // BondedEngagementAccepted event emitted + // QuorumRegistered event emitted + } + + function test__unit__acceptBondedEngagementMustRevertForInvalidLicense() public { + //==========PRE-CONDITIONS================== + // Invalid license ID + + //==============TEST======================== + // Accept bonded engagement + + //==========POST-CONDITIONS================= + // Reverts with InvalidLicense error + } + + function test__unit__acceptBondedEngagementMustRevertIfAlreadyAccepted() public { + //==========PRE-CONDITIONS================== + // Engagement already accepted for license + + //==============TEST======================== + // Accept again + + //==========POST-CONDITIONS================= + // Reverts with EngagementAlreadyAccepted error + } + + function test__unit__acceptBondedEngagementMustUseCorrectQuorums() public { + //==========PRE-CONDITIONS================== + // License with specific quorum numbers + + //==============TEST======================== + // Accept bonded engagement + + //==========POST-CONDITIONS================= + // RegistryCoordinator called with correct quorums + } + + // ═══════════════════════════════════════════════════════════════════════ + // TERMINATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__terminateBondedEngagementMustDeregisterOperator() public { + //==========PRE-CONDITIONS================== + // Engagement active + + //==============TEST======================== + // Terminate engagement + + //==========POST-CONDITIONS================= + // Operator deregistered from RegistryCoordinator + // Engagement marked as inactive + } + + function test__unit__terminateBondedEngagementMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Engagement active + + //==============TEST======================== + // Terminate engagement + + //==========POST-CONDITIONS================= + // BondedEngagementTerminated event emitted + } + + function test__unit__terminateBondedEngagementMustOnlyBeCallableByOperatorOrOwner() public { + //==========PRE-CONDITIONS================== + // Caller is neither operator nor owner + + //==============TEST======================== + // Attempt termination + + //==========POST-CONDITIONS================= + // Reverts with Unauthorized error + } + + function test__unit__terminateBondedEngagementMustRevertForInactiveEngagement() public { + //==========PRE-CONDITIONS================== + // No active engagement + + //==============TEST======================== + // Attempt termination + + //==========POST-CONDITIONS================= + // Reverts with InvalidLicense error + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__isEngagementActiveMustReturnCorrectValue() public { + //==========PRE-CONDITIONS================== + // Engagement accepted + + //==============TEST======================== + // Check isEngagementActive + + //==========POST-CONDITIONS================= + // Returns true + } + + function test__unit__getEngagementOperatorMustReturnCorrectAddress() public { + //==========PRE-CONDITIONS================== + // Engagement accepted by operator + + //==============TEST======================== + // Get engagement operator + + //==========POST-CONDITIONS================= + // Returns correct operator address + } +} + +/// @title EscrowCoordinatorTest +/// @notice Test suite for EscrowCoordinator +/// @dev Tests bond posting, release, and slashing +contract EscrowCoordinatorTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy mock market oracle + // Deploy mock strategy manager + // Deploy mock ERC20 token + // Deploy escrow coordinator + // Initialize + } + + // ═══════════════════════════════════════════════════════════════════════ + // BOND POSTING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__postBondMustTransferTokens() public { + //==========PRE-CONDITIONS================== + // User has approved tokens + // Market oracle returns bond amount + + //==============TEST======================== + // Post bond + + //==========POST-CONDITIONS================= + // Tokens transferred from user + // Tokens deposited in strategy + } + + function test__unit__postBondMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Valid inputs + + //==============TEST======================== + // Post bond + + //==========POST-CONDITIONS================= + // BondPosted event emitted + } + + function test__unit__postBondMustRevertIfAlreadyPosted() public { + //==========PRE-CONDITIONS================== + // Bond already posted for license + + //==============TEST======================== + // Post bond again + + //==========POST-CONDITIONS================= + // Reverts with BondAlreadyPosted error + } + + function test__unit__postBondWithAmountMustRevertIfInsufficient() public { + //==========PRE-CONDITIONS================== + // Amount less than required + + //==============TEST======================== + // Post bond with insufficient amount + + //==========POST-CONDITIONS================= + // Reverts with InsufficientBond error + } + + function test__unit__postBondMustSetLockPeriod() public { + //==========PRE-CONDITIONS================== + // Bond not posted + + //==============TEST======================== + // Post bond + + //==========POST-CONDITIONS================= + // lockedUntil = block.timestamp + BOND_LOCK_PERIOD + } + + // ═══════════════════════════════════════════════════════════════════════ + // BOND RELEASE TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__releaseBondMustTransferTokensBack() public { + //==========PRE-CONDITIONS================== + // Bond posted + // Lock period expired + + //==============TEST======================== + // Release bond + + //==========POST-CONDITIONS================= + // Tokens transferred to depositor + } + + function test__unit__releaseBondMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Bond posted and unlocked + + //==============TEST======================== + // Release bond + + //==========POST-CONDITIONS================= + // BondReleased event emitted + } + + function test__unit__releaseBondMustRevertIfNotPosted() public { + //==========PRE-CONDITIONS================== + // No bond posted + + //==============TEST======================== + // Attempt release + + //==========POST-CONDITIONS================= + // Reverts with BondNotPosted error + } + + function test__unit__releaseBondMustRevertIfLocked() public { + //==========PRE-CONDITIONS================== + // Bond posted + // Lock period not expired + + //==============TEST======================== + // Attempt release + + //==========POST-CONDITIONS================= + // Reverts with BondLocked error + } + + function test__unit__releaseBondMustOnlyBeCallableByDepositorOrOwner() public { + //==========PRE-CONDITIONS================== + // Caller is neither depositor nor owner + + //==============TEST======================== + // Attempt release + + //==========POST-CONDITIONS================= + // Reverts with Unauthorized error + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLASHING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__slashBondMustReduceDepositedAmount() public { + //==========PRE-CONDITIONS================== + // Bond posted with amount X + // Slash amount Y < X + + //==============TEST======================== + // Slash bond + + //==========POST-CONDITIONS================= + // depositedAmount = X - Y + } + + function test__unit__slashBondMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Bond posted + + //==============TEST======================== + // Slash bond + + //==========POST-CONDITIONS================= + // BondSlashed event emitted + } + + function test__unit__slashBondMustTransferToOwner() public { + //==========PRE-CONDITIONS================== + // Bond posted + + //==============TEST======================== + // Slash bond + + //==========POST-CONDITIONS================= + // Slashed amount transferred to owner + } + + function test__unit__slashBondMustCapAtDepositedAmount() public { + //==========PRE-CONDITIONS================== + // Slash amount > deposited amount + + //==============TEST======================== + // Slash bond + + //==========POST-CONDITIONS================= + // Only deposited amount slashed + } + + function test__unit__slashBondMustDeactivateIfFullySlashed() public { + //==========PRE-CONDITIONS================== + // Slash entire deposit + + //==============TEST======================== + // Slash bond + + //==========POST-CONDITIONS================= + // isActive = false + } + + function test__unit__slashBondMustOnlyBeCallableByOwner() public { + //==========PRE-CONDITIONS================== + // Caller is not owner + + //==============TEST======================== + // Attempt slash + + //==========POST-CONDITIONS================= + // Reverts + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getBondDetailsMustReturnCorrectData() public { + //==========PRE-CONDITIONS================== + // Bond posted + + //==============TEST======================== + // Get bond details + + //==========POST-CONDITIONS================= + // All fields correct + } + + function test__unit__isBondPostedMustReturnCorrectValue() public { + //==========PRE-CONDITIONS================== + // Bond posted + + //==============TEST======================== + // Check isBondPosted + + //==========POST-CONDITIONS================= + // Returns true + } + + function test__unit__getRequiredBondMustQueryMarketOracle() public { + //==========PRE-CONDITIONS================== + // Market oracle configured + + //==============TEST======================== + // Get required bond + + //==========POST-CONDITIONS================= + // Returns oracle values + } +} diff --git a/contracts/test/hooks-operator-avs/HaaSVendorManagement.t.sol b/contracts/test/hooks-operator-avs/HaaSVendorManagement.t.sol new file mode 100644 index 000000000..ce7f66be2 --- /dev/null +++ b/contracts/test/hooks-operator-avs/HaaSVendorManagement.t.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title HaaSVendorManagementTest +/// @notice Test suite for HaaSVendorManagement (HookLicense NFT) +/// @dev Tests license issuance, operator registration, and license management +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HaaSVendorManagementTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy vendor management + // Initialize + } + + // ═══════════════════════════════════════════════════════════════════════ + // LICENSE ISSUANCE TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__commitToHookSpecMustMintLicenseNFT() public { + //==========PRE-CONDITIONS================== + // Vendor management initialized + // Valid hook spec URI + // Valid operator account + + //==============TEST======================== + // Commit to hook spec + + //==========POST-CONDITIONS================= + // License NFT minted to operator + // ownerOf(licenseId) == operatorAccount + } + + function test__unit__commitToHookSpecMustEmitEvents() public { + //==========PRE-CONDITIONS================== + // Vendor management initialized + + //==============TEST======================== + // Commit to hook spec + + //==========POST-CONDITIONS================= + // HookDeveloperRegistered event emitted + // HookLicenseIssued event emitted + } + + function test__unit__commitToHookSpecMustIncrementLicenseId() public { + //==========PRE-CONDITIONS================== + // Initial licenseId = 0 + + //==============TEST======================== + // Commit to hook spec twice + + //==========POST-CONDITIONS================= + // First license ID = 0 + // Second license ID = 1 + } + + function test__unit__commitToHookSpecMustRevertForEmptyURI() public { + //==========PRE-CONDITIONS================== + // Empty hook spec URI + + //==============TEST======================== + // Commit to hook spec + + //==========POST-CONDITIONS================= + // Reverts with InvalidHookSpec error + } + + function test__unit__commitToHookSpecMustRevertForZeroOperator() public { + //==========PRE-CONDITIONS================== + // Zero address operator + + //==============TEST======================== + // Commit to hook spec + + //==========POST-CONDITIONS================= + // Reverts with OperatorNotRegistered error + } + + function test__unit__commitToHookSpecMustStoreSpecURI() public { + //==========PRE-CONDITIONS================== + // Valid inputs + + //==============TEST======================== + // Commit to hook spec + + //==========POST-CONDITIONS================= + // tokenURI returns spec URI + } + + // ═══════════════════════════════════════════════════════════════════════ + // LICENSE MANAGEMENT TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getHookLicenseMustReturnCorrectData() public { + //==========PRE-CONDITIONS================== + // License issued + + //==============TEST======================== + // Get hook license + + //==========POST-CONDITIONS================= + // License ID matches + // Strategies array accessible + } + + function test__unit__getHookLicenseMustRevertForInvalidId() public { + //==========PRE-CONDITIONS================== + // License not issued + + //==============TEST======================== + // Get hook license for invalid ID + + //==========POST-CONDITIONS================= + // Reverts with LicenseNotFound error + } + + function test__unit__getOperatorLicensesMustReturnAllLicenses() public { + //==========PRE-CONDITIONS================== + // Operator with multiple licenses + + //==============TEST======================== + // Get operator licenses + + //==========POST-CONDITIONS================= + // Returns array of all license IDs + } + + function test__unit__isLicenseValidMustReturnTrueForActiveLicense() public { + //==========PRE-CONDITIONS================== + // License issued and active + + //==============TEST======================== + // Check isLicenseValid + + //==========POST-CONDITIONS================= + // Returns true + } + + function test__unit__isLicenseValidMustReturnFalseForRevokedLicense() public { + //==========PRE-CONDITIONS================== + // License revoked + + //==============TEST======================== + // Check isLicenseValid + + //==========POST-CONDITIONS================= + // Returns false + } + + // ═══════════════════════════════════════════════════════════════════════ + // REVOCATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__revokeLicenseMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // License issued + // Caller is owner + + //==============TEST======================== + // Revoke license + + //==========POST-CONDITIONS================= + // HookLicenseRevoked event emitted + } + + function test__unit__revokeLicenseMustSetValidityFalse() public { + //==========PRE-CONDITIONS================== + // License issued + + //==============TEST======================== + // Revoke license + + //==========POST-CONDITIONS================= + // isLicenseValid returns false + } + + function test__unit__revokeLicenseMustRevertForNonOwner() public { + //==========PRE-CONDITIONS================== + // Caller is not owner + + //==============TEST======================== + // Attempt revocation + + //==========POST-CONDITIONS================= + // Reverts + } + + function test__unit__revokeLicenseMustRevertForInvalidLicense() public { + //==========PRE-CONDITIONS================== + // License does not exist + + //==============TEST======================== + // Attempt revocation + + //==========POST-CONDITIONS================= + // Reverts with LicenseNotFound error + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENGAGEMENT PARAMS TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getHaaSEngagementParamsMustReturnSetValues() public { + //==========PRE-CONDITIONS================== + // License with quorum and pubkey params set + + //==============TEST======================== + // Get engagement params + + //==========POST-CONDITIONS================= + // Returns correct quorum numbers + // Returns correct pubkey params + } + + function test__unit__getOperatorSocketMustReturnSetValue() public { + //==========PRE-CONDITIONS================== + // License with socket set + + //==============TEST======================== + // Get operator socket + + //==========POST-CONDITIONS================= + // Returns correct socket bytes + } + + function test__unit__setLicenseQuorumsMustOnlyBeCallableByOwner() public { + //==========PRE-CONDITIONS================== + // Caller is not owner + + //==============TEST======================== + // Attempt to set quorums + + //==========POST-CONDITIONS================= + // Reverts + } + + // ═══════════════════════════════════════════════════════════════════════ + // ERC721 TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__tokenURIMustReturnSpecURI() public { + //==========PRE-CONDITIONS================== + // License issued with spec URI + + //==============TEST======================== + // Get tokenURI + + //==========POST-CONDITIONS================= + // Returns spec URI + } + + function test__unit__licenseNFTMustBeTransferable() public { + //==========PRE-CONDITIONS================== + // License issued to operator A + + //==============TEST======================== + // Transfer to operator B + + //==========POST-CONDITIONS================= + // ownerOf returns operator B + } +} diff --git a/contracts/test/hooks-operator-avs/HookAttestationServiceManager.t.sol b/contracts/test/hooks-operator-avs/HookAttestationServiceManager.t.sol new file mode 100644 index 000000000..e0ec22515 --- /dev/null +++ b/contracts/test/hooks-operator-avs/HookAttestationServiceManager.t.sol @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title HookAttestationServiceManagerTest +/// @notice Test suite for HookAttestationServiceManager +/// @dev Tests operator registration, slashing, and service manager functions +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HookAttestationServiceManagerTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy mock AVS directory + // Deploy mock rewards coordinator + // Deploy mock stake registry + // Deploy service manager + // Initialize + } + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__initializeMustSetOwner() public { + //==========PRE-CONDITIONS================== + // Service manager deployed + + //==============TEST======================== + // Initialize with owner + + //==========POST-CONDITIONS================= + // Owner set correctly + } + + function test__unit__initializeMustSetRewardsInitiator() public { + //==========PRE-CONDITIONS================== + // Service manager deployed + + //==============TEST======================== + // Initialize + + //==========POST-CONDITIONS================= + // Rewards initiator set + } + + function test__unit__initializeMustSetTaskManager() public { + //==========PRE-CONDITIONS================== + // Service manager deployed + + //==============TEST======================== + // Initialize + + //==========POST-CONDITIONS================= + // Task manager set + } + + function test__unit__cannotInitializeTwice() public { + //==========PRE-CONDITIONS================== + // Already initialized + + //==============TEST======================== + // Attempt second initialization + + //==========POST-CONDITIONS================= + // Reverts + } + + // ═══════════════════════════════════════════════════════════════════════ + // OPERATOR REGISTRATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__registerOperatorMustCallAVSDirectory() public { + //==========PRE-CONDITIONS================== + // Service manager initialized + // Valid operator signature + + //==============TEST======================== + // Register operator + + //==========POST-CONDITIONS================= + // AVS directory registerOperatorToAVS called + // Operator marked as registered + } + + function test__unit__deregisterOperatorMustCallAVSDirectory() public { + //==========PRE-CONDITIONS================== + // Operator registered + + //==============TEST======================== + // Deregister operator + + //==========POST-CONDITIONS================= + // AVS directory deregisterOperatorFromAVS called + // Operator marked as not registered + } + + function test__unit__isOperatorRegisteredMustReturnCorrectValue() public { + //==========PRE-CONDITIONS================== + // Operator registered + + //==============TEST======================== + // Check isOperatorRegistered + + //==========POST-CONDITIONS================= + // Returns true + } + + // ═══════════════════════════════════════════════════════════════════════ + // ADMIN FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__setTaskManagerMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Service manager initialized + // Caller is owner + + //==============TEST======================== + // Set task manager + + //==========POST-CONDITIONS================= + // TaskManagerUpdated event emitted + // Task manager updated + } + + function test__unit__setTaskManagerMustRevertForNonOwner() public { + //==========PRE-CONDITIONS================== + // Caller is not owner + + //==============TEST======================== + // Attempt to set task manager + + //==========POST-CONDITIONS================= + // Reverts + } + + function test__unit__setAttestationRegistryMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Service manager initialized + // Caller is owner + + //==============TEST======================== + // Set attestation registry + + //==========POST-CONDITIONS================= + // AttestationRegistryUpdated event emitted + } + + function test__unit__updateAVSMetadataURIMustCallAVSDirectory() public { + //==========PRE-CONDITIONS================== + // Service manager initialized + // Caller is owner + + //==============TEST======================== + // Update metadata URI + + //==========POST-CONDITIONS================= + // AVS directory updateAVSMetadataURI called + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLASHING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__slashOperatorMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Operator registered with stake + // Caller is task manager + + //==============TEST======================== + // Slash operator + + //==========POST-CONDITIONS================= + // OperatorSlashed event emitted + // Stake reduced + } + + function test__unit__slashOperatorMustRevertForUnregisteredOperator() public { + //==========PRE-CONDITIONS================== + // Operator not registered + + //==============TEST======================== + // Attempt to slash + + //==========POST-CONDITIONS================= + // Reverts with OperatorNotRegistered error + } + + function test__unit__slashOperatorMustRevertForNonTaskManager() public { + //==========PRE-CONDITIONS================== + // Caller is not task manager + + //==============TEST======================== + // Attempt to slash + + //==========POST-CONDITIONS================= + // Reverts with OnlyTaskManager error + } + + function test__unit__slashAmountMustDependOnOffenseType() public { + //==========PRE-CONDITIONS================== + // Operator with known stake + + //==============TEST======================== + // Slash for different offense types + + //==========POST-CONDITIONS================= + // COLLUSION: 100% slashed + // FALSE_POSITIVE: 50% slashed + // FALSE_NEGATIVE: 30% slashed + // SAMPLE_MANIPULATION: 20% slashed + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getTaskManagerMustReturnCorrectAddress() public { + //==========PRE-CONDITIONS================== + // Task manager set + + //==============TEST======================== + // Call getTaskManager + + //==========POST-CONDITIONS================= + // Returns correct address + } + + function test__unit__getAttestationRegistryMustReturnCorrectAddress() public { + //==========PRE-CONDITIONS================== + // Registry set + + //==============TEST======================== + // Call getAttestationRegistry + + //==========POST-CONDITIONS================= + // Returns correct address + } + + function test__unit__getOperatorStakeMustReturnCorrectValue() public { + //==========PRE-CONDITIONS================== + // Operator with stake + + //==============TEST======================== + // Call getOperatorStake + + //==========POST-CONDITIONS================= + // Returns correct stake amount + } + + function test__unit__avsDirectoryMustReturnImmutableAddress() public { + //==========PRE-CONDITIONS================== + // Service manager deployed + + //==============TEST======================== + // Call avsDirectory + + //==========POST-CONDITIONS================= + // Returns constructor-set address + } +} + +/// @title HookAttestationServiceManagerIntegrationTest +/// @notice Integration tests for service manager with EigenLayer +contract HookAttestationServiceManagerIntegrationTest is Test { + + function setUp() public { + // Full EigenLayer integration setup + } + + function test__integration__operatorRegistrationFlow() public { + //==========PRE-CONDITIONS================== + // EigenLayer contracts deployed + // Operator with stake in DelegationManager + + //==============TEST======================== + // Register operator with signature + + //==========POST-CONDITIONS================= + // Operator registered in AVS + // Can participate in verification tasks + } + + function test__integration__slashingWithAllocationManager() public { + //==========PRE-CONDITIONS================== + // Operator registered with allocated stake + // Slashing enabled + + //==============TEST======================== + // Slash operator for offense + + //==========POST-CONDITIONS================= + // AllocationManager slashing executed + // Stake reduced + } +} diff --git a/contracts/test/hooks-operator-avs/HookAttestationTaskManager.t.sol b/contracts/test/hooks-operator-avs/HookAttestationTaskManager.t.sol new file mode 100644 index 000000000..ca2be78bc --- /dev/null +++ b/contracts/test/hooks-operator-avs/HookAttestationTaskManager.t.sol @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title HookAttestationTaskManagerTest +/// @notice Test suite for HookAttestationTaskManager +/// @dev Tests task creation, response submission, and challenge mechanisms +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HookAttestationTaskManagerTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy mock BLS signature checker + // Deploy attestation registry + // Deploy task manager + // Initialize with test parameters + } + + // ═══════════════════════════════════════════════════════════════════════ + // TASK CREATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__createAttestationTaskMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Task manager initialized + // Valid hook address + // Valid specification URI + + //==============TEST======================== + // Create attestation task + + //==========POST-CONDITIONS================= + // Task created event emitted + // Task index incremented + // Task hash stored + } + + function test__unit__createTaskMustRevertForZeroHookAddress() public { + //==========PRE-CONDITIONS================== + // Task manager initialized + + //==============TEST======================== + // Create task with zero address hook + + //==========POST-CONDITIONS================= + // Reverts with InvalidTask error + } + + function test__unit__createTaskMustRevertForEmptySpecificationURI() public { + //==========PRE-CONDITIONS================== + // Task manager initialized + + //==============TEST======================== + // Create task with empty URI + + //==========POST-CONDITIONS================= + // Reverts with InvalidTask error + } + + function test__unit__createTaskMustRevertForEmptyPoolIds() public { + //==========PRE-CONDITIONS================== + // Task manager initialized + + //==============TEST======================== + // Create task with empty poolIds array + + //==========POST-CONDITIONS================= + // Reverts with InvalidTask error + } + + function test__unit__createTaskMustStoreCorrectTaskData() public { + //==========PRE-CONDITIONS================== + // Task manager initialized + // Valid task parameters + + //==============TEST======================== + // Create attestation task + // Retrieve task data + + //==========POST-CONDITIONS================= + // Hook address matches + // Specification URI matches + // Pool IDs match + // Callbacks match + // Sample count matches + } + + // ═══════════════════════════════════════════════════════════════════════ + // TASK RESPONSE TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__respondToTaskMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Task created + // Within response window + // Valid BLS signature + + //==============TEST======================== + // Submit response + + //==========POST-CONDITIONS================= + // TaskResponded event emitted + // Response stored + } + + function test__unit__respondToTaskMustRevertForInvalidTask() public { + //==========PRE-CONDITIONS================== + // No task created + + //==============TEST======================== + // Submit response for non-existent task + + //==========POST-CONDITIONS================= + // Reverts with InvalidTask error + } + + function test__unit__respondToTaskMustRevertIfAlreadyResponded() public { + //==========PRE-CONDITIONS================== + // Task created + // Response already submitted + + //==============TEST======================== + // Submit duplicate response + + //==========POST-CONDITIONS================= + // Reverts with TaskAlreadyResponded error + } + + function test__unit__respondToTaskMustRevertAfterResponseWindow() public { + //==========PRE-CONDITIONS================== + // Task created + // Response window expired + + //==============TEST======================== + // Submit response after window + + //==========POST-CONDITIONS================= + // Reverts with TaskExpired error + } + + function test__unit__compliantResponseMustRecordAttestation() public { + //==========PRE-CONDITIONS================== + // Task created + // Valid response with specCompliant = true + + //==============TEST======================== + // Submit compliant response + + //==========POST-CONDITIONS================= + // Attestation recorded in registry + // Hook marked as attested + } + + function test__unit__nonCompliantResponseMustNotRecordAttestation() public { + //==========PRE-CONDITIONS================== + // Task created + // Response with specCompliant = false + + //==============TEST======================== + // Submit non-compliant response + + //==========POST-CONDITIONS================= + // No attestation recorded + // TaskCompleted emitted with false + } + + // ═══════════════════════════════════════════════════════════════════════ + // CHALLENGE TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__challengeFalsePositiveMustRevokeAttestation() public { + //==========PRE-CONDITIONS================== + // Task completed with compliant = true + // Valid counter sample proving non-compliance + + //==============TEST======================== + // Challenge false positive + + //==========POST-CONDITIONS================= + // Attestation revoked + // Challenge event emitted + } + + function test__unit__challengeFalsePositiveMustRevertAfterWindow() public { + //==========PRE-CONDITIONS================== + // Task completed + // Challenge window expired + + //==============TEST======================== + // Attempt challenge + + //==========POST-CONDITIONS================= + // Reverts with ChallengeWindowExpired error + } + + function test__unit__challengeFalseNegativeMustRecordAttestation() public { + //==========PRE-CONDITIONS================== + // Task completed with compliant = false + // Valid compliance samples proving hook is compliant + + //==============TEST======================== + // Challenge false negative + + //==========POST-CONDITIONS================= + // Attestation recorded + // Challenge event emitted + } + + function test__unit__challengeMustRevertForInvalidEvidence() public { + //==========PRE-CONDITIONS================== + // Task completed + // Invalid counter/compliance samples + + //==============TEST======================== + // Attempt challenge with invalid evidence + + //==========POST-CONDITIONS================= + // Challenge fails + // No state changes + } + + // ═══════════════════════════════════════════════════════════════════════ + // VIEW FUNCTION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__getTaskMustReturnCorrectData() public { + //==========PRE-CONDITIONS================== + // Task created + + //==============TEST======================== + // Call getTask + + //==========POST-CONDITIONS================= + // Returns correct task data + } + + function test__unit__getTaskResponseMustReturnCorrectData() public { + //==========PRE-CONDITIONS================== + // Task responded + + //==============TEST======================== + // Call getTaskResponse + + //==========POST-CONDITIONS================= + // Returns correct response data + } + + function test__unit__latestTaskNumMustIncrementCorrectly() public { + //==========PRE-CONDITIONS================== + // Multiple tasks created + + //==============TEST======================== + // Check latestTaskNum + + //==========POST-CONDITIONS================= + // Returns correct count + } +} + +/// @title HookAttestationTaskManagerIntegrationTest +/// @notice Integration tests for task manager with other AVS components +contract HookAttestationTaskManagerIntegrationTest is Test { + + function setUp() public { + // Full AVS stack deployment + } + + function test__integration__fullTaskLifecycle() public { + //==========PRE-CONDITIONS================== + // Full AVS stack deployed + // Operator registered + // Hook deployed + + //==============TEST======================== + // 1. Create attestation task + // 2. Operators verify hook + // 3. Submit aggregated response + // 4. Attestation recorded + + //==========POST-CONDITIONS================= + // Hook is attested + // Operators received task reward + } + + function test__integration__taskWithBLSSignatureVerification() public { + //==========PRE-CONDITIONS================== + // BLS signature checker configured + // Operators with BLS keys registered + + //==============TEST======================== + // Submit response with valid BLS signature + + //==========POST-CONDITIONS================= + // Signature verified + // Response accepted + } + + function test__integration__slashingAfterSuccessfulChallenge() public { + //==========PRE-CONDITIONS================== + // Task completed with false attestation + // Valid challenge evidence + + //==============TEST======================== + // Challenge and slash operators + + //==========POST-CONDITIONS================= + // Operators slashed + // Attestation revoked + } +} diff --git a/contracts/test/hooks-operator-avs/HookStateSampler.t.sol b/contracts/test/hooks-operator-avs/HookStateSampler.t.sol new file mode 100644 index 000000000..a9c924f0f --- /dev/null +++ b/contracts/test/hooks-operator-avs/HookStateSampler.t.sol @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; + +/// @title HookStateSamplerTest +/// @notice Test suite for HookStateSampler +/// @dev Tests state sampling for behavioral verification +/// @dev Reference: docs/hook-pkg/architecture/avs-verification-system.md +contract HookStateSamplerTest is Test { + + // ═══════════════════════════════════════════════════════════════════════ + // SETUP + // ═══════════════════════════════════════════════════════════════════════ + + function setUp() public { + // Deploy mock state view + // Deploy mock hook + // Deploy sampler + } + + // ═══════════════════════════════════════════════════════════════════════ + // STATE SAMPLING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__sampleCurrentStateMustCaptureBlockInfo() public { + //==========PRE-CONDITIONS================== + // Valid pool and hook + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // blockNumber = block.number + // timestamp = block.timestamp + } + + function test__unit__sampleCurrentStateMustCapturePoolId() public { + //==========PRE-CONDITIONS================== + // Specific pool ID + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // poolId matches input + } + + function test__unit__sampleCurrentStateMustCaptureLPState() public { + //==========PRE-CONDITIONS================== + // Pool with LP state + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // lpState populated + } + + function test__unit__sampleCurrentStateMustCaptureTraderState() public { + //==========PRE-CONDITIONS================== + // Pool with trader state + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // traderState populated + } + + function test__unit__sampleCurrentStateMustCaptureHookState() public { + //==========PRE-CONDITIONS================== + // Hook with state + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // hookState populated + } + + function test__unit__sampleCurrentStateMustCaptureSharedState() public { + //==========PRE-CONDITIONS================== + // Pool with fee growth state + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // sharedState = encoded(feeGrowth0, feeGrowth1) + } + + function test__unit__sampleCurrentStateMustRevertForInvalidPool() public { + //==========PRE-CONDITIONS================== + // Zero poolId + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // Reverts with InvalidPool error + } + + function test__unit__sampleCurrentStateMustRevertForInvalidHook() public { + //==========PRE-CONDITIONS================== + // Zero hook address + + //==============TEST======================== + // Sample current state + + //==========POST-CONDITIONS================= + // Reverts with InvalidHook error + } + + // ═══════════════════════════════════════════════════════════════════════ + // TRANSITION SAMPLING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__sampleTransitionMustCapturePreState() public { + //==========PRE-CONDITIONS================== + // Valid pool and callback + + //==============TEST======================== + // Sample transition + + //==========POST-CONDITIONS================= + // preState populated before callback + } + + function test__unit__sampleTransitionMustCapturePostState() public { + //==========PRE-CONDITIONS================== + // Valid callback that modifies state + + //==============TEST======================== + // Sample transition + + //==========POST-CONDITIONS================= + // postState captured after callback + } + + function test__unit__sampleTransitionMustCaptureCallbackInfo() public { + //==========PRE-CONDITIONS================== + // Specific callback and input + + //==============TEST======================== + // Sample transition + + //==========POST-CONDITIONS================= + // callback selector stored + // input bytes stored + } + + function test__unit__sampleTransitionMustCaptureGasUsed() public { + //==========PRE-CONDITIONS================== + // Callback execution + + //==============TEST======================== + // Sample transition + + //==========POST-CONDITIONS================= + // gasUsed > 0 + } + + function test__unit__sampleTransitionMustCaptureReturnData() public { + //==========PRE-CONDITIONS================== + // Callback returns data + + //==============TEST======================== + // Sample transition + + //==========POST-CONDITIONS================= + // returnData populated + } + + function test__unit__sampleTransitionMustEmitEvent() public { + //==========PRE-CONDITIONS================== + // Valid inputs + + //==============TEST======================== + // Sample transition + + //==========POST-CONDITIONS================= + // TransitionSampled event emitted + } + + // ═══════════════════════════════════════════════════════════════════════ + // BATCH SAMPLING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__batchSampleStatesMustReturnAllSamples() public { + //==========PRE-CONDITIONS================== + // Multiple pool IDs + + //==============TEST======================== + // Batch sample states + + //==========POST-CONDITIONS================= + // samples.length == poolIds.length + } + + function test__unit__batchSampleStatesMustSampleInOrder() public { + //==========PRE-CONDITIONS================== + // Ordered pool IDs + + //==============TEST======================== + // Batch sample states + + //==========POST-CONDITIONS================= + // samples[i].poolId == poolIds[i] + } + + // ═══════════════════════════════════════════════════════════════════════ + // HASH COMPUTATION TESTS + // ═══════════════════════════════════════════════════════════════════════ + + function test__unit__computeSamplesHashMustBeDeterministic() public { + //==========PRE-CONDITIONS================== + // Same samples array + + //==============TEST======================== + // Compute hash twice + + //==========POST-CONDITIONS================= + // Both hashes equal + } + + function test__unit__computeSamplesHashMustDifferForDifferentSamples() public { + //==========PRE-CONDITIONS================== + // Different samples + + //==============TEST======================== + // Compute hashes + + //==========POST-CONDITIONS================= + // Hashes differ + } + + function test__unit__computeTransitionsHashMustBeDeterministic() public { + //==========PRE-CONDITIONS================== + // Same transitions array + + //==============TEST======================== + // Compute hash twice + + //==========POST-CONDITIONS================= + // Both hashes equal + } + + function test__unit__computeTransitionsHashMustIncludeAllFields() public { + //==========PRE-CONDITIONS================== + // Transitions with different fields + + //==============TEST======================== + // Change any field, compute hash + + //==========POST-CONDITIONS================= + // Hash changes for any field change + } +} + +/// @title HookStateSamplerIntegrationTest +/// @notice Integration tests for state sampler with hook verification +contract HookStateSamplerIntegrationTest is Test { + + function setUp() public { + // Deploy full hook system + // Deploy sampler + } + + function test__integration__sampleRealHookState() public { + //==========PRE-CONDITIONS================== + // Real hook deployed with state + + //==============TEST======================== + // Sample hook state + + //==========POST-CONDITIONS================= + // State accurately captured + } + + function test__integration__sampleRealTransition() public { + //==========PRE-CONDITIONS================== + // Real hook with beforeSwap callback + + //==============TEST======================== + // Sample beforeSwap transition + + //==========POST-CONDITIONS================= + // Pre/post states show difference + // Gas measured accurately + } + + function test__integration__batchSampleMultiplePools() public { + //==========PRE-CONDITIONS================== + // Multiple pools with same hook + + //==============TEST======================== + // Batch sample all pools + + //==========POST-CONDITIONS================= + // Each pool state captured + // Independent states + } +} diff --git a/docs/hook-pkg/integration-guides/cofhe-hook-template.md b/docs/hook-pkg/integration-guides/cofhe-hook-template.md new file mode 100644 index 000000000..17a57f01b --- /dev/null +++ b/docs/hook-pkg/integration-guides/cofhe-hook-template.md @@ -0,0 +1,1357 @@ +# CoFHE Hook Template: Obfuscated Hook Development Guide + +> **Status:** Architecture Design +> **Last Updated:** 2025-12-10 +> **Prerequisites:** [State-Space Model](../mathematical-models/state-space-model.md), [AVS Verification System](../architecture/avs-verification-system.md) +> **References:** [Fhenix CoFHE Docs](https://cofhe-docs.fhenix.zone/), [IHooks Interface](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IHooks.sol) + +--- + +## 1. Overview + +This document specifies the **CoFHE Hook Template** - a standardized framework for developing Uniswap V4 hooks with bytecode obfuscation using Fhenix Fully Homomorphic Encryption (FHE). The template ensures: + +1. **Code Obfuscation**: Deployed bytecode is encrypted, preventing decompilation and IP theft +2. **IHooks Compliance**: Full compatibility with Uniswap V4 PoolManager +3. **IHookStateView Compliance**: AVS operators can verify behavior without seeing source code +4. **Revenue Management**: Hook developers have APIs to manage revenue flows +5. **Third-Party Verification**: Authorized parties can verify functionality cryptographically + +--- + +## 2. Architecture Overview + +### 2.1 System Components + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ COFHE HOOK ARCHITECTURE │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ Hook Developer │ │ Protocol Admin │ │ +│ │ (Code Author) │ │ (Integrator) │ │ +│ └─────────┬───────────┘ └─────────┬───────────┘ │ +│ │ │ │ +│ │ Deploys │ Integrates │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ OBFUSCATED HOOK CONTRACT │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ IHooks │ │IHookState │ │ Revenue │ │ │ +│ │ │ Interface │ │View Compat │ │ Manager │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ ▲ ▲ ▲ │ │ +│ │ │ │ │ │ │ +│ │ ┌─────────┴───────────────┴───────────────┴──────────┐ │ │ +│ │ │ ENCRYPTED CORE LOGIC │ │ │ +│ │ │ (FHE.sol encrypted state & computation) │ │ │ +│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ +│ │ │ │ euint256 │ │ ebool │ │ eaddress │ │ │ │ +│ │ │ │ states │ │ flags │ │ access │ │ │ │ +│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ +│ │ └────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ │ State Access │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ IHookStateView │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ getLPState │ │ getTraderSt │ │ getHookState │ │ │ +│ │ │ (public) │ │ (public) │ │ (authorized) │ │ │ +│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ │ Verification │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ HOOKATTESTATIONAVS (Off-Chain) │ │ +│ │ - Samples state via IHookStateView │ │ +│ │ - Verifies behavior matches specification │ │ +│ │ - Does NOT see decrypted source code │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 Data Flow + +``` + ┌─────────────────────┐ + │ Hook Specification │ + │ (IPFS - Public) │ + └──────────┬──────────┘ + │ + ┌──────────────────────┼──────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ AVS Operators │ │ Protocol Admin │ │ Hook Developer │ +│ (Verifiers) │ │ (Integrators) │ │ (Owner) │ +└───────┬───────┘ └────────┬────────┘ └────────┬────────┘ + │ │ │ + │ Verify │ Use │ Manage + │ Behavior │ Hook │ Revenue + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────┐ +│ OBFUSCATED HOOK │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ PUBLIC INTERFACE LAYER │ │ +│ │ IHooks callbacks (beforeSwap, afterSwap, etc.) │ │ +│ │ IHookStateView getters (state sampling) │ │ +│ │ IRevenueManager (revenue withdrawal) │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ ENCRYPTED LOGIC LAYER │ │ +│ │ FHE.sol operations on encrypted state │ │ +│ │ Access control via allowThis/allowSender │ │ +│ │ Decryption only with explicit permission │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. Template Contract Structure + +### 3.1 Base Template Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {FHE, euint256, euint128, euint32, ebool} from "@fhenix/fhenix-contracts/contracts/FHE.sol"; + +/// @title ICoFHEHook +/// @notice Base interface for CoFHE-obfuscated hooks +/// @dev All hooks using the CoFHE template MUST implement this interface +interface ICoFHEHook is IHooks { + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Emitted when revenue is withdrawn by the hook developer + event RevenueWithdrawn( + address indexed recipient, + address indexed token, + uint256 amount + ); + + /// @notice Emitted when an authorized verifier is added/removed + event VerifierUpdated( + address indexed verifier, + bool authorized + ); + + /// @notice Emitted when encrypted state is updated + event EncryptedStateUpdated( + PoolId indexed poolId, + bytes32 stateHash + ); + + // ═══════════════════════════════════════════════════════════════════════ + // DEVELOPER REVENUE MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Withdraw accumulated revenue to developer address + /// @param token The token to withdraw (address(0) for ETH) + /// @param amount Amount to withdraw + /// @param recipient Recipient address + function withdrawRevenue( + address token, + uint256 amount, + address recipient + ) external; + + /// @notice Get pending revenue balance + /// @param token The token to query + /// @return balance Pending revenue balance + function pendingRevenue(address token) external view returns (uint256 balance); + + /// @notice Get the hook developer (owner) address + /// @return developer The developer address + function hookDeveloper() external view returns (address developer); + + // ═══════════════════════════════════════════════════════════════════════ + // STATE VIEW (IHookStateView COMPATIBLE) + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get hook-specific state for AVS verification + /// @dev Returns DECRYPTED state for authorized verifiers only + /// @param poolId The pool identifier + /// @return hookState ABI-encoded hook state variables + function getHookState(PoolId poolId) external view returns (bytes memory hookState); + + /// @notice Get encrypted hook state (for public queries) + /// @dev Returns encrypted handles, not plaintext values + /// @param poolId The pool identifier + /// @return encryptedState Encrypted state handles + function getEncryptedHookState(PoolId poolId) external view returns (bytes memory encryptedState); + + // ═══════════════════════════════════════════════════════════════════════ + // VERIFIER ACCESS CONTROL + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Check if an address is an authorized verifier + /// @param verifier Address to check + /// @return authorized True if authorized + function isAuthorizedVerifier(address verifier) external view returns (bool authorized); + + /// @notice Add or remove an authorized verifier + /// @dev Only callable by hook developer + /// @param verifier Address to update + /// @param authorized New authorization status + function setVerifierAuthorization(address verifier, bool authorized) external; + + // ═══════════════════════════════════════════════════════════════════════ + // SPECIFICATION METADATA + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get the IPFS URI of the hook specification + /// @return uri IPFS CID of specification document + function specificationURI() external view returns (string memory uri); + + /// @notice Get the hash of the specification for integrity verification + /// @return hash Keccak256 hash of specification + function specificationHash() external view returns (bytes32 hash); +} +``` + +### 3.2 Encrypted State Storage Pattern + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, euint256, euint128, euint32, ebool, inEuint256} from "@fhenix/fhenix-contracts/contracts/FHE.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; + +/// @title CoFHEHookStorage +/// @notice Base storage contract for encrypted hook state +/// @dev Inherit this to add encrypted state management +abstract contract CoFHEHookStorage { + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE TYPES + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Encrypted fee configuration + /// @dev Fee values kept encrypted to hide strategy + struct EncryptedFeeConfig { + euint32 baseFee; // Base fee in bps (encrypted) + euint32 maxFee; // Maximum fee cap (encrypted) + euint32 volatilityFactor; // Volatility sensitivity (encrypted) + } + + /// @notice Encrypted position tracking + /// @dev Position data encrypted to hide LP strategies + struct EncryptedPositionData { + euint128 liquidity; // Position liquidity (encrypted) + euint256 feeAccrued0; // Token0 fees earned (encrypted) + euint256 feeAccrued1; // Token1 fees earned (encrypted) + ebool isActive; // Position active flag (encrypted) + } + + /// @notice Encrypted pool metrics + /// @dev Aggregate metrics kept private + struct EncryptedPoolMetrics { + euint256 totalVolume; // Cumulative volume (encrypted) + euint256 totalFees; // Cumulative fees (encrypted) + euint128 avgLiquidity; // Average liquidity (encrypted) + euint32 txCount; // Transaction count (encrypted) + } + + // ═══════════════════════════════════════════════════════════════════════ + // STORAGE MAPPINGS + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev Pool ID => Encrypted fee configuration + mapping(PoolId => EncryptedFeeConfig) internal _encryptedFees; + + /// @dev Pool ID => Position ID => Encrypted position data + mapping(PoolId => mapping(bytes32 => EncryptedPositionData)) internal _encryptedPositions; + + /// @dev Pool ID => Encrypted pool metrics + mapping(PoolId => EncryptedPoolMetrics) internal _encryptedMetrics; + + /// @dev Authorized verifiers who can decrypt state + mapping(address => bool) internal _authorizedVerifiers; + + /// @dev Hook developer address (revenue recipient) + address internal _hookDeveloper; + + /// @dev Revenue balances per token + mapping(address => uint256) internal _revenueBalances; + + // ═══════════════════════════════════════════════════════════════════════ + // ACCESS CONTROL MODIFIERS + // ═══════════════════════════════════════════════════════════════════════ + + modifier onlyDeveloper() { + require(msg.sender == _hookDeveloper, "CoFHEHook: not developer"); + _; + } + + modifier onlyAuthorizedVerifier() { + require( + _authorizedVerifiers[msg.sender] || msg.sender == _hookDeveloper, + "CoFHEHook: not authorized verifier" + ); + _; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE OPERATIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Initialize encrypted fee configuration + /// @dev Encrypts plaintext values using FHE + function _initializeEncryptedFees( + PoolId poolId, + uint32 baseFee, + uint32 maxFee, + uint32 volatilityFactor + ) internal { + _encryptedFees[poolId] = EncryptedFeeConfig({ + baseFee: FHE.asEuint32(baseFee), + maxFee: FHE.asEuint32(maxFee), + volatilityFactor: FHE.asEuint32(volatilityFactor) + }); + + // Grant this contract permission to operate on encrypted values + FHE.allowThis(_encryptedFees[poolId].baseFee); + FHE.allowThis(_encryptedFees[poolId].maxFee); + FHE.allowThis(_encryptedFees[poolId].volatilityFactor); + } + + /// @notice Update encrypted fee with encrypted computation + /// @dev Performs arithmetic on encrypted values without revealing them + function _updateEncryptedFee( + PoolId poolId, + euint32 newBaseFee + ) internal { + EncryptedFeeConfig storage config = _encryptedFees[poolId]; + + // Encrypted comparison: ensure new fee <= maxFee + ebool isValid = FHE.lte(newBaseFee, config.maxFee); + + // Encrypted select: use new fee if valid, else keep old + config.baseFee = FHE.select(isValid, newBaseFee, config.baseFee); + + // Re-grant permission after update + FHE.allowThis(config.baseFee); + } + + /// @notice Decrypt state for authorized verifiers + /// @dev Only callable by authorized verifiers + function _decryptFeeConfig( + PoolId poolId + ) internal view onlyAuthorizedVerifier returns ( + uint32 baseFee, + uint32 maxFee, + uint32 volatilityFactor + ) { + EncryptedFeeConfig storage config = _encryptedFees[poolId]; + + // Decrypt encrypted values (requires permission) + baseFee = FHE.decrypt(config.baseFee); + maxFee = FHE.decrypt(config.maxFee); + volatilityFactor = FHE.decrypt(config.volatilityFactor); + } +} +``` + +### 3.3 Complete Template Implementation + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FHE, euint256, euint128, euint32, ebool} from "@fhenix/fhenix-contracts/contracts/FHE.sol"; +import {ICoFHEHook} from "./interfaces/ICoFHEHook.sol"; +import {CoFHEHookStorage} from "./CoFHEHookStorage.sol"; + +/// @title CoFHEHookTemplate +/// @notice Template for CoFHE-obfuscated Uniswap V4 hooks +/// @dev Extends this contract to create obfuscated hooks +abstract contract CoFHEHookTemplate is BaseHook, ICoFHEHook, CoFHEHookStorage { + using PoolIdLibrary for PoolKey; + + // ═══════════════════════════════════════════════════════════════════════ + // IMMUTABLES + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev IPFS URI of the hook specification + string private _specificationURI; + + /// @dev Hash of specification for integrity verification + bytes32 private _specificationHash; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor( + IPoolManager poolManager_, + address developer_, + string memory specificationURI_, + bytes32 specificationHash_ + ) BaseHook(poolManager_) { + require(developer_ != address(0), "CoFHEHook: zero developer"); + require(bytes(specificationURI_).length > 0, "CoFHEHook: empty spec URI"); + + _hookDeveloper = developer_; + _specificationURI = specificationURI_; + _specificationHash = specificationHash_; + + // Developer is automatically an authorized verifier + _authorizedVerifiers[developer_] = true; + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS IMPLEMENTATION (Required by Uniswap V4) + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc BaseHook + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) { + // Override in child contract to specify which hooks are enabled + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: false, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + // ═══════════════════════════════════════════════════════════════════════ + // DEVELOPER REVENUE MANAGEMENT + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc ICoFHEHook + function withdrawRevenue( + address token, + uint256 amount, + address recipient + ) external override onlyDeveloper { + require(recipient != address(0), "CoFHEHook: zero recipient"); + require(_revenueBalances[token] >= amount, "CoFHEHook: insufficient balance"); + + _revenueBalances[token] -= amount; + + if (token == address(0)) { + // ETH withdrawal + (bool success, ) = recipient.call{value: amount}(""); + require(success, "CoFHEHook: ETH transfer failed"); + } else { + // ERC20 withdrawal + (bool success, bytes memory data) = token.call( + abi.encodeWithSignature("transfer(address,uint256)", recipient, amount) + ); + require(success && (data.length == 0 || abi.decode(data, (bool))), "CoFHEHook: token transfer failed"); + } + + emit RevenueWithdrawn(recipient, token, amount); + } + + /// @inheritdoc ICoFHEHook + function pendingRevenue(address token) external view override returns (uint256 balance) { + return _revenueBalances[token]; + } + + /// @inheritdoc ICoFHEHook + function hookDeveloper() external view override returns (address developer) { + return _hookDeveloper; + } + + /// @dev Internal function to accrue revenue + function _accrueRevenue(address token, uint256 amount) internal { + _revenueBalances[token] += amount; + } + + // ═══════════════════════════════════════════════════════════════════════ + // STATE VIEW (IHookStateView COMPATIBLE) + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc ICoFHEHook + function getHookState(PoolId poolId) external view override onlyAuthorizedVerifier returns (bytes memory hookState) { + // Decrypt and return hook state for authorized verifiers + // This enables AVS verification without exposing source code + + (uint32 baseFee, uint32 maxFee, uint32 volatilityFactor) = _decryptFeeConfig(poolId); + + // Encode decrypted state for verifier + hookState = abi.encode( + baseFee, + maxFee, + volatilityFactor, + _getAdditionalState(poolId) // Hook-specific state + ); + } + + /// @inheritdoc ICoFHEHook + function getEncryptedHookState(PoolId poolId) external view override returns (bytes memory encryptedState) { + // Return encrypted handles (not plaintext) for public queries + EncryptedFeeConfig storage config = _encryptedFees[poolId]; + + // Encode handles (not values) - anyone can see encrypted references + encryptedState = abi.encode( + euint32.unwrap(config.baseFee), + euint32.unwrap(config.maxFee), + euint32.unwrap(config.volatilityFactor) + ); + } + + /// @dev Override to provide additional hook-specific state + function _getAdditionalState(PoolId poolId) internal view virtual returns (bytes memory) { + return ""; + } + + // ═══════════════════════════════════════════════════════════════════════ + // VERIFIER ACCESS CONTROL + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc ICoFHEHook + function isAuthorizedVerifier(address verifier) external view override returns (bool authorized) { + return _authorizedVerifiers[verifier]; + } + + /// @inheritdoc ICoFHEHook + function setVerifierAuthorization(address verifier, bool authorized) external override onlyDeveloper { + require(verifier != address(0), "CoFHEHook: zero verifier"); + _authorizedVerifiers[verifier] = authorized; + emit VerifierUpdated(verifier, authorized); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SPECIFICATION METADATA + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc ICoFHEHook + function specificationURI() external view override returns (string memory uri) { + return _specificationURI; + } + + /// @inheritdoc ICoFHEHook + function specificationHash() external view override returns (bytes32 hash) { + return _specificationHash; + } + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED COMPUTATION HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Compute dynamic fee using encrypted arithmetic + /// @dev Fee computation happens on encrypted values + /// @param poolId The pool identifier + /// @param volatility Current volatility metric + /// @return encryptedFee The computed fee (encrypted) + function _computeDynamicFee( + PoolId poolId, + uint32 volatility + ) internal view returns (euint32 encryptedFee) { + EncryptedFeeConfig storage config = _encryptedFees[poolId]; + + // Encrypt the volatility input + euint32 encryptedVolatility = FHE.asEuint32(volatility); + + // Encrypted computation: fee = baseFee + (volatility * volatilityFactor / 10000) + euint32 adjustment = FHE.mul(encryptedVolatility, config.volatilityFactor); + adjustment = FHE.div(adjustment, FHE.asEuint32(10000)); + + encryptedFee = FHE.add(config.baseFee, adjustment); + + // Cap at maxFee using encrypted comparison + ebool exceedsMax = FHE.gt(encryptedFee, config.maxFee); + encryptedFee = FHE.select(exceedsMax, config.maxFee, encryptedFee); + } + + /// @notice Safely decrypt a value for return to PoolManager + /// @dev Only decrypts when necessary for external interfaces + function _decryptForReturn(euint32 encrypted) internal view returns (uint32) { + // Grant permission to decrypt + FHE.allowThis(encrypted); + return FHE.decrypt(encrypted); + } + + // ═══════════════════════════════════════════════════════════════════════ + // RECEIVE ETH + // ═══════════════════════════════════════════════════════════════════════ + + receive() external payable { + // Accept ETH for revenue collection + _accrueRevenue(address(0), msg.value); + } +} +``` + +--- + +## 4. Example Implementation: Dynamic Fee Hook + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {CoFHEHookTemplate} from "./CoFHEHookTemplate.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FHE, euint32} from "@fhenix/fhenix-contracts/contracts/FHE.sol"; + +/// @title CoFHEDynamicFeeHook +/// @notice Example CoFHE-obfuscated dynamic fee hook +/// @dev Fee computation logic is encrypted, only results are revealed +contract CoFHEDynamicFeeHook is CoFHEHookTemplate { + using PoolIdLibrary for PoolKey; + + // ═══════════════════════════════════════════════════════════════════════ + // ADDITIONAL ENCRYPTED STATE + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev Pool ID => Last price for volatility calculation + mapping(PoolId => uint160) private _lastSqrtPrice; + + /// @dev Pool ID => Cumulative volatility (encrypted) + mapping(PoolId => euint256) private _encryptedCumulativeVolatility; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor( + IPoolManager poolManager_, + address developer_, + string memory specificationURI_, + bytes32 specificationHash_ + ) CoFHEHookTemplate(poolManager_, developer_, specificationURI_, specificationHash_) {} + + // ═══════════════════════════════════════════════════════════════════════ + // HOOK PERMISSIONS + // ═══════════════════════════════════════════════════════════════════════ + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: true, // Initialize encrypted fee config + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, // Compute dynamic fee + afterSwap: true, // Update metrics and revenue + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + // ═══════════════════════════════════════════════════════════════════════ + // HOOK CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + function beforeInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96 + ) external override poolManagerOnly returns (bytes4) { + PoolId poolId = key.toId(); + + // Initialize encrypted fee configuration + // Default: 0.3% base, 1% max, 100 volatility factor + _initializeEncryptedFees(poolId, 3000, 10000, 100); + + // Initialize last price for volatility tracking + _lastSqrtPrice[poolId] = sqrtPriceX96; + + return this.beforeInitialize.selector; + } + + function beforeSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + bytes calldata hookData + ) external override poolManagerOnly returns (bytes4, BeforeSwapDelta, uint24) { + PoolId poolId = key.toId(); + + // Calculate volatility from price change + uint160 currentPrice = _getCurrentSqrtPrice(poolId); + uint160 lastPrice = _lastSqrtPrice[poolId]; + + uint32 volatility = _calculateVolatility(lastPrice, currentPrice); + + // Compute fee using encrypted arithmetic + // The fee computation logic is hidden from observers + euint32 encryptedFee = _computeDynamicFee(poolId, volatility); + + // Decrypt only the final result for PoolManager + uint24 lpFeeOverride = uint24(_decryptForReturn(encryptedFee)); + + // Update last price + _lastSqrtPrice[poolId] = currentPrice; + + // Return fee override with override flag set (bit 23) + return ( + this.beforeSwap.selector, + BeforeSwapDeltaLibrary.ZERO_DELTA, + lpFeeOverride | 0x400000 // Set override flag + ); + } + + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external override poolManagerOnly returns (bytes4, int128) { + PoolId poolId = key.toId(); + + // Calculate and accrue revenue (portion of fees) + // Revenue calculation happens on plaintext for simplicity + // Could be encrypted if needed + + uint256 swapAmount = params.amountSpecified > 0 + ? uint256(params.amountSpecified) + : uint256(-params.amountSpecified); + + // Hook takes 10% of the fee as revenue + uint256 hookRevenue = (swapAmount * 3000 / 1000000) / 10; // ~0.03% + + // Accrue to the appropriate token + address revenueToken = params.zeroForOne + ? Currency.unwrap(key.currency0) + : Currency.unwrap(key.currency1); + + _accrueRevenue(revenueToken, hookRevenue); + + return (this.afterSwap.selector, 0); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INTERNAL HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + function _getCurrentSqrtPrice(PoolId poolId) internal view returns (uint160) { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + return sqrtPriceX96; + } + + function _calculateVolatility( + uint160 lastPrice, + uint160 currentPrice + ) internal pure returns (uint32) { + if (lastPrice == 0) return 0; + + // Calculate percentage change in basis points + uint256 priceDiff = currentPrice > lastPrice + ? currentPrice - lastPrice + : lastPrice - currentPrice; + + uint256 volatilityBps = (priceDiff * 10000) / lastPrice; + + // Cap at max uint32 + return volatilityBps > type(uint32).max + ? type(uint32).max + : uint32(volatilityBps); + } + + /// @inheritdoc CoFHEHookTemplate + function _getAdditionalState(PoolId poolId) internal view override returns (bytes memory) { + return abi.encode( + _lastSqrtPrice[poolId], + euint256.unwrap(_encryptedCumulativeVolatility[poolId]) + ); + } +} +``` + +--- + +## 5. Revenue Management API + +### 5.1 Revenue Flow Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ HOOK REVENUE FLOW │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Swap Tx │─────▶│ PoolManager │─────▶│ Hook.after │ │ +│ │ (User) │ │ │ │ Swap() │ │ +│ └─────────────┘ └─────────────┘ └──────┬──────┘ │ +│ │ │ +│ Fee calculation │ │ +│ (encrypted) │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ Hook Revenue │ │ +│ │ Accumulator │ │ +│ │ (per token) │ │ +│ └──────┬───────┘ │ +│ │ │ +│ ┌───────────────────────────┼───────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌────────────────┐ ┌────────────────┐ ┌───────────┐│ +│ │ pendingRevenue │ │withdrawRevenue │ │ Vault ││ +│ │ (view) │ │ (action) │────▶│(optional) ││ +│ └────────────────┘ └────────────────┘ └───────────┘│ +│ │ │ │ +│ │ Developer │ │ +│ └───────────Dashboard───────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 5.2 Revenue Manager Interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @title IHookRevenueManager +/// @notice Extended revenue management interface for hook developers +interface IHookRevenueManager { + + // ═══════════════════════════════════════════════════════════════════════ + // EVENTS + // ═══════════════════════════════════════════════════════════════════════ + + event RevenueAccrued( + address indexed token, + uint256 amount, + PoolId indexed poolId + ); + + event VaultConfigured( + address indexed vault, + address indexed token, + uint256 autoWithdrawThreshold + ); + + event RevenueShareUpdated( + address indexed recipient, + uint256 shareBps + ); + + // ═══════════════════════════════════════════════════════════════════════ + // REVENUE QUERIES + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Get total revenue across all tokens + /// @return tokens Array of token addresses + /// @return amounts Array of pending amounts + function getAllPendingRevenue() external view returns ( + address[] memory tokens, + uint256[] memory amounts + ); + + /// @notice Get revenue breakdown by pool + /// @param token Token to query + /// @return poolIds Pools generating revenue + /// @return amounts Revenue per pool + function getRevenueByPool(address token) external view returns ( + PoolId[] memory poolIds, + uint256[] memory amounts + ); + + /// @notice Get historical revenue data + /// @param token Token to query + /// @param fromBlock Starting block + /// @param toBlock Ending block + /// @return totalRevenue Total revenue in period + /// @return withdrawals Total withdrawals in period + function getRevenueHistory( + address token, + uint256 fromBlock, + uint256 toBlock + ) external view returns ( + uint256 totalRevenue, + uint256 withdrawals + ); + + // ═══════════════════════════════════════════════════════════════════════ + // REVENUE ACTIONS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Batch withdraw multiple tokens + /// @param tokens Array of token addresses + /// @param amounts Array of amounts to withdraw + /// @param recipient Recipient address + function batchWithdrawRevenue( + address[] calldata tokens, + uint256[] calldata amounts, + address recipient + ) external; + + /// @notice Withdraw all pending revenue for a token + /// @param token Token to withdraw + /// @param recipient Recipient address + /// @return amount Amount withdrawn + function withdrawAllRevenue( + address token, + address recipient + ) external returns (uint256 amount); + + // ═══════════════════════════════════════════════════════════════════════ + // VAULT INTEGRATION + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Configure automatic revenue forwarding to vault + /// @param vault Vault address to forward revenue to + /// @param token Token to configure + /// @param autoWithdrawThreshold Minimum balance to trigger auto-withdraw + function configureVault( + address vault, + address token, + uint256 autoWithdrawThreshold + ) external; + + /// @notice Get vault configuration + /// @param token Token to query + /// @return vault Configured vault address + /// @return threshold Auto-withdraw threshold + function getVaultConfig(address token) external view returns ( + address vault, + uint256 threshold + ); + + // ═══════════════════════════════════════════════════════════════════════ + // REVENUE SHARING + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Configure revenue sharing with other addresses + /// @param recipient Address to share revenue with + /// @param shareBps Share percentage in basis points (max 10000) + function setRevenueShare( + address recipient, + uint256 shareBps + ) external; + + /// @notice Get revenue share configuration + /// @return recipients Array of share recipients + /// @return sharesBps Array of share percentages + function getRevenueShares() external view returns ( + address[] memory recipients, + uint256[] memory sharesBps + ); + + /// @notice Distribute revenue according to shares + /// @param token Token to distribute + /// @return distributed Total amount distributed + function distributeRevenue(address token) external returns (uint256 distributed); +} +``` + +--- + +## 6. AVS Verification Integration + +### 6.1 Verification Without Code Disclosure + +The CoFHE template enables behavioral verification through: + +1. **Public Specification**: Mathematical behavior defined in IPFS-stored specification +2. **Encrypted Implementation**: Source code protected via FHE +3. **Authorized Decryption**: AVS operators granted decrypt permission via `setVerifierAuthorization` +4. **State Sampling**: Operators call `getHookState()` to sample decrypted state +5. **Behavioral Verification**: Compare actual behavior to specification + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ VERIFICATION FLOW │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. OPERATOR REGISTRATION │ +│ ──────────────────────── │ +│ Developer calls: setVerifierAuthorization(operatorAddress, true) │ +│ │ +│ 2. STATE SAMPLING │ +│ ───────────────── │ +│ Operator calls: getHookState(poolId) │ +│ → Returns DECRYPTED state (authorized access) │ +│ │ +│ 3. CALLBACK EXECUTION │ +│ ──────────────────── │ +│ Operator simulates: beforeSwap(params) │ +│ → Captures pre-state and post-state │ +│ │ +│ 4. SPECIFICATION COMPARISON │ +│ ────────────────────────── │ +│ Operator verifies: │ +│ - State transitions match specification equations │ +│ - Invariants hold │ +│ - Return values within tolerance │ +│ │ +│ 5. ATTESTATION SUBMISSION │ +│ ──────────────────────── │ +│ Operator signs: AttestationResponse { specCompliant: true/false } │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 6.2 IHookStateView Compatibility + +The template implements the state sampling interface required by the AVS: + +```solidity +/// @title IHookStateView Compatibility Layer +/// @notice Maps CoFHE template to AVS verification requirements +interface IHookStateViewCompatible { + + /// @notice Get trader-relevant state (slot0 + liquidity) + /// @param poolId Pool identifier + /// @return state TraderState struct + function getTraderState(PoolId poolId) external view returns (TraderState memory state); + + /// @notice Get LP position state + /// @param poolId Pool identifier + /// @param positionId Position identifier + /// @return state LPPositionState struct + function getLPPositionState( + PoolId poolId, + bytes32 positionId + ) external view returns (LPPositionState memory state); + + /// @notice Get hook-specific state (CoFHE encrypted) + /// @dev Decrypts for authorized callers + /// @param poolId Pool identifier + /// @return hookState Encoded hook state + function getHookState(PoolId poolId) external view returns (bytes memory hookState); +} +``` + +--- + +## 7. Deployment Guide + +### 7.1 Prerequisites + +1. **Fhenix CoFHE Setup** + ```bash + # Clone CoFHE starter + git clone https://github.com/FhenixProtocol/cofhe-hardhat-starter + cd cofhe-hardhat-starter + pnpm install + + # Install CoFHE contracts + pnpm add @fhenix/fhenix-contracts + ``` + +2. **Environment Configuration** + ```bash + # .env file + PRIVATE_KEY=your_private_key + RPC_URL=https://rpc.fhenix.zone + ETHERSCAN_API_KEY=your_key + IPFS_GATEWAY=https://gateway.pinata.cloud + ``` + +### 7.2 Deployment Steps + +```typescript +// deploy/deploy-cofhe-hook.ts +import { ethers } from "hardhat"; +import { uploadToIPFS } from "./utils/ipfs"; + +async function main() { + const [deployer] = await ethers.getSigners(); + console.log("Deploying with:", deployer.address); + + // 1. Upload specification to IPFS + const specification = { + name: "CoFHE Dynamic Fee Hook", + version: "1.0.0", + callbacks: ["beforeInitialize", "beforeSwap", "afterSwap"], + stateVariables: { + baseFee: { type: "euint32", description: "Base fee in bps" }, + maxFee: { type: "euint32", description: "Maximum fee cap" }, + volatilityFactor: { type: "euint32", description: "Volatility sensitivity" } + }, + invariants: [ + "baseFee <= maxFee", + "computedFee <= maxFee" + ], + testVectors: [ + { input: { volatility: 0 }, expected: { fee: "baseFee" } }, + { input: { volatility: 10000 }, expected: { fee: "maxFee" } } + ] + }; + + const specificationURI = await uploadToIPFS(specification); + const specificationHash = ethers.keccak256( + ethers.toUtf8Bytes(JSON.stringify(specification)) + ); + + console.log("Specification uploaded:", specificationURI); + + // 2. Get PoolManager address + const poolManagerAddress = "0x..."; // Network-specific + + // 3. Deploy the hook + const CoFHEHook = await ethers.getContractFactory("CoFHEDynamicFeeHook"); + const hook = await CoFHEHook.deploy( + poolManagerAddress, + deployer.address, + specificationURI, + specificationHash + ); + + await hook.waitForDeployment(); + console.log("Hook deployed to:", await hook.getAddress()); + + // 4. Verify on explorer (optional) + await hre.run("verify:verify", { + address: await hook.getAddress(), + constructorArguments: [ + poolManagerAddress, + deployer.address, + specificationURI, + specificationHash + ] + }); + + // 5. Register AVS verifiers + const avsOperatorAddress = "0x..."; + await hook.setVerifierAuthorization(avsOperatorAddress, true); + console.log("AVS operator authorized"); +} + +main().catch(console.error); +``` + +### 7.3 Testing + +```typescript +// test/CoFHEDynamicFeeHook.test.ts +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { cofhejs_initializeWithHardhatSigner, cofhejs } from "@fhenix/cofhejs"; +import { Encryptable } from "@fhenix/cofhejs"; + +describe("CoFHEDynamicFeeHook", function() { + let hook: Contract; + let poolManager: Contract; + let developer: Signer; + let verifier: Signer; + let user: Signer; + + beforeEach(async function() { + [developer, verifier, user] = await ethers.getSigners(); + + // Initialize CoFHE for testing + await cofhejs_initializeWithHardhatSigner(developer); + + // Deploy mock PoolManager + const MockPoolManager = await ethers.getContractFactory("MockPoolManager"); + poolManager = await MockPoolManager.deploy(); + + // Deploy hook + const CoFHEHook = await ethers.getContractFactory("CoFHEDynamicFeeHook"); + hook = await CoFHEHook.deploy( + await poolManager.getAddress(), + developer.address, + "ipfs://QmTest", + ethers.keccak256(ethers.toUtf8Bytes("test")) + ); + }); + + describe("Encrypted State", function() { + it("should initialize encrypted fee configuration", async function() { + // Call beforeInitialize + await hook.beforeInitialize( + user.address, + mockPoolKey, + BigInt(1e18) // sqrtPriceX96 + ); + + // Try to read encrypted state without authorization + await expect( + hook.connect(user).getHookState(poolId) + ).to.be.revertedWith("CoFHEHook: not authorized verifier"); + }); + + it("should allow authorized verifier to decrypt state", async function() { + // Initialize + await hook.beforeInitialize(user.address, mockPoolKey, BigInt(1e18)); + + // Authorize verifier + await hook.connect(developer).setVerifierAuthorization(verifier.address, true); + + // Verifier can read decrypted state + const state = await hook.connect(verifier).getHookState(poolId); + const [baseFee, maxFee, volatilityFactor] = ethers.AbiCoder.defaultAbiCoder().decode( + ["uint32", "uint32", "uint32", "bytes"], + state + ); + + expect(baseFee).to.equal(3000); // Default 0.3% + expect(maxFee).to.equal(10000); // Default 1% + }); + }); + + describe("Revenue Management", function() { + it("should accrue and withdraw revenue", async function() { + // Simulate swap that generates revenue + await simulateSwap(hook, mockPoolKey); + + // Check pending revenue + const pending = await hook.pendingRevenue(tokenAddress); + expect(pending).to.be.gt(0); + + // Withdraw + const balanceBefore = await token.balanceOf(developer.address); + await hook.connect(developer).withdrawRevenue( + tokenAddress, + pending, + developer.address + ); + const balanceAfter = await token.balanceOf(developer.address); + + expect(balanceAfter - balanceBefore).to.equal(pending); + }); + + it("should only allow developer to withdraw", async function() { + await expect( + hook.connect(user).withdrawRevenue(tokenAddress, 100, user.address) + ).to.be.revertedWith("CoFHEHook: not developer"); + }); + }); +}); +``` + +--- + +## 8. Security Considerations + +### 8.1 Access Control Matrix + +| Function | Developer | Verifier | Public | +|----------|:---------:|:--------:|:------:| +| `getHookState()` (decrypted) | Yes | Yes | No | +| `getEncryptedHookState()` | Yes | Yes | Yes | +| `withdrawRevenue()` | Yes | No | No | +| `setVerifierAuthorization()` | Yes | No | No | +| `specificationURI()` | Yes | Yes | Yes | +| Hook callbacks | PoolManager Only | No | No | + +### 8.2 Trust Assumptions + +1. **Fhenix Network**: FHE operations are secure and correctly implemented +2. **Developer Honesty**: Developer correctly implements specification +3. **Verifier Independence**: AVS operators are economically incentivized to verify correctly +4. **Specification Accuracy**: Public specification accurately describes intended behavior + +### 8.3 Attack Vectors & Mitigations + +| Attack | Description | Mitigation | +|--------|-------------|------------| +| **Unauthorized Decryption** | Attacker tries to decrypt state | Access control + FHE.allow() | +| **Revenue Theft** | Attacker tries to withdraw revenue | onlyDeveloper modifier | +| **Specification Gaming** | Developer writes misleading spec | Community review, slashing | +| **Verifier Collusion** | Verifiers collude to false attest | Minimum operator count, stake distribution | + +--- + +## 9. References + +1. **[Fhenix CoFHE]** Fhenix Protocol. *CoFHE Documentation*. https://cofhe-docs.fhenix.zone/ +2. **[Fhenix Contracts]** Fhenix Protocol. *fhenix-contracts*. https://github.com/FhenixProtocol/fhenix-contracts +3. **[IHooks]** Uniswap. *v4-core IHooks Interface*. https://github.com/Uniswap/v4-core +4. **[State-Space Model]** Hook Bazaar. *Hook State-Space Model*. `docs/hook-pkg/mathematical-models/state-space-model.md` +5. **[AVS Verification]** Hook Bazaar. *AVS Verification System*. `docs/hook-pkg/architecture/avs-verification-system.md` + +--- + +## 10. Appendix: Quick Reference + +### 10.1 CoFHE Types + +| Type | Bits | Use Case | +|------|------|----------| +| `ebool` | 1 | Flags, conditions | +| `euint8` | 8 | Small counters | +| `euint16` | 16 | Tick values | +| `euint32` | 32 | Fees, timestamps | +| `euint64` | 64 | Amounts | +| `euint128` | 128 | Liquidity | +| `euint256` | 256 | Large amounts | +| `eaddress` | 160 | Encrypted addresses | + +### 10.2 FHE Operations + +```solidity +// Arithmetic +FHE.add(a, b) // a + b +FHE.sub(a, b) // a - b +FHE.mul(a, b) // a * b +FHE.div(a, b) // a / b + +// Comparison (returns ebool) +FHE.eq(a, b) // a == b +FHE.ne(a, b) // a != b +FHE.lt(a, b) // a < b +FHE.lte(a, b) // a <= b +FHE.gt(a, b) // a > b +FHE.gte(a, b) // a >= b + +// Control Flow +FHE.select(cond, a, b) // cond ? a : b + +// Access Control +FHE.allowThis(val) // Allow current contract +FHE.allowSender(val) // Allow msg.sender +FHE.allow(val, addr) // Allow specific address + +// Conversion +FHE.asEuint32(plaintext) // Encrypt +FHE.decrypt(encrypted) // Decrypt (requires permission) +``` + +### 10.3 Template Checklist + +- [ ] Implement `IHooks` interface +- [ ] Implement `ICoFHEHook` interface +- [ ] Store specification on IPFS +- [ ] Initialize encrypted state in `beforeInitialize` +- [ ] Implement revenue accrual in callbacks +- [ ] Grant FHE permissions with `allowThis()` +- [ ] Implement `getHookState()` for verifiers +- [ ] Test with CoFHE mock contracts +- [ ] Deploy to Fhenix testnet +- [ ] Register AVS verifiers diff --git a/docs/hook-pkg/integration-guides/create-hook-flow.md b/docs/hook-pkg/integration-guides/create-hook-flow.md new file mode 100644 index 000000000..92ca029ce --- /dev/null +++ b/docs/hook-pkg/integration-guides/create-hook-flow.md @@ -0,0 +1,1165 @@ +# Create Hook Flow: End-to-End Development Guide + +> **Status:** Architecture Design +> **Last Updated:** 2025-12-10 +> **Prerequisites:** [CoFHE Hook Template](./cofhe-hook-template.md), [AVS Verification System](../architecture/avs-verification-system.md) +> **References:** [IHooks Interface](https://github.com/Uniswap/v4-core), [State-Space Model](../mathematical-models/state-space-model.md) + +--- + +## 1. Overview + +This document provides a complete end-to-end guide for hook developers to create, deploy, and verify hooks on the Hook Bazaar marketplace. The flow ensures: + +1. **IHooks Compliance**: Full compatibility with Uniswap V4 PoolManager +2. **IHookStateView Compliance**: AVS operators can verify behavior +3. **Code Obfuscation**: Bytecode protected via Fhenix CoFHE +4. **Revenue Management**: Developer APIs for revenue collection +5. **Marketplace Listing**: Verified hooks can be discovered and used + +--- + +## 2. Complete Development Flow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ HOOK DEVELOPMENT LIFECYCLE │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ PHASE 1: SPECIFICATION │ │ +│ │ ───────────────────────── │ │ +│ │ 1.1 Define State Variables (H) │ │ +│ │ 1.2 Define State Transitions f_i(H, P) → (H', Δ) │ │ +│ │ 1.3 Define Invariants │ │ +│ │ 1.4 Create Test Vectors │ │ +│ │ 1.5 Upload Specification to IPFS │ │ +│ └────────────────────────────────────┬────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ PHASE 2: IMPLEMENTATION │ │ +│ │ ────────────────────────── │ │ +│ │ 2.1 Extend CoFHEHookTemplate │ │ +│ │ 2.2 Implement IHooks callbacks │ │ +│ │ 2.3 Add encrypted state using FHE.sol │ │ +│ │ 2.4 Implement IHookStateView getters │ │ +│ │ 2.5 Implement revenue accrual logic │ │ +│ └────────────────────────────────────┬────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ PHASE 3: TESTING │ │ +│ │ ───────────────── │ │ +│ │ 3.1 Unit tests with CoFHE mock contracts │ │ +│ │ 3.2 Integration tests with mock PoolManager │ │ +│ │ 3.3 Verify state transitions match specification │ │ +│ │ 3.4 Test revenue accrual and withdrawal │ │ +│ │ 3.5 Test verifier access control │ │ +│ └────────────────────────────────────┬────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ PHASE 4: DEPLOYMENT │ │ +│ │ ─────────────────── │ │ +│ │ 4.1 Deploy to Fhenix testnet │ │ +│ │ 4.2 Verify contract on explorer │ │ +│ │ 4.3 Register AVS verifier operators │ │ +│ │ 4.4 Deploy to mainnet │ │ +│ └────────────────────────────────────┬────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ PHASE 5: ATTESTATION │ │ +│ │ ──────────────────── │ │ +│ │ 5.1 Request attestation via HookAttestationTaskManager │ │ +│ │ 5.2 AVS operators verify behavior against specification │ │ +│ │ 5.3 Receive attestation certificate │ │ +│ │ 5.4 Attestation recorded in AttestationRegistry │ │ +│ └────────────────────────────────────┬────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ PHASE 6: MARKETPLACE LISTING │ │ +│ │ ──────────────────────────── │ │ +│ │ 6.1 List hook in HookMarket │ │ +│ │ 6.2 Set pricing and terms │ │ +│ │ 6.3 Protocols discover and integrate │ │ +│ │ 6.4 Collect revenue via withdrawRevenue() │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. Phase 1: Specification + +### 3.1 Specification Document Structure + +Create a formal specification document following this template: + +```markdown +# Hook Specification: [HookName] v[Version] + +## 1. Hook Identity +- **Name:** [HookName] +- **Version:** [SemVer] +- **Author:** [Developer Address] +- **Hook Address:** [To be filled after deployment] +- **Specification Hash:** [Keccak256 of this document] + +## 2. Callbacks Implemented + +| Callback | Enabled | Description | +|----------|:-------:|-------------| +| beforeInitialize | [Yes/No] | [Description] | +| afterInitialize | [Yes/No] | [Description] | +| beforeAddLiquidity | [Yes/No] | [Description] | +| afterAddLiquidity | [Yes/No] | [Description] | +| beforeRemoveLiquidity | [Yes/No] | [Description] | +| afterRemoveLiquidity | [Yes/No] | [Description] | +| beforeSwap | [Yes/No] | [Description] | +| afterSwap | [Yes/No] | [Description] | +| beforeDonate | [Yes/No] | [Description] | +| afterDonate | [Yes/No] | [Description] | + +## 3. State Variables + +### 3.1 Hook State (H) +| Variable | Type | Encrypted | Description | +|----------|------|:---------:|-------------| +| [varName] | [type] | [Yes/No] | [Description] | + +### 3.2 Pool State Dependencies (P) +- **Reads:** [List state variables read] +- **Writes:** [List state variables written] + +## 4. State Transition Functions + +### 4.1 [CallbackName](H, P) → (H', Δ) + +**Preconditions:** +- [Condition 1] +- [Condition 2] + +**Transition Equations:** + +$$ +[Variable]' = f([inputs]) +$$ + +**Postconditions:** +- [Condition 1] +- [Condition 2] + +### 4.2 [NextCallback]... + +## 5. Invariants + +### INV-1: [Invariant Name] +$$ +[Mathematical Expression] +$$ +**Description:** [What this invariant ensures] + +### INV-2: [Next Invariant]... + +## 6. Test Vectors + +| ID | Pre-State | Input | Expected Post-State | Expected Return | +|----|-----------|-------|---------------------|-----------------| +| TV-1 | { H, P } | { params } | { H', P' } | { return } | +| TV-2 | ... | ... | ... | ... | + +## 7. Gas Bounds + +| Callback | Max Gas | Typical Gas | +|----------|---------|-------------| +| [callback] | [max] | [typical] | + +## 8. Security Considerations + +- [Consideration 1] +- [Consideration 2] + +## 9. Revenue Model + +| Source | Calculation | Recipient | +|--------|-------------|-----------| +| [source] | [formula] | [address] | +``` + +### 3.2 Upload to IPFS + +```typescript +// scripts/upload-specification.ts +import { create } from 'ipfs-http-client'; +import * as fs from 'fs'; + +async function uploadSpecification() { + // Read specification + const specPath = './specification.md'; + const specContent = fs.readFileSync(specPath, 'utf8'); + + // Connect to IPFS + const ipfs = create({ url: 'https://ipfs.infura.io:5001/api/v0' }); + + // Upload + const result = await ipfs.add(specContent); + const ipfsURI = `ipfs://${result.path}`; + + console.log('Specification uploaded:', ipfsURI); + + // Compute hash for on-chain verification + const specHash = ethers.keccak256(ethers.toUtf8Bytes(specContent)); + console.log('Specification hash:', specHash); + + return { ipfsURI, specHash }; +} + +uploadSpecification(); +``` + +--- + +## 4. Phase 2: Implementation + +### 4.1 IHooks Interface Compliance + +Every hook MUST implement the IHooks interface callbacks it enables: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; + +/// @title IHooks Compliance Checklist +/// @notice All hooks MUST implement enabled callbacks correctly +interface IHooksCompliance { + + // ═══════════════════════════════════════════════════════════════════════ + // INITIALIZATION CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Called before pool initialization + /// @dev MUST return this.beforeInitialize.selector + /// @param sender The address initializing the pool + /// @param key The pool configuration + /// @param sqrtPriceX96 Initial sqrt price + function beforeInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96 + ) external returns (bytes4); + + /// @notice Called after pool initialization + /// @dev MUST return this.afterInitialize.selector + function afterInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96, + int24 tick + ) external returns (bytes4); + + // ═══════════════════════════════════════════════════════════════════════ + // LIQUIDITY CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Called before adding liquidity + /// @dev MUST return this.beforeAddLiquidity.selector + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice Called after adding liquidity + /// @dev Returns selector + optional BalanceDelta for hook's token delta + function afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external returns (bytes4, BalanceDelta); + + /// @notice Called before removing liquidity + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice Called after removing liquidity + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external returns (bytes4, BalanceDelta); + + // ═══════════════════════════════════════════════════════════════════════ + // SWAP CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + /// @notice Called before swap execution + /// @dev Can modify fee via return value + /// @return selector Function selector + /// @return beforeSwapDelta Hook's delta (if enabled) + /// @return lpFeeOverride Fee override (if bit 23 set) + function beforeSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + bytes calldata hookData + ) external returns (bytes4, BeforeSwapDelta, uint24); + + /// @notice Called after swap execution + /// @return selector Function selector + /// @return hookDelta Hook's unspecified currency delta (if enabled) + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external returns (bytes4, int128); + + // ═══════════════════════════════════════════════════════════════════════ + // DONATE CALLBACKS + // ═══════════════════════════════════════════════════════════════════════ + + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external returns (bytes4); + + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external returns (bytes4); +} +``` + +### 4.2 Hook Address Requirements + +Uniswap V4 hooks MUST be deployed to specific addresses where the least significant bits encode enabled callbacks: + +```solidity +/// @title Hook Address Flags +/// @notice Bit positions for hook permissions +library HookAddressFlags { + // Permission flags (from least significant bit) + uint160 constant BEFORE_INITIALIZE_FLAG = 1 << 13; + uint160 constant AFTER_INITIALIZE_FLAG = 1 << 12; + uint160 constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 11; + uint160 constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 10; + uint160 constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 9; + uint160 constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 8; + uint160 constant BEFORE_SWAP_FLAG = 1 << 7; + uint160 constant AFTER_SWAP_FLAG = 1 << 6; + uint160 constant BEFORE_DONATE_FLAG = 1 << 5; + uint160 constant AFTER_DONATE_FLAG = 1 << 4; + uint160 constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 3; + uint160 constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2; + uint160 constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 1; + uint160 constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 0; + + /// @notice Calculate required address suffix for given permissions + function calculateAddressSuffix( + bool beforeInitialize, + bool afterInitialize, + bool beforeAddLiquidity, + bool afterAddLiquidity, + bool beforeRemoveLiquidity, + bool afterRemoveLiquidity, + bool beforeSwap, + bool afterSwap, + bool beforeDonate, + bool afterDonate + ) internal pure returns (uint160 suffix) { + if (beforeInitialize) suffix |= BEFORE_INITIALIZE_FLAG; + if (afterInitialize) suffix |= AFTER_INITIALIZE_FLAG; + if (beforeAddLiquidity) suffix |= BEFORE_ADD_LIQUIDITY_FLAG; + if (afterAddLiquidity) suffix |= AFTER_ADD_LIQUIDITY_FLAG; + if (beforeRemoveLiquidity) suffix |= BEFORE_REMOVE_LIQUIDITY_FLAG; + if (afterRemoveLiquidity) suffix |= AFTER_REMOVE_LIQUIDITY_FLAG; + if (beforeSwap) suffix |= BEFORE_SWAP_FLAG; + if (afterSwap) suffix |= AFTER_SWAP_FLAG; + if (beforeDonate) suffix |= BEFORE_DONATE_FLAG; + if (afterDonate) suffix |= AFTER_DONATE_FLAG; + } +} +``` + +### 4.3 Mining Hook Address + +Use CREATE2 to deploy hooks to addresses with correct permission flags: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @title HookDeployer +/// @notice Deploy hooks to addresses with correct permission flags +contract HookDeployer { + + event HookDeployed(address hook, bytes32 salt, uint160 flags); + + /// @notice Find a salt that produces an address with required flags + /// @param bytecodeHash Keccak256 of hook creation bytecode + /// @param requiredFlags The permission flags needed in the address + /// @return salt The salt to use with CREATE2 + /// @return hookAddress The resulting hook address + function findSalt( + bytes32 bytecodeHash, + uint160 requiredFlags + ) external view returns (bytes32 salt, address hookAddress) { + uint256 nonce = 0; + uint160 mask = (1 << 14) - 1; // 14 LSBs + + while (true) { + salt = keccak256(abi.encodePacked(msg.sender, nonce)); + hookAddress = address(uint160(uint256(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + bytecodeHash + ))))); + + // Check if address has required flags + if ((uint160(hookAddress) & mask) == requiredFlags) { + return (salt, hookAddress); + } + + nonce++; + require(nonce < 1000000, "Salt not found"); + } + } + + /// @notice Deploy hook using CREATE2 + /// @param salt Salt for CREATE2 + /// @param bytecode Hook creation bytecode + /// @return hook Deployed hook address + function deploy( + bytes32 salt, + bytes calldata bytecode + ) external returns (address hook) { + assembly { + hook := create2(0, add(bytecode, 0x20), mload(bytecode), salt) + } + require(hook != address(0), "Deployment failed"); + + emit HookDeployed(hook, salt, uint160(hook) & ((1 << 14) - 1)); + } +} +``` + +### 4.4 Complete Implementation Example + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {CoFHEHookTemplate} from "./CoFHEHookTemplate.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {FHE, euint32, euint256, ebool} from "@fhenix/fhenix-contracts/contracts/FHE.sol"; + +/// @title MyCoFHEHook +/// @notice Example implementation following create-hook flow +/// @dev Implements dynamic fee based on volatility with encrypted parameters +contract MyCoFHEHook is CoFHEHookTemplate { + using PoolIdLibrary for PoolKey; + + // ═══════════════════════════════════════════════════════════════════════ + // ENCRYPTED STATE (Step 2.3) + // ═══════════════════════════════════════════════════════════════════════ + + /// @dev Pool ID => Last sqrt price + mapping(PoolId => uint160) private _lastPrice; + + /// @dev Pool ID => Cumulative volume (encrypted) + mapping(PoolId => euint256) private _encryptedVolume; + + /// @dev Pool ID => Fee tier parameters (encrypted) + struct EncryptedFeeTiers { + euint32 lowVolatilityFee; // Fee when volatility < threshold1 + euint32 midVolatilityFee; // Fee when threshold1 <= volatility < threshold2 + euint32 highVolatilityFee; // Fee when volatility >= threshold2 + euint32 threshold1; // Low/mid boundary + euint32 threshold2; // Mid/high boundary + } + mapping(PoolId => EncryptedFeeTiers) private _feeTiers; + + // ═══════════════════════════════════════════════════════════════════════ + // CONSTRUCTOR + // ═══════════════════════════════════════════════════════════════════════ + + constructor( + IPoolManager poolManager_, + address developer_, + string memory specificationURI_, + bytes32 specificationHash_ + ) CoFHEHookTemplate( + poolManager_, + developer_, + specificationURI_, + specificationHash_ + ) {} + + // ═══════════════════════════════════════════════════════════════════════ + // HOOK PERMISSIONS (Step 2.2) + // ═══════════════════════════════════════════════════════════════════════ + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: true, // Initialize fee tiers + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, // Compute dynamic fee + afterSwap: true, // Update volume metrics & revenue + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKS CALLBACKS (Step 2.2) + // ═══════════════════════════════════════════════════════════════════════ + + function beforeInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96 + ) external override poolManagerOnly returns (bytes4) { + PoolId poolId = key.toId(); + + // Initialize encrypted fee tiers + _feeTiers[poolId] = EncryptedFeeTiers({ + lowVolatilityFee: FHE.asEuint32(500), // 0.05% + midVolatilityFee: FHE.asEuint32(3000), // 0.30% + highVolatilityFee: FHE.asEuint32(10000), // 1.00% + threshold1: FHE.asEuint32(100), // 1% volatility + threshold2: FHE.asEuint32(500) // 5% volatility + }); + + // Grant contract permission to operate on encrypted values + FHE.allowThis(_feeTiers[poolId].lowVolatilityFee); + FHE.allowThis(_feeTiers[poolId].midVolatilityFee); + FHE.allowThis(_feeTiers[poolId].highVolatilityFee); + FHE.allowThis(_feeTiers[poolId].threshold1); + FHE.allowThis(_feeTiers[poolId].threshold2); + + // Initialize last price + _lastPrice[poolId] = sqrtPriceX96; + + // Initialize encrypted volume + _encryptedVolume[poolId] = FHE.asEuint256(0); + FHE.allowThis(_encryptedVolume[poolId]); + + return this.beforeInitialize.selector; + } + + function beforeSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + bytes calldata hookData + ) external override poolManagerOnly returns (bytes4, BeforeSwapDelta, uint24) { + PoolId poolId = key.toId(); + + // Calculate volatility + uint160 currentPrice = _getCurrentPrice(poolId); + uint160 lastPrice = _lastPrice[poolId]; + uint32 volatility = _calculateVolatility(lastPrice, currentPrice); + + // Select fee tier based on volatility using encrypted comparison + euint32 encryptedVolatility = FHE.asEuint32(volatility); + EncryptedFeeTiers storage tiers = _feeTiers[poolId]; + + // Encrypted tier selection + ebool isLow = FHE.lt(encryptedVolatility, tiers.threshold1); + ebool isMid = FHE.and( + FHE.gte(encryptedVolatility, tiers.threshold1), + FHE.lt(encryptedVolatility, tiers.threshold2) + ); + + // Select fee: low if isLow, mid if isMid, else high + euint32 selectedFee = FHE.select( + isLow, + tiers.lowVolatilityFee, + FHE.select(isMid, tiers.midVolatilityFee, tiers.highVolatilityFee) + ); + + // Decrypt for PoolManager return + uint24 lpFeeOverride = uint24(FHE.decrypt(selectedFee)); + + // Update last price + _lastPrice[poolId] = currentPrice; + + return ( + this.beforeSwap.selector, + BeforeSwapDeltaLibrary.ZERO_DELTA, + lpFeeOverride | 0x400000 // Set override flag + ); + } + + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external override poolManagerOnly returns (bytes4, int128) { + PoolId poolId = key.toId(); + + // Calculate swap amount + uint256 swapAmount = params.amountSpecified > 0 + ? uint256(params.amountSpecified) + : uint256(-params.amountSpecified); + + // Update encrypted volume + euint256 volumeDelta = FHE.asEuint256(swapAmount); + _encryptedVolume[poolId] = FHE.add(_encryptedVolume[poolId], volumeDelta); + FHE.allowThis(_encryptedVolume[poolId]); + + // Calculate and accrue revenue (Step 2.5) + // Hook takes 5% of the fee charged + uint256 hookRevenue = (swapAmount * 3000 / 1000000) * 5 / 100; + + address revenueToken = params.zeroForOne + ? Currency.unwrap(key.currency0) + : Currency.unwrap(key.currency1); + + _accrueRevenue(revenueToken, hookRevenue); + + return (this.afterSwap.selector, 0); + } + + // ═══════════════════════════════════════════════════════════════════════ + // IHOOKSTATEVIEW GETTERS (Step 2.4) + // ═══════════════════════════════════════════════════════════════════════ + + /// @inheritdoc CoFHEHookTemplate + function _getAdditionalState(PoolId poolId) internal view override returns (bytes memory) { + // Return decrypted state for authorized verifiers + EncryptedFeeTiers storage tiers = _feeTiers[poolId]; + + return abi.encode( + _lastPrice[poolId], + FHE.decrypt(tiers.lowVolatilityFee), + FHE.decrypt(tiers.midVolatilityFee), + FHE.decrypt(tiers.highVolatilityFee), + FHE.decrypt(tiers.threshold1), + FHE.decrypt(tiers.threshold2), + FHE.decrypt(_encryptedVolume[poolId]) + ); + } + + // ═══════════════════════════════════════════════════════════════════════ + // INTERNAL HELPERS + // ═══════════════════════════════════════════════════════════════════════ + + function _getCurrentPrice(PoolId poolId) internal view returns (uint160) { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + return sqrtPriceX96; + } + + function _calculateVolatility( + uint160 lastPrice, + uint160 currentPrice + ) internal pure returns (uint32) { + if (lastPrice == 0) return 0; + + uint256 priceDiff = currentPrice > lastPrice + ? currentPrice - lastPrice + : lastPrice - currentPrice; + + // Return volatility in basis points (0.01% = 1) + uint256 volatilityBps = (priceDiff * 10000) / lastPrice; + return volatilityBps > type(uint32).max ? type(uint32).max : uint32(volatilityBps); + } +} +``` + +--- + +## 5. Phase 3: Testing + +### 5.1 Test Suite Structure + +``` +test/ +├── unit/ +│ ├── MyCoFHEHook.test.ts # Core functionality +│ ├── EncryptedState.test.ts # FHE operations +│ └── RevenueManager.test.ts # Revenue functions +├── integration/ +│ ├── PoolManager.test.ts # PoolManager integration +│ └── AVSVerification.test.ts # Verifier access +└── specification/ + └── Compliance.test.ts # Spec compliance tests +``` + +### 5.2 Specification Compliance Tests + +```typescript +// test/specification/Compliance.test.ts +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { loadSpecification, TestVector } from "../utils/specification"; + +describe("Specification Compliance", function() { + let hook: Contract; + let specification: any; + let testVectors: TestVector[]; + + beforeEach(async function() { + // Load specification from IPFS or local file + specification = await loadSpecification("./specification.md"); + testVectors = specification.testVectors; + + // Deploy hook + // ... deployment code + }); + + describe("Test Vectors", function() { + testVectors.forEach((tv, index) => { + it(`should pass test vector TV-${index + 1}`, async function() { + // Setup pre-state + await setupState(hook, tv.preState); + + // Execute callback + const result = await executeCallback( + hook, + tv.callback, + tv.input + ); + + // Verify post-state matches expected + const postState = await getState(hook, tv.poolId); + + for (const [key, expected] of Object.entries(tv.expectedPostState)) { + expect(postState[key]).to.be.closeTo( + expected, + tv.tolerance || 0, + `State variable ${key} mismatch` + ); + } + + // Verify return value + if (tv.expectedReturn) { + expect(result).to.deep.equal(tv.expectedReturn); + } + }); + }); + }); + + describe("Invariants", function() { + specification.invariants.forEach((inv: any) => { + it(`should maintain ${inv.name}`, async function() { + // Generate random inputs + const inputs = generateRandomInputs(100); + + for (const input of inputs) { + const preState = await getState(hook, input.poolId); + + // Execute callback + await executeCallback(hook, input.callback, input.params); + + const postState = await getState(hook, input.poolId); + + // Check invariant + const holds = evaluateInvariant(inv.expression, preState, postState); + expect(holds).to.be.true; + } + }); + }); + }); +}); +``` + +--- + +## 6. Phase 4: Deployment + +### 6.1 Deployment Script + +```typescript +// scripts/deploy.ts +import { ethers } from "hardhat"; +import { HookDeployer__factory } from "../typechain-types"; + +async function main() { + const [deployer] = await ethers.getSigners(); + console.log("Deploying with account:", deployer.address); + + // 1. Get specification details + const specificationURI = process.env.SPECIFICATION_URI!; + const specificationHash = process.env.SPECIFICATION_HASH!; + + // 2. Get PoolManager address for network + const poolManagerAddress = getPoolManagerAddress(network.name); + + // 3. Calculate required hook address flags + // beforeInitialize (bit 13), beforeSwap (bit 7), afterSwap (bit 6) + const requiredFlags = (1 << 13) | (1 << 7) | (1 << 6); + + // 4. Deploy HookDeployer + const HookDeployer = await ethers.getContractFactory("HookDeployer"); + const hookDeployer = await HookDeployer.deploy(); + await hookDeployer.waitForDeployment(); + + // 5. Get hook bytecode + const MyCoFHEHook = await ethers.getContractFactory("MyCoFHEHook"); + const bytecode = MyCoFHEHook.bytecode + ethers.AbiCoder.defaultAbiCoder().encode( + ["address", "address", "string", "bytes32"], + [poolManagerAddress, deployer.address, specificationURI, specificationHash] + ).slice(2); + + const bytecodeHash = ethers.keccak256(bytecode); + + // 6. Find salt for correct address + console.log("Finding salt for address with flags:", requiredFlags.toString(16)); + const { salt, hookAddress } = await hookDeployer.findSalt(bytecodeHash, requiredFlags); + console.log("Found salt:", salt); + console.log("Hook will deploy to:", hookAddress); + + // 7. Deploy hook + const tx = await hookDeployer.deploy(salt, bytecode); + await tx.wait(); + + console.log("Hook deployed to:", hookAddress); + + // 8. Verify on explorer + if (network.name !== "hardhat" && network.name !== "localhost") { + console.log("Verifying contract..."); + await hre.run("verify:verify", { + address: hookAddress, + constructorArguments: [ + poolManagerAddress, + deployer.address, + specificationURI, + specificationHash + ] + }); + } + + // 9. Register AVS verifiers + const hook = MyCoFHEHook.attach(hookAddress); + const avsOperators = getAVSOperators(network.name); + + for (const operator of avsOperators) { + await hook.setVerifierAuthorization(operator, true); + console.log("Authorized verifier:", operator); + } + + // 10. Save deployment info + saveDeployment({ + hook: hookAddress, + deployer: deployer.address, + specificationURI, + specificationHash, + network: network.name, + timestamp: Date.now() + }); + + console.log("\nDeployment complete!"); + console.log("Next steps:"); + console.log("1. Request attestation via HookAttestationTaskManager"); + console.log("2. Wait for AVS verification"); + console.log("3. List in HookMarket"); +} + +main().catch(console.error); +``` + +--- + +## 7. Phase 5: Attestation + +### 7.1 Request Attestation + +```typescript +// scripts/request-attestation.ts +import { ethers } from "hardhat"; + +async function requestAttestation() { + const [developer] = await ethers.getSigners(); + + // Get deployed contracts + const hookAddress = process.env.HOOK_ADDRESS!; + const taskManagerAddress = getTaskManagerAddress(network.name); + + const taskManager = await ethers.getContractAt( + "IHookAttestationTaskManager", + taskManagerAddress + ); + + // Get pools using this hook + const poolIds = await getPoolsUsingHook(hookAddress); + + // Specify callbacks to verify + const callbacks = [ + "0x34fcd5be", // beforeInitialize + "0x8b803435", // beforeSwap + "0x9f5d7a8d" // afterSwap + ]; + + // Create attestation task + const tx = await taskManager.createAttestationTask( + hookAddress, + process.env.SPECIFICATION_URI!, + poolIds, + callbacks, + 100 // Number of state samples + ); + + const receipt = await tx.wait(); + const event = receipt.logs.find( + (log: any) => log.fragment?.name === "AttestationTaskCreated" + ); + + const taskIndex = event.args.taskIndex; + console.log("Attestation task created:", taskIndex); + console.log("Waiting for operator verification..."); + + // Monitor for response + await waitForAttestationResponse(taskManager, taskIndex); +} + +async function waitForAttestationResponse( + taskManager: Contract, + taskIndex: number +) { + return new Promise((resolve, reject) => { + taskManager.on("AttestationTaskResponded", (index, response, metadata) => { + if (index === taskIndex) { + if (response.specCompliant) { + console.log("Attestation successful!"); + console.log("Invariants verified:", response.invariantsVerified); + resolve(response); + } else { + console.log("Attestation failed!"); + console.log("Invariants failed:", response.invariantsFailed); + reject(new Error("Hook does not comply with specification")); + } + } + }); + + // Timeout after 1 hour + setTimeout(() => reject(new Error("Attestation timeout")), 3600000); + }); +} + +requestAttestation().catch(console.error); +``` + +--- + +## 8. Phase 6: Marketplace Listing + +### 8.1 List Hook + +```typescript +// scripts/list-hook.ts +import { ethers } from "hardhat"; + +async function listHook() { + const [developer] = await ethers.getSigners(); + + const hookAddress = process.env.HOOK_ADDRESS!; + const hookMarketAddress = getHookMarketAddress(network.name); + + const hookMarket = await ethers.getContractAt("HookMarket", hookMarketAddress); + + // Verify attestation exists + const attestationRegistry = await ethers.getContractAt( + "AttestationRegistry", + getAttestationRegistryAddress(network.name) + ); + + const isAttested = await attestationRegistry.isHookAttested(hookAddress); + if (!isAttested) { + throw new Error("Hook must have valid attestation before listing"); + } + + // Prepare listing metadata + const metadata = ethers.AbiCoder.defaultAbiCoder().encode( + ["string", "string", "string[]"], + [ + "Dynamic Fee Hook", // Name + "Volatility-based dynamic fee hook", // Description + ["DeFi", "Fee", "Dynamic", "CoFHE"] // Tags + ] + ); + + // List hook + const listingPrice = ethers.parseEther("0.1"); // Price per integration + const tx = await hookMarket.listHook(hookAddress, listingPrice, metadata); + await tx.wait(); + + console.log("Hook listed successfully!"); + console.log("Listing price:", ethers.formatEther(listingPrice), "ETH"); +} + +listHook().catch(console.error); +``` + +### 8.2 Monitor Revenue + +```typescript +// scripts/monitor-revenue.ts +import { ethers } from "hardhat"; + +async function monitorRevenue() { + const [developer] = await ethers.getSigners(); + const hookAddress = process.env.HOOK_ADDRESS!; + + const hook = await ethers.getContractAt("ICoFHEHook", hookAddress); + + // Get supported tokens + const tokens = [ + ethers.ZeroAddress, // ETH + "0x...", // USDC + "0x..." // WETH + ]; + + console.log("\nPending Revenue:"); + console.log("================"); + + let totalValueUSD = 0; + + for (const token of tokens) { + const balance = await hook.pendingRevenue(token); + const tokenSymbol = token === ethers.ZeroAddress ? "ETH" : await getTokenSymbol(token); + const tokenPrice = await getTokenPrice(token); + const valueUSD = Number(ethers.formatEther(balance)) * tokenPrice; + + console.log(`${tokenSymbol}: ${ethers.formatEther(balance)} (~$${valueUSD.toFixed(2)})`); + totalValueUSD += valueUSD; + } + + console.log("================"); + console.log(`Total: ~$${totalValueUSD.toFixed(2)}`); + + // Option to withdraw + const readline = require("readline"); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.question("\nWithdraw all revenue? (y/n): ", async (answer: string) => { + if (answer.toLowerCase() === "y") { + for (const token of tokens) { + const balance = await hook.pendingRevenue(token); + if (balance > 0) { + await hook.withdrawRevenue(token, balance, developer.address); + console.log(`Withdrawn ${ethers.formatEther(balance)} ${token === ethers.ZeroAddress ? "ETH" : await getTokenSymbol(token)}`); + } + } + } + rl.close(); + }); +} + +monitorRevenue().catch(console.error); +``` + +--- + +## 9. Quick Reference Checklist + +### Pre-Development +- [ ] Read State-Space Model documentation +- [ ] Understand IHooks interface requirements +- [ ] Plan state variables and transitions +- [ ] Identify which callbacks are needed + +### Phase 1: Specification +- [ ] Define all state variables (H) +- [ ] Define state transitions for each callback +- [ ] Define invariants +- [ ] Create comprehensive test vectors +- [ ] Upload specification to IPFS +- [ ] Save specification hash + +### Phase 2: Implementation +- [ ] Extend CoFHEHookTemplate +- [ ] Implement getHookPermissions() +- [ ] Implement all enabled callbacks +- [ ] Add encrypted state using FHE.sol +- [ ] Implement _getAdditionalState() +- [ ] Add revenue accrual in afterSwap/afterAddLiquidity +- [ ] Call FHE.allowThis() on all encrypted values + +### Phase 3: Testing +- [ ] Unit tests pass +- [ ] Integration tests pass +- [ ] All test vectors pass +- [ ] Invariants hold for random inputs +- [ ] Revenue accrual works correctly +- [ ] Verifier access control works + +### Phase 4: Deployment +- [ ] Find correct salt for hook address +- [ ] Deploy to testnet first +- [ ] Verify contract on explorer +- [ ] Register AVS verifiers +- [ ] Test on testnet pools +- [ ] Deploy to mainnet + +### Phase 5: Attestation +- [ ] Submit attestation request +- [ ] Wait for operator verification +- [ ] Receive attestation certificate +- [ ] Verify attestation in registry + +### Phase 6: Marketplace +- [ ] List hook in HookMarket +- [ ] Set pricing and terms +- [ ] Monitor for integrations +- [ ] Collect revenue regularly + +--- + +## 10. References + +1. **[IHooks]** Uniswap. *v4-core IHooks Interface*. https://github.com/Uniswap/v4-core +2. **[CoFHE Template]** Hook Bazaar. *CoFHE Hook Template*. `docs/hook-pkg/integration-guides/cofhe-hook-template.md` +3. **[AVS Verification]** Hook Bazaar. *AVS Verification System*. `docs/hook-pkg/architecture/avs-verification-system.md` +4. **[State-Space Model]** Hook Bazaar. *Hook State-Space Model*. `docs/hook-pkg/mathematical-models/state-space-model.md` +5. **[Fhenix CoFHE]** Fhenix Protocol. *CoFHE Documentation*. https://cofhe-docs.fhenix.zone/