-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {PoolKey, IPoolManager} from "./IPoolManager.sol"; | ||
|
||
type Currency is address; | ||
type BalanceDelta is int256; | ||
type BeforeSwapDelta is int256; | ||
|
||
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits | ||
/// of the address that the hooks contract is deployed to. | ||
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400 | ||
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used. | ||
/// See the Hooks library for the full spec. | ||
/// @dev Should only be callable by the v4 PoolManager. | ||
interface IHooks { | ||
/// @notice The hook called before the state of a pool is initialized | ||
/// @param sender The initial msg.sender for the initialize call | ||
/// @param key The key for the pool being initialized | ||
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96 | ||
/// @return bytes4 The function selector for the hook | ||
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4); | ||
|
||
/// @notice The hook called after the state of a pool is initialized | ||
/// @param sender The initial msg.sender for the initialize call | ||
/// @param key The key for the pool being initialized | ||
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96 | ||
/// @param tick The current tick after the state of a pool is initialized | ||
/// @return bytes4 The function selector for the hook | ||
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) | ||
external | ||
returns (bytes4); | ||
|
||
/// @notice The hook called before liquidity is added | ||
/// @param sender The initial msg.sender for the add liquidity call | ||
/// @param key The key for the pool | ||
/// @param params The parameters for adding liquidity | ||
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
function beforeAddLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
bytes calldata hookData | ||
) external returns (bytes4); | ||
|
||
/// @notice The hook called after liquidity is added | ||
/// @param sender The initial msg.sender for the add liquidity call | ||
/// @param key The key for the pool | ||
/// @param params The parameters for adding liquidity | ||
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta | ||
/// @param feesAccrued The fees accrued since the last time fees were collected from this position | ||
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency | ||
function afterAddLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
BalanceDelta delta, | ||
BalanceDelta feesAccrued, | ||
bytes calldata hookData | ||
) external returns (bytes4, BalanceDelta); | ||
|
||
/// @notice The hook called before liquidity is removed | ||
/// @param sender The initial msg.sender for the remove liquidity call | ||
/// @param key The key for the pool | ||
/// @param params The parameters for removing liquidity | ||
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
function beforeRemoveLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
bytes calldata hookData | ||
) external returns (bytes4); | ||
|
||
/// @notice The hook called after liquidity is removed | ||
/// @param sender The initial msg.sender for the remove liquidity call | ||
/// @param key The key for the pool | ||
/// @param params The parameters for removing liquidity | ||
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta | ||
/// @param feesAccrued The fees accrued since the last time fees were collected from this position | ||
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency | ||
function afterRemoveLiquidity( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata params, | ||
BalanceDelta delta, | ||
BalanceDelta feesAccrued, | ||
bytes calldata hookData | ||
) external returns (bytes4, BalanceDelta); | ||
|
||
/// @notice The hook called before a swap | ||
/// @param sender The initial msg.sender for the swap call | ||
/// @param key The key for the pool | ||
/// @param params The parameters for the swap | ||
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency | ||
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million) | ||
function beforeSwap( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.SwapParams calldata params, | ||
bytes calldata hookData | ||
) external returns (bytes4, BeforeSwapDelta, uint24); | ||
|
||
/// @notice The hook called after a swap | ||
/// @param sender The initial msg.sender for the swap call | ||
/// @param key The key for the pool | ||
/// @param params The parameters for the swap | ||
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative) | ||
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency | ||
function afterSwap( | ||
address sender, | ||
PoolKey calldata key, | ||
IPoolManager.SwapParams calldata params, | ||
BalanceDelta delta, | ||
bytes calldata hookData | ||
) external returns (bytes4, int128); | ||
|
||
/// @notice The hook called before donate | ||
/// @param sender The initial msg.sender for the donate call | ||
/// @param key The key for the pool | ||
/// @param amount0 The amount of token0 being donated | ||
/// @param amount1 The amount of token1 being donated | ||
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
function beforeDonate( | ||
address sender, | ||
PoolKey calldata key, | ||
uint256 amount0, | ||
uint256 amount1, | ||
bytes calldata hookData | ||
) external returns (bytes4); | ||
|
||
/// @notice The hook called after donate | ||
/// @param sender The initial msg.sender for the donate call | ||
/// @param key The key for the pool | ||
/// @param amount0 The amount of token0 being donated | ||
/// @param amount1 The amount of token1 being donated | ||
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook | ||
/// @return bytes4 The function selector for the hook | ||
function afterDonate( | ||
address sender, | ||
PoolKey calldata key, | ||
uint256 amount0, | ||
uint256 amount1, | ||
bytes calldata hookData | ||
) external returns (bytes4); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import {IHooks} from "./IHooks.sol"; | ||
|
||
type Currency is address; | ||
type BalanceDelta is int256; | ||
|
||
/// @notice Returns the key for identifying a pool | ||
struct PoolKey { | ||
/// @notice The lower currency of the pool, sorted numerically | ||
Currency currency0; | ||
/// @notice The higher currency of the pool, sorted numerically | ||
Currency currency1; | ||
/// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 | ||
uint24 fee; | ||
/// @notice Ticks that involve positions must be a multiple of tick spacing | ||
int24 tickSpacing; | ||
/// @notice The hooks of the pool | ||
IHooks hooks; | ||
} | ||
|
||
interface IPoolManager { | ||
|
||
struct SwapParams { | ||
/// Whether to swap token0 for token1 or vice versa | ||
bool zeroForOne; | ||
/// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut) | ||
int256 amountSpecified; | ||
/// The sqrt price at which, if reached, the swap will stop executing | ||
uint160 sqrtPriceLimitX96; | ||
} | ||
|
||
/// @notice Swap against the given pool | ||
/// @param key The pool to swap in | ||
/// @param params The parameters for swapping | ||
/// @param hookData The data to pass through to the swap hooks | ||
/// @return swapDelta The balance delta of the address swapping | ||
/// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified. | ||
/// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG | ||
/// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta. | ||
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) | ||
external | ||
returns (BalanceDelta swapDelta); | ||
|
||
struct ModifyLiquidityParams { | ||
// the lower and upper tick of the position | ||
int24 tickLower; | ||
int24 tickUpper; | ||
// how to modify the liquidity | ||
int256 liquidityDelta; | ||
// a value to set if you want unique liquidity positions at the same range | ||
bytes32 salt; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.9; | ||
|
||
import {PoolKey, IPoolManager} from "./IPoolManager.sol"; | ||
|
||
interface ITickOracleServiceManager { | ||
event NewTaskCreated(uint32 indexed taskIndex); | ||
|
||
event TaskResponded(uint32 indexed taskIndex, address operator); | ||
|
||
function latestTaskNum() external view returns (uint32); | ||
|
||
function allTaskBlocks( | ||
uint32 taskIndex | ||
) external view returns (uint32 blockNumber); | ||
|
||
function allTaskResponses( | ||
address operator, | ||
uint32 taskIndex | ||
) external view returns (bytes memory); | ||
|
||
function createNewTask( | ||
) external; | ||
|
||
function respondToTask( | ||
PoolKey memory key, | ||
IPoolManager.SwapParams calldata swapParams, | ||
bytes calldata hookData, | ||
uint32 referenceTaskIndex, | ||
bytes calldata signature | ||
) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.9; | ||
|
||
import {ECDSAServiceManagerBase} from | ||
"@eigenlayer-middleware/src/unaudited/ECDSAServiceManagerBase.sol"; | ||
import {ECDSAStakeRegistry} from "@eigenlayer-middleware/src/unaudited/ECDSAStakeRegistry.sol"; | ||
import {IServiceManager} from "@eigenlayer-middleware/src/interfaces/IServiceManager.sol"; | ||
import {ECDSAUpgradeable} from | ||
"@openzeppelin-upgrades/contracts/utils/cryptography/ECDSAUpgradeable.sol"; | ||
import {IERC1271Upgradeable} from "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; | ||
import {ITickOracleServiceManager} from "./ITickOracleServiceManager.sol"; | ||
import "@openzeppelin/contracts/utils/Strings.sol"; | ||
import "@eigenlayer/contracts/interfaces/IRewardsCoordinator.sol"; | ||
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; | ||
|
||
import {PoolKey, IPoolManager} from "./IPoolManager.sol"; | ||
|
||
/** | ||
* @title Primary entrypoint for procuring services from TickOracle. | ||
* @author Eigen Labs, Inc. | ||
*/ | ||
contract TickOracleServiceManager is ECDSAServiceManagerBase, ITickOracleServiceManager { | ||
using ECDSAUpgradeable for bytes32; | ||
|
||
IPoolManager public poolManager; | ||
|
||
uint32 public latestTaskNum; | ||
|
||
// mapping of task indices to all tasks hashes | ||
// when a task is created, task hash is stored here, | ||
// and responses need to pass the actual task, | ||
// which is hashed onchain and checked against this mapping | ||
mapping(uint32 taskNumber => uint32 blockNumber) public allTaskBlocks; | ||
|
||
// mapping of task indices to hash of abi.encode(taskResponse, taskResponseMetadata) | ||
mapping(address => mapping(uint32 => bytes)) public allTaskResponses; | ||
|
||
modifier onlyOperator() { | ||
require( | ||
ECDSAStakeRegistry(stakeRegistry).operatorRegistered(msg.sender), | ||
"Operator must be the caller" | ||
); | ||
_; | ||
} | ||
|
||
constructor( | ||
address _avsDirectory, | ||
address _stakeRegistry, | ||
address _rewardsCoordinator, | ||
address _delegationManager, | ||
address _poolManager | ||
) | ||
ECDSAServiceManagerBase( | ||
_avsDirectory, | ||
_stakeRegistry, | ||
_rewardsCoordinator, | ||
_delegationManager | ||
) | ||
{ | ||
poolManager = IPoolManager(_poolManager); | ||
} | ||
|
||
/* FUNCTIONS */ | ||
// NOTE: this function creates new task, assigns it a taskId | ||
function createNewTask( | ||
) public { | ||
// NOTE: creates new task only if the previous task was created 5 blocks ago | ||
if (allTaskBlocks[latestTaskNum] + 5 < block.number) { | ||
return; | ||
} | ||
latestTaskNum = latestTaskNum + 1; | ||
emit NewTaskCreated(latestTaskNum); | ||
} | ||
|
||
function respondToTask( | ||
PoolKey memory key, | ||
IPoolManager.SwapParams calldata swapParams, | ||
bytes calldata hookData, | ||
uint32 referenceTaskIndex, | ||
bytes memory signature | ||
) external { | ||
// check that the task is valid, hasn't been responsed yet, and is being responded in time | ||
require( | ||
referenceTaskIndex == latestTaskNum, | ||
"Task too old to respond to" | ||
); | ||
require( | ||
allTaskResponses[msg.sender][referenceTaskIndex].length == 0, | ||
"Operator has already responded to the task" | ||
); | ||
|
||
// The message that was signed | ||
bytes32 messageHash = keccak256(abi.encode(key, swapParams, hookData)); | ||
bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash(); | ||
bytes4 magicValue = IERC1271Upgradeable.isValidSignature.selector; | ||
if (!(magicValue == ECDSAStakeRegistry(stakeRegistry).isValidSignature(ethSignedMessageHash,signature))){ | ||
revert(); | ||
} | ||
|
||
poolManager.swap(key, swapParams, hookData); | ||
|
||
// updating the storage with task responses | ||
allTaskResponses[msg.sender][referenceTaskIndex] = signature; | ||
|
||
// emitting event | ||
emit TaskResponded(referenceTaskIndex, msg.sender); | ||
|
||
createNewTask(); | ||
} | ||
} |