-
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
1 parent
213a019
commit cfca04c
Showing
11 changed files
with
3,189 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
Large diffs are not rendered by default.
Oops, something went wrong.
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,16 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; | ||
import {TWAMM} from "../TWAMM.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; | ||
|
||
contract TWAMMImplementation is TWAMM { | ||
constructor(IPoolManager poolManager, uint256 interval, TWAMM addressToEtch) TWAMM(poolManager, interval) { | ||
Hooks.validateHookPermissions(addressToEtch, getHookPermissions()); | ||
} | ||
|
||
// make this a no-op in testing | ||
function validateHookAddress(BaseHook _this) internal pure override {} | ||
} |
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,136 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.15; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; | ||
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; | ||
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
|
||
interface ITWAMM { | ||
/// @notice Thrown when account other than owner attempts to interact with an order | ||
/// @param owner The owner of the order | ||
/// @param currentAccount The invalid account attempting to interact with the order | ||
error MustBeOwner(address owner, address currentAccount); | ||
|
||
/// @notice Thrown when trying to cancel an already completed order | ||
/// @param orderKey The orderKey | ||
error CannotModifyCompletedOrder(OrderKey orderKey); | ||
|
||
/// @notice Thrown when trying to submit an order with an expiration that isn't on the interval. | ||
/// @param expiration The expiration timestamp of the order | ||
error ExpirationNotOnInterval(uint256 expiration); | ||
|
||
/// @notice Thrown when trying to submit an order with an expiration time in the past. | ||
/// @param expiration The expiration timestamp of the order | ||
error ExpirationLessThanBlocktime(uint256 expiration); | ||
|
||
/// @notice Thrown when trying to submit an order without initializing TWAMM state first | ||
error NotInitialized(); | ||
|
||
/// @notice Thrown when trying to submit an order that's already ongoing. | ||
/// @param orderKey The already existing orderKey | ||
error OrderAlreadyExists(OrderKey orderKey); | ||
|
||
/// @notice Thrown when trying to interact with an order that does not exist. | ||
/// @param orderKey The already existing orderKey | ||
error OrderDoesNotExist(OrderKey orderKey); | ||
|
||
/// @notice Thrown when trying to subtract more value from a long term order than exists | ||
/// @param orderKey The orderKey | ||
/// @param unsoldAmount The amount still unsold | ||
/// @param amountDelta The amount delta for the order | ||
error InvalidAmountDelta(OrderKey orderKey, uint256 unsoldAmount, int256 amountDelta); | ||
|
||
/// @notice Thrown when submitting an order with a sellRate of 0 | ||
error SellRateCannotBeZero(); | ||
|
||
/// @notice Information associated with a long term order | ||
/// @member sellRate Amount of tokens sold per interval | ||
/// @member earningsFactorLast The accrued earnings factor from which to start claiming owed earnings for this order | ||
struct Order { | ||
uint256 sellRate; | ||
uint256 earningsFactorLast; | ||
} | ||
|
||
/// @notice Information that identifies an order | ||
/// @member owner Owner of the order | ||
/// @member expiration Timestamp when the order expires | ||
/// @member zeroForOne Bool whether the order is zeroForOne | ||
struct OrderKey { | ||
address owner; | ||
uint160 expiration; | ||
bool zeroForOne; | ||
} | ||
|
||
/// @notice Emitted when a new long term order is submitted | ||
/// @param poolId The id of the corresponding pool | ||
/// @param owner The owner of the new order | ||
/// @param expiration The expiration timestamp of the order | ||
/// @param zeroForOne Whether the order is selling token 0 for token 1 | ||
/// @param sellRate The sell rate of tokens per second being sold in the order | ||
/// @param earningsFactorLast The current earningsFactor of the order pool | ||
event SubmitOrder( | ||
PoolId indexed poolId, | ||
address indexed owner, | ||
uint160 expiration, | ||
bool zeroForOne, | ||
uint256 sellRate, | ||
uint256 earningsFactorLast | ||
); | ||
|
||
/// @notice Emitted when a long term order is updated | ||
/// @param poolId The id of the corresponding pool | ||
/// @param owner The owner of the existing order | ||
/// @param expiration The expiration timestamp of the order | ||
/// @param zeroForOne Whether the order is selling token 0 for token 1 | ||
/// @param sellRate The updated sellRate of tokens per second being sold in the order | ||
/// @param earningsFactorLast The current earningsFactor of the order pool | ||
/// (since updated orders will claim existing earnings) | ||
event UpdateOrder( | ||
PoolId indexed poolId, | ||
address indexed owner, | ||
uint160 expiration, | ||
bool zeroForOne, | ||
uint256 sellRate, | ||
uint256 earningsFactorLast | ||
); | ||
|
||
/// @notice Time interval on which orders are allowed to expire. Conserves processing needed on execute. | ||
function expirationInterval() external view returns (uint256); | ||
|
||
/// @notice Submits a new long term order into the TWAMM. Also executes TWAMM orders if not up to date. | ||
/// @param key The PoolKey for which to identify the amm pool of the order | ||
/// @param orderKey The OrderKey for the new order | ||
/// @param amountIn The amount of sell token to add to the order. Some precision on amountIn may be lost up to the | ||
/// magnitude of (orderKey.expiration - block.timestamp) | ||
/// @return orderId The bytes32 ID of the order | ||
function submitOrder(PoolKey calldata key, OrderKey calldata orderKey, uint256 amountIn) | ||
external | ||
returns (bytes32 orderId); | ||
|
||
/// @notice Update an existing long term order with current earnings, optionally modify the amount selling. | ||
/// @param key The PoolKey for which to identify the amm pool of the order | ||
/// @param orderKey The OrderKey for which to identify the order | ||
/// @param amountDelta The delta for the order sell amount. Negative to remove from order, positive to add, or | ||
/// -1 to remove full amount from order. | ||
function updateOrder(PoolKey calldata key, OrderKey calldata orderKey, int256 amountDelta) | ||
external | ||
returns (uint256 tokens0Owed, uint256 tokens1Owed); | ||
|
||
/// @notice Claim tokens owed from TWAMM contract | ||
/// @param token The token to claim | ||
/// @param to The receipient of the claim | ||
/// @param amountRequested The amount of tokens requested to claim. Set to 0 to claim all. | ||
/// @return amountTransferred The total token amount to be collected | ||
function claimTokens(Currency token, address to, uint256 amountRequested) | ||
external | ||
returns (uint256 amountTransferred); | ||
|
||
/// @notice Executes TWAMM orders on the pool, swapping on the pool itself to make up the difference between the | ||
/// two TWAMM pools swapping against each other | ||
/// @param key The pool key associated with the TWAMM | ||
function executeTWAMMOrders(PoolKey memory key) external; | ||
|
||
function tokensOwed(Currency token, address owner) external returns (uint256); | ||
} |
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,106 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; | ||
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; | ||
import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; | ||
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; | ||
|
||
/// @title Helper functions to access pool information | ||
/// TODO: Expose other getters on core with extsload. Only use when extsload is available and storage layout is frozen. | ||
library PoolGetters { | ||
uint256 constant POOL_SLOT = 10; | ||
uint256 constant TICKS_OFFSET = 4; | ||
uint256 constant TICK_BITMAP_OFFSET = 5; | ||
|
||
using StateLibrary for IPoolManager; | ||
|
||
function getNetLiquidityAtTick(IPoolManager poolManager, PoolId poolId, int24 tick) | ||
internal | ||
view | ||
returns (int128 l) | ||
{ | ||
bytes32 value = poolManager.extsload( | ||
keccak256(abi.encode(tick, uint256(keccak256(abi.encode(poolId, POOL_SLOT))) + TICKS_OFFSET)) | ||
); | ||
|
||
assembly { | ||
l := shr(128, and(value, shl(128, sub(shl(128, 1), 1)))) | ||
} | ||
} | ||
|
||
function getTickBitmapAtWord(IPoolManager poolManager, PoolId poolId, int16 word) | ||
internal | ||
view | ||
returns (uint256 bm) | ||
{ | ||
bm = uint256( | ||
poolManager.extsload( | ||
keccak256(abi.encode(word, uint256(keccak256(abi.encode(poolId, POOL_SLOT))) + TICK_BITMAP_OFFSET)) | ||
) | ||
); | ||
} | ||
|
||
/// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either | ||
/// to the left (less than or equal to) or right (greater than) of the given tick | ||
/// @param poolManager The mapping in which to compute the next initialized tick | ||
/// @param tick The starting tick | ||
/// @param tickSpacing The spacing between usable ticks | ||
/// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) | ||
/// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick | ||
/// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks | ||
function getNextInitializedTickWithinOneWord( | ||
IPoolManager poolManager, | ||
PoolId poolId, | ||
int24 tick, | ||
int24 tickSpacing, | ||
bool lte | ||
) internal view returns (int24 next, bool initialized) { | ||
unchecked { | ||
int24 compressed = tick / tickSpacing; | ||
if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity | ||
|
||
if (lte) { | ||
(int16 wordPos, uint8 bitPos) = position(compressed); | ||
// all the 1s at or to the right of the current bitPos | ||
uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); | ||
// uint256 masked = self[wordPos] & mask; | ||
uint256 tickBitmap = poolManager.getTickBitmap(poolId, wordPos); | ||
uint256 masked = tickBitmap & mask; | ||
|
||
// if there are no initialized ticks to the right of or at the current tick, return rightmost in the word | ||
initialized = masked != 0; | ||
// overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick | ||
next = initialized | ||
? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing | ||
: (compressed - int24(uint24(bitPos))) * tickSpacing; | ||
} else { | ||
// start from the word of the next tick, since the current tick state doesn't matter | ||
(int16 wordPos, uint8 bitPos) = position(compressed + 1); | ||
// all the 1s at or to the left of the bitPos | ||
uint256 mask = ~((1 << bitPos) - 1); | ||
uint256 tickBitmap = poolManager.getTickBitmap(poolId, wordPos); | ||
uint256 masked = tickBitmap & mask; | ||
|
||
// if there are no initialized ticks to the left of the current tick, return leftmost in the word | ||
initialized = masked != 0; | ||
// overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick | ||
next = initialized | ||
? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing | ||
: (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; | ||
} | ||
} | ||
} | ||
|
||
/// @notice Computes the position in the mapping where the initialized bit for a tick lives | ||
/// @param tick The tick for which to compute the position | ||
/// @return wordPos The key in the mapping containing the word in which the bit is stored | ||
/// @return bitPos The bit position in the word where the flag is stored | ||
function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { | ||
unchecked { | ||
wordPos = int16(tick >> 8); | ||
bitPos = uint8(int8(tick % 256)); | ||
} | ||
} | ||
} |
Oops, something went wrong.