Skip to content

Commit

Permalink
is compiling and test are passing
Browse files Browse the repository at this point in the history
  • Loading branch information
DeluxeRaph committed Aug 23, 2024
1 parent 213a019 commit cfca04c
Show file tree
Hide file tree
Showing 11 changed files with 3,189 additions and 0 deletions.
8 changes: 8 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

# foundry.toml

solc_version = '0.8.26'
evm_version = "cancun"
optimizer_runs = 800
via_ir = false
ffi = true
654 changes: 654 additions & 0 deletions src/TWAMM.sol

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/implementation/TWAMMImplementation.sol
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 {}
}
136 changes: 136 additions & 0 deletions src/interfaces/ITWAMM.sol
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);
}
106 changes: 106 additions & 0 deletions src/libraries/PoolGetters.sol
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));
}
}
}
Loading

0 comments on commit cfca04c

Please sign in to comment.