diff --git a/contracts/src/MiladyPoolTaskManager.sol b/contracts/src/MiladyPoolTaskManager.sol index 5936fd9a..3e2b374f 100644 --- a/contracts/src/MiladyPoolTaskManager.sol +++ b/contracts/src/MiladyPoolTaskManager.sol @@ -31,82 +31,56 @@ contract MiladyPoolTaskManager is BLSSignatureChecker, OperatorStateRetriever, IMiladyPoolTaskManager, - Hook + Hook, { using BN254 for BN254.G1Point; constructor( IRegistryCoordinator _registryCoordinator, IPoolManager _poolManager, - // SP1 contracts to verify public values - address _verifier ) BLSSignatureChecker(_registryCoordinator) Hook(_poolManager, _verifier) { - // TASK_RESPONSE_WINDOW_BLOCK = 100; } function initialize( IPauserRegistry _pauserRegistry, address initialOwner, - bytes32 _miladyPoolProgramVKey ) public initializer { _initializePauser(_pauserRegistry, UNPAUSE_ALL); _transferOwnership(initialOwner); - _setMiladyPoolProgramVKey(_miladyPoolProgramVKey); } - function setVerifier(address _verifier) public onlyOwner { - _setVerifier(_verifier); + function beforeSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + bytes calldata data + ) external override returns (bytes4, BeforeSwapDelta, uint24) { + if (data.length == 0) + return (this.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0); + ( + bytes4 selector, + BeforeSwapDelta delta, + // TODO: Figure out what this is + uint24 lpFeeOverride, + bytes memory proofBytes + ) = _beforeSwap(sender, key, params, data); + emit OrderFulfilled(proofBytes); + return (selector, delta, lpFeeOverride); } - function setMiladyPoolProgramVKey( - bytes32 _miladyPoolProgramVKey - ) public onlyOwner { - _setMiladyPoolProgramVKey(_miladyPoolProgramVKey); + function afterSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + BalanceDelta delta, + bytes calldata data + ) external override onlyByPoolManager returns (bytes4, int128) { + if (sender == address(this)) return (this.afterSwap.selector, 0); + int24 currentTick = _afterSwap(sender, key, params, delta, data); + emit TickUpdated(currentTick); + return (this.afterSwap.selector, 0); } - function createOrder(bytes calldata _proofBytes) external { - pendingOrders[_proofBytes] = true; - emit OrderCreated(_proofBytes); - } - - function cancelOrder(bytes calldata _proofBytes) external { - if (!pendingOrders[_proofBytes]) revert InvalidOrder(); - pendingOrders[_proofBytes] = false; - emit OrderCancelled(_proofBytes); - } - - // function beforeSwap( - // address sender, - // PoolKey calldata key, - // IPoolManager.SwapParams calldata params, - // bytes calldata data - // ) external override returns (bytes4, BeforeSwapDelta, uint24) { - // if (data.length == 0) - // return (this.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0); - // ( - // bytes4 selector, - // BeforeSwapDelta delta, - // // TODO: Figure out what this is - // uint24 lpFeeOverride, - // bytes memory proofBytes - // ) = _beforeSwap(sender, key, params, data); - // emit OrderFulfilled(proofBytes); - // return (selector, delta, lpFeeOverride); - // } - - // function afterSwap( - // address sender, - // PoolKey calldata key, - // IPoolManager.SwapParams calldata params, - // BalanceDelta delta, - // bytes calldata data - // ) external override onlyByPoolManager returns (bytes4, int128) { - // if (sender == address(this)) return (this.afterSwap.selector, 0); - // int24 currentTick = _afterSwap(sender, key, params, delta, data); - // emit TickUpdated(currentTick); - // return (this.afterSwap.selector, 0); - // } - function getLowerUsableTick( int24 tick, int24 tickSpacing diff --git a/contracts/src/_zk/MiladyPoolTaskManager.sol b/contracts/src/_zk/MiladyPoolTaskManager.sol new file mode 100644 index 00000000..5936fd9a --- /dev/null +++ b/contracts/src/_zk/MiladyPoolTaskManager.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: VPL-1.0 +pragma solidity ^0.8.26; + +// Uniswap +import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; +import {PoolKey} from "v4-core/types/PoolKey.sol"; +import {BalanceDelta} from "v4-core/types/BalanceDelta.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/types/BeforeSwapDelta.sol"; + +// Eigenlayer +import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import "@eigenlayer/contracts/permissions/Pausable.sol"; +import "@eigenlayer-middleware/src/interfaces/IServiceManager.sol"; +import {BLSApkRegistry} from "@eigenlayer-middleware/src/BLSApkRegistry.sol"; +import {RegistryCoordinator} from "@eigenlayer-middleware/src/RegistryCoordinator.sol"; +import {BLSSignatureChecker, IRegistryCoordinator} from "@eigenlayer-middleware/src/BLSSignatureChecker.sol"; +import {OperatorStateRetriever} from "@eigenlayer-middleware/src/OperatorStateRetriever.sol"; +import "@eigenlayer-middleware/src/libraries/BN254.sol"; + +// Custom: +import "./interfaces/IMiladyPoolTaskManager.sol"; +import {Hook} from "./base/Hook.sol"; + +// TODO: Need to add ERC6909 or ERC1155 to ensure that +// users can withdraw their tokens after a trade takes place +// through the pool and their verified proof is accepted. +contract MiladyPoolTaskManager is + OwnableUpgradeable, + Pausable, + BLSSignatureChecker, + OperatorStateRetriever, + IMiladyPoolTaskManager, + Hook +{ + using BN254 for BN254.G1Point; + + constructor( + IRegistryCoordinator _registryCoordinator, + IPoolManager _poolManager, + // SP1 contracts to verify public values + address _verifier + ) BLSSignatureChecker(_registryCoordinator) Hook(_poolManager, _verifier) { + // TASK_RESPONSE_WINDOW_BLOCK = 100; + } + + function initialize( + IPauserRegistry _pauserRegistry, + address initialOwner, + bytes32 _miladyPoolProgramVKey + ) public initializer { + _initializePauser(_pauserRegistry, UNPAUSE_ALL); + _transferOwnership(initialOwner); + _setMiladyPoolProgramVKey(_miladyPoolProgramVKey); + } + + function setVerifier(address _verifier) public onlyOwner { + _setVerifier(_verifier); + } + + function setMiladyPoolProgramVKey( + bytes32 _miladyPoolProgramVKey + ) public onlyOwner { + _setMiladyPoolProgramVKey(_miladyPoolProgramVKey); + } + + function createOrder(bytes calldata _proofBytes) external { + pendingOrders[_proofBytes] = true; + emit OrderCreated(_proofBytes); + } + + function cancelOrder(bytes calldata _proofBytes) external { + if (!pendingOrders[_proofBytes]) revert InvalidOrder(); + pendingOrders[_proofBytes] = false; + emit OrderCancelled(_proofBytes); + } + + // function beforeSwap( + // address sender, + // PoolKey calldata key, + // IPoolManager.SwapParams calldata params, + // bytes calldata data + // ) external override returns (bytes4, BeforeSwapDelta, uint24) { + // if (data.length == 0) + // return (this.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0); + // ( + // bytes4 selector, + // BeforeSwapDelta delta, + // // TODO: Figure out what this is + // uint24 lpFeeOverride, + // bytes memory proofBytes + // ) = _beforeSwap(sender, key, params, data); + // emit OrderFulfilled(proofBytes); + // return (selector, delta, lpFeeOverride); + // } + + // function afterSwap( + // address sender, + // PoolKey calldata key, + // IPoolManager.SwapParams calldata params, + // BalanceDelta delta, + // bytes calldata data + // ) external override onlyByPoolManager returns (bytes4, int128) { + // if (sender == address(this)) return (this.afterSwap.selector, 0); + // int24 currentTick = _afterSwap(sender, key, params, delta, data); + // emit TickUpdated(currentTick); + // return (this.afterSwap.selector, 0); + // } + + function getLowerUsableTick( + int24 tick, + int24 tickSpacing + ) public pure returns (int24) { + int24 intervals = tick / tickSpacing; + + if (tick < 0 && tick % tickSpacing != 0) { + intervals--; + } + + return intervals * tickSpacing; + } +} diff --git a/contracts/src/_zk/README.md b/contracts/src/_zk/README.md new file mode 100644 index 00000000..19255021 --- /dev/null +++ b/contracts/src/_zk/README.md @@ -0,0 +1 @@ +# DO NOT USE FOR NOW diff --git a/contracts/src/_zk/base/Hook.sol b/contracts/src/_zk/base/Hook.sol new file mode 100644 index 00000000..ac3ba1b1 --- /dev/null +++ b/contracts/src/_zk/base/Hook.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: VPL-1.0 +pragma solidity ^0.8.26; + +// Uniswap +import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; +import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "v4-core/types/PoolId.sol"; +import {Hooks} from "v4-core/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/types/PoolKey.sol"; +import {StateLibrary} from "v4-core/libraries/StateLibrary.sol"; +import {BalanceDelta} from "v4-core/types/BalanceDelta.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/types/BeforeSwapDelta.sol"; +import {Currency, CurrencyLibrary} from "v4-core/types/Currency.sol"; +import {TickMath} from "v4-core/libraries/TickMath.sol"; +import {Verification} from "./Verification.sol"; + +abstract contract Hook is BaseHook, Verification { + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + + // TODO: Figure out if we need latest order id, order hashes, zkps, matching order ids, responses, challenges, etc. + mapping(PoolId poolId => int24 lastTick) public lastTicks; + mapping(bytes => bool) public pendingOrders; + + // Errors + error InvalidOrder(); + error NothingToClaim(); + error NotEnoughToClaim(); + + constructor( + IPoolManager _poolManager, + // SP1 contracts to verify public values + address _verifier + ) BaseHook(_poolManager) Verification(_verifier) {} + + function getHookPermissions() + public + pure + override + returns (Hooks.Permissions memory) + { + return + Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: true, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + function afterInitialize( + address, + PoolKey calldata key, + uint160, + int24 tick, + bytes calldata + ) external override onlyByPoolManager returns (bytes4) { + lastTicks[key.toId()] = tick; + return this.afterInitialize.selector; + } + + function _beforeSwap( + address, + PoolKey calldata key, + IPoolManager.SwapParams calldata, + bytes calldata data + ) internal returns (bytes4, BeforeSwapDelta, uint24, bytes memory) { + (bytes memory publicValues, bytes memory proofBytes) = abi.decode( + data, + (bytes, bytes) + ); + + if (!pendingOrders[proofBytes]) { + revert InvalidOrder(); + } + + ( + // TODO: Probably need some of this... + // One is to check the direction of the trade + // Another is to call this with permit2 instead + // of the way we are doing it now (which has no + // actual assets lmao) + address walletAddress, + int24 tickToSellAt, + bool zeroForOne, + uint256 inputAmount, + uint256 outputAmount, + address tokenInput, + address token0, + address token1, + uint24 fee, + int24 tickSpacing, + address hooks, + bytes32 permit2Signature + ) = _verifyMiladyPoolOrderProof(publicValues, proofBytes); + + // Validate the pool key + // Validate the outputs of the proof + + // TODO: Refactor this section; not gonna work because + // you have to activate the permit 2 signature + BalanceDelta delta = poolManager.swap( + key, + IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + // TODO: Come back to this because you might need to cast it and add a negative (check logic) + amountSpecified: tokenInput == token0 + ? zeroForOne + ? int256(inputAmount) + : -int256(inputAmount) + : zeroForOne + ? int256(outputAmount) + : -int256(outputAmount), + sqrtPriceLimitX96: zeroForOne + ? TickMath.MIN_SQRT_PRICE + 1 + : TickMath.MAX_SQRT_PRICE - 1 + }), + // TODO: Figure out if you want to handle this here again too lmao (permit2signature) + "" + ); + + if (zeroForOne) { + if (delta.amount0() < 0) { + _settle(key.currency0, uint128(-delta.amount0())); + } + + if (delta.amount1() > 0) { + _take(key.currency1, uint128(-delta.amount1())); + } + } else { + if (delta.amount0() > 0) { + _take(key.currency0, uint128(-delta.amount0())); + } + + if (delta.amount1() < 0) { + _settle(key.currency1, uint128(-delta.amount1())); + } + } + + pendingOrders[proofBytes] = false; + + // Return the appropriate values + // TODO: Need to update toBeforeSwapDelta to handle token in and out amounts + return ( + this.beforeSwap.selector, + toBeforeSwapDelta(0, 0), + 0, + proofBytes + ); + } + + function _afterSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata, + BalanceDelta, + bytes calldata + ) internal returns (int24) { + (, int24 currentTick, , ) = poolManager.getSlot0(key.toId()); + return currentTick; + } + + function _settle(Currency currency, uint128 amount) internal { + // Transfer tokens to PM and let it know + poolManager.sync(currency); + currency.transfer(address(poolManager), amount); + poolManager.settle(); + } + + function _take(Currency currency, uint128 amount) internal { + // Take tokens out of PM to our hook contract + poolManager.take(currency, address(this), amount); + } +} diff --git a/contracts/src/_zk/base/Structs.sol b/contracts/src/_zk/base/Structs.sol new file mode 100644 index 00000000..c4133ee5 --- /dev/null +++ b/contracts/src/_zk/base/Structs.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: VPL-1.0 +pragma solidity ^0.8.26; + +import {IHooks} from "v4-core/interfaces/IHooks.sol"; + +struct PublicValuesStruct { + address walletAddress; + int24 tickToSellAt; + bool zeroForOne; + uint256 inputAmount; + uint256 outputAmount; + address tokenInput; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + address hooks; // Should be passed into IHooks interface + bytes32 permit2Signature; +} diff --git a/contracts/src/base/Verification.sol b/contracts/src/_zk/base/Verification.sol similarity index 100% rename from contracts/src/base/Verification.sol rename to contracts/src/_zk/base/Verification.sol diff --git a/contracts/src/_zk/interfaces/IMiladyPoolTaskManager.sol b/contracts/src/_zk/interfaces/IMiladyPoolTaskManager.sol new file mode 100644 index 00000000..63193944 --- /dev/null +++ b/contracts/src/_zk/interfaces/IMiladyPoolTaskManager.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: VPL-1.0 +pragma solidity ^0.8.26; + +interface IMiladyPoolTaskManager { + // Need events + event OrderCreated(bytes proofBytes); + // TODO: Figure out if we need a correpsonding order id + // or a zkp or both + // TODO: Figure out if you want to add more details for the trade here + event OrderFulfilled(bytes proofBytes); + + event TickUpdated(int24 tick); + + event OrderCancelled(bytes proofBytes); + // TODO: Add events for challenges (successful and unsuccessful) + + // TODO: Figure out what other structs you need here (look at Arena X, Uniswap X, etc.) + // Need functions + function createOrder(bytes calldata proofBytes) external; + function cancelOrder(bytes calldata proofBytes) external; + // TODO: Look at Uniswap X and Arena X for inspiration +} diff --git a/contracts/src/base/Hook.sol b/contracts/src/base/Hook.sol index ac3ba1b1..80a6bfb3 100644 --- a/contracts/src/base/Hook.sol +++ b/contracts/src/base/Hook.sol @@ -12,9 +12,10 @@ import {BalanceDelta} from "v4-core/types/BalanceDelta.sol"; import {BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/types/BeforeSwapDelta.sol"; import {Currency, CurrencyLibrary} from "v4-core/types/Currency.sol"; import {TickMath} from "v4-core/libraries/TickMath.sol"; -import {Verification} from "./Verification.sol"; +import {WyvernInspired} from "v4-core/src/base/WyvernInspired.sol"; +import {PublicValuesStruct, Sig} from "./Structs.sol"; -abstract contract Hook is BaseHook, Verification { +abstract contract Hook is BaseHook, WyvernInspired { using StateLibrary for IPoolManager; using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; @@ -67,6 +68,7 @@ abstract contract Hook is BaseHook, Verification { bytes calldata ) external override onlyByPoolManager returns (bytes4) { lastTicks[key.toId()] = tick; + emit TickUpdated(tick); return this.afterInitialize.selector; } @@ -76,21 +78,28 @@ abstract contract Hook is BaseHook, Verification { IPoolManager.SwapParams calldata, bytes calldata data ) internal returns (bytes4, BeforeSwapDelta, uint24, bytes memory) { - (bytes memory publicValues, bytes memory proofBytes) = abi.decode( + (bytes memory publicValues, bytes memory sig) = abi.decode( data, (bytes, bytes) ); - if (!pendingOrders[proofBytes]) { + PublicValuesStruct memory _publicValues = abi.decode( + publicValues, + (PublicValuesStruct) + ); + + Sig memory _sig = abi.decode(sig, (Sig)); + + bytes32 hash = _hashOrder(_publicValues); + + if (!_validateOrder(hash, _publicValues, _sig)) { revert InvalidOrder(); } + // Validate the pool key + // Validate the outputs of the proof + ( - // TODO: Probably need some of this... - // One is to check the direction of the trade - // Another is to call this with permit2 instead - // of the way we are doing it now (which has no - // actual assets lmao) address walletAddress, int24 tickToSellAt, bool zeroForOne, @@ -103,10 +112,20 @@ abstract contract Hook is BaseHook, Verification { int24 tickSpacing, address hooks, bytes32 permit2Signature - ) = _verifyMiladyPoolOrderProof(publicValues, proofBytes); - - // Validate the pool key - // Validate the outputs of the proof + ) = ( + _publicValues.walletAddress, + _publicValues.tickToSellAt, + _publicValues.zeroForOne, + _publicValues.inputAmount, + _publicValues.outputAmount, + _publicValues.tokenInput, + _publicValues.token0, + _publicValues.token1, + _publicValues.fee, + _publicValues.tickSpacing, + _publicValues.hooks, + _publicValues.permit2Signature + ); // TODO: Refactor this section; not gonna work because // you have to activate the permit 2 signature diff --git a/contracts/src/base/Structs.sol b/contracts/src/base/Structs.sol index c4133ee5..23fc1efd 100644 --- a/contracts/src/base/Structs.sol +++ b/contracts/src/base/Structs.sol @@ -17,3 +17,9 @@ struct PublicValuesStruct { address hooks; // Should be passed into IHooks interface bytes32 permit2Signature; } + +struct Sig { + uint8 v; + bytes32 r; + bytes32 s; +} diff --git a/contracts/src/base/WyvernInspired.sol b/contracts/src/base/WyvernInspired.sol new file mode 100644 index 00000000..2bbf835c --- /dev/null +++ b/contracts/src/base/WyvernInspired.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: VPL-1.0 +pragma solidity ^0.8.26; + +import {PublicValuesStruct, Sig} from "./Structs.sol"; + +// NOTE: Only supporting ECDSA signatures for now +// TODO: Support EIP-1271 and other signature standards +abstract contract WyvernInspired { + mapping(bytes32 => bool) public cancelledOrFinalized; + + function hashOrder( + PublicValuesStruct memory _publicValues + ) public pure returns (bytes32) { + return _hashOrder(_publicValues); + } + + function hashToSign( + PublicValuesStruct memory _publicValues + ) public pure returns (bytes32) { + return _hashToSign(_publicValues); + } + + function validateOrderParameters( + PublicValuesStruct memory order + ) public view { + return _validateOrderParameters(order); + } + + function validateOrder( + PublicValuesStruct memory order, + Sig memory sig + ) public view returns (bool) { + return _validateOrder(order, sig); + } + + function cancelOrder( + PublicValuesStruct memory order, + Sig memory sig + ) public { + return _cancelOrder(order, sig); + } + + function _hashOrder( + PublicValuesStruct memory _publicValues + ) internal pure returns (bytes32) { + return keccak256(abi.encode(_publicValues)); + } + + function _hashToSign( + PublicValuesStruct memory _publicValues + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + _hashOrder(_publicValues) + ) + ); + } + + function _requireValidOrder( + PublicValuesStruct memory order, + Sig memory sig + ) internal view returns (bytes32) { + bytes32 hash = _hashToSign(_publicValues); + require(_validateOrder(hash, order, sig)); + return hash; + } + + // TODO: May not need this for now but nice to have + function _validateOrderParameters( + PublicValuesStruct memory order + ) internal view { + require(order.walletAddress != address(0), "INVALID_ADDRESS"); + require(order.tickToSellAt != 0, "INVALID_TICK"); + require(order.inputAmount != 0, "INVALID_AMOUNT"); + require(order.outputAmount != 0, "INVALID_AMOUNT"); + require(order.tokenInput != address(0), "INVALID_ADDRESS"); + require(order.token0 != address(0), "INVALID_ADDRESS"); + require(order.token1 != address(0), "INVALID_ADDRESS"); + require(order.fee != 0, "INVALID_FEE"); + require(order.tickSpacing != 0, "INVALID_TICK_SPACING"); + require(order.hooks != address(0), "INVALID_HOOKS"); + } + + function _validateOrder( + bytes32 hash, + PublicValuesStruct memory order, + Sig memory sig + ) internal view returns (bool) { + if (!_validateOrderParameters(order)) { + return false; + } + + if (cancelledOrFinalized[hash]) { + return false; + } + + if (ecrecover(hash, sig.v, sig.r, sig.s) == order.walletAddress) { + return true; + } + + return false; + } + + function _cancelOrder(PublicValuesStruct memory order) internal { + bytes32 hash = _requireValidOrder(order, sig); + require(msg.sender = order.walletAddress, "INVALID_ADDRESS"); + + cancelledOrFinalized[hash] = true; + + // TODO: Add event + } +}