Skip to content

Commit

Permalink
Tick Oracle Contract
Browse files Browse the repository at this point in the history
  • Loading branch information
nobita851 committed Nov 30, 2024
1 parent 001dc6e commit 64e3c57
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 0 deletions.
156 changes: 156 additions & 0 deletions contracts/src/IHooks.sol
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);
}
55 changes: 55 additions & 0 deletions contracts/src/IPoolManager.sol
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;
}
}
32 changes: 32 additions & 0 deletions contracts/src/ITickOracleServiceManager.sol
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;
}
110 changes: 110 additions & 0 deletions contracts/src/TickOracleServiceManager.sol
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();
}
}

0 comments on commit 64e3c57

Please sign in to comment.