Overview
S Balance
0 S
S Value
-More Info
Private Name Tags
ContractCreator
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
931219 | 9 hrs ago | Contract Creation | 0 S |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Source Code Verified (Exact Match)
Contract Name:
Position
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 100 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; import {FullMath} from './FullMath.sol'; import {FixedPoint128} from './FixedPoint128.sol'; import {FixedPoint32} from './FixedPoint32.sol'; import {FixedPoint96} from './FixedPoint96.sol'; import {Oracle} from './Oracle.sol'; import {SafeCast} from './SafeCast.sol'; import {Tick} from './Tick.sol'; import {TickBitmap} from './TickBitmap.sol'; import {PoolStorage, PositionInfo, PositionCheckpoint, RewardInfo} from './PoolStorage.sol'; /// @title Position /// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary /// @dev Positions store additional state for tracking fees owed to the position library Position { error NP(); error FTR(); /// @notice Returns the hash used to store positions in a mapping /// @param owner The address of the position owner /// @param index The index of the position /// @param tickLower The lower tick boundary of the position /// @param tickUpper The upper tick boundary of the position /// @return _hash The hash used to store positions in a mapping function positionHash( address owner, uint256 index, int24 tickLower, int24 tickUpper ) internal pure returns (bytes32) { return keccak256(abi.encodePacked(owner, index, tickLower, tickUpper)); } /// @notice Returns the Info struct of a position, given an owner and position boundaries /// @param self The mapping containing all user positions /// @param owner The address of the position owner /// @param tickLower The lower tick boundary of the position /// @param tickUpper The upper tick boundary of the position /// @return position The position info struct of the given owners' position function get( mapping(bytes32 => PositionInfo) storage self, address owner, uint256 index, int24 tickLower, int24 tickUpper ) internal view returns (PositionInfo storage position) { position = self[positionHash(owner, index, tickLower, tickUpper)]; } /// @notice Credits accumulated fees to a user's position /// @param self The individual position to update /// @param liquidityDelta The change in pool liquidity as a result of the position update /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries function update( PositionInfo storage self, int128 liquidityDelta, uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128, bytes32 _positionHash, uint256 period, uint160 secondsPerLiquidityPeriodX128 ) internal { PoolStorage.PoolState storage $ = PoolStorage.getStorage(); uint128 liquidity = self.liquidity; uint128 liquidityNext; if (liquidityDelta == 0) { /// @dev disallow pokes for 0 liquidity positions if (liquidity <= 0) revert NP(); liquidityNext = liquidity; } else { liquidityNext = liquidityDelta < 0 ? liquidity - uint128(-liquidityDelta) : liquidity + uint128(liquidityDelta); } /// @dev calculate accumulated fees. overflow in the subtraction of fee growth is expected uint128 tokensOwed0; uint128 tokensOwed1; unchecked { tokensOwed0 = uint128( FullMath.mulDiv(feeGrowthInside0X128 - self.feeGrowthInside0LastX128, liquidity, FixedPoint128.Q128) ); tokensOwed1 = uint128( FullMath.mulDiv(feeGrowthInside1X128 - self.feeGrowthInside1LastX128, liquidity, FixedPoint128.Q128) ); /// @dev update the position if (liquidityDelta != 0) self.liquidity = liquidityNext; self.feeGrowthInside0LastX128 = feeGrowthInside0X128; self.feeGrowthInside1LastX128 = feeGrowthInside1X128; if (tokensOwed0 > 0 || tokensOwed1 > 0) { /// @dev overflow is acceptable, user must withdraw before they hit type(uint128).max fees self.tokensOwed0 += tokensOwed0; self.tokensOwed1 += tokensOwed1; } } /// @dev write checkpoint, push a checkpoint if the last period is different, overwrite if not uint256 checkpointLength = $.positionCheckpoints[_positionHash].length; if (checkpointLength == 0 || $.positionCheckpoints[_positionHash][checkpointLength - 1].period != period) { $.positionCheckpoints[_positionHash].push(PositionCheckpoint({period: period, liquidity: liquidityNext})); } else { $.positionCheckpoints[_positionHash][checkpointLength - 1].liquidity = liquidityNext; } int160 secondsPerLiquidityPeriodIntX128 = int160(secondsPerLiquidityPeriodX128); int160 secondsPerLiquidityPeriodStartX128 = self.periodRewardInfo[period].secondsPerLiquidityPeriodStartX128; /// @dev take the difference to make the delta positive or zero secondsPerLiquidityPeriodIntX128 -= secondsPerLiquidityPeriodStartX128; /// @dev these int should never be negative if (secondsPerLiquidityPeriodIntX128 < 0) { secondsPerLiquidityPeriodIntX128 = 0; } /// @dev secondsDebtDeltaX96 is declared differently based on the liquidityDelta int256 secondsDebtDeltaX96 = liquidityDelta > 0 /// @dev case: delta > 0 ? SafeCast.toInt256( /// @dev round upwards FullMath.mulDivRoundingUp( uint256(uint128(liquidityDelta)), uint256(uint160(secondsPerLiquidityPeriodIntX128)), FixedPoint32.Q32 ) ) /// @dev case: delta <= 0 : SafeCast.toInt256( /// @dev round downwards FullMath.mulDiv( /// @dev flip liquidityDelta sign uint256(uint128(-liquidityDelta)), uint256(uint160(secondsPerLiquidityPeriodIntX128)), FixedPoint32.Q32 ) ); self.periodRewardInfo[period].secondsDebtX96 = liquidityDelta > 0 ? self.periodRewardInfo[period].secondsDebtX96 + secondsDebtDeltaX96 /// @dev can't overflow since each period is way less than uint31 : self.periodRewardInfo[period].secondsDebtX96 - secondsDebtDeltaX96; } /// @notice gets the checkpoint directly before the period /// @dev returns the 0th index if there's no checkpoints /// @param checkpoints the position's checkpoints in storage /// @param period the period of interest function getCheckpoint( PositionCheckpoint[] storage checkpoints, uint256 period ) internal view returns (uint256 checkpointIndex, uint256 checkpointPeriod) { { uint256 checkpointLength = checkpoints.length; /// @dev return 0 if length is 0 if (checkpointLength == 0) { return (0, 0); } checkpointPeriod = checkpoints[0].period; /// @dev return 0 if first checkpoint happened after period if (checkpointPeriod > period) { return (0, 0); } checkpointIndex = checkpointLength - 1; } checkpointPeriod = checkpoints[checkpointIndex].period; /// @dev Find relevant checkpoint if latest checkpoint isn't before period of interest if (checkpointPeriod > period) { uint256 lower = 0; uint256 upper = checkpointIndex; while (upper > lower) { /// @dev ceil, avoiding overflow uint256 center = upper - (upper - lower) / 2; checkpointPeriod = checkpoints[center].period; if (checkpointPeriod == period) { checkpointIndex = center; return (checkpointIndex, checkpointPeriod); } else if (checkpointPeriod < period) { lower = center; } else { upper = center - 1; } } checkpointIndex = lower; checkpointPeriod = checkpoints[checkpointIndex].period; } return (checkpointIndex, checkpointPeriod); } struct PositionPeriodSecondsInRangeParams { uint256 period; address owner; uint256 index; int24 tickLower; int24 tickUpper; uint32 _blockTimestamp; } /// @notice Get the period seconds in range of a specific position /// @return periodSecondsInsideX96 seconds the position was not in range for the period function positionPeriodSecondsInRange( PositionPeriodSecondsInRangeParams memory params ) public view returns (uint256 periodSecondsInsideX96) { PoolStorage.PoolState storage $ = PoolStorage.getStorage(); uint256 currentPeriod = $.lastPeriod; if (params.period > currentPeriod) revert FTR(); bytes32 _positionHash = positionHash(params.owner, params.index, params.tickLower, params.tickUpper); uint256 liquidity; int160 secondsPerLiquidityPeriodStartX128; PositionCheckpoint[] storage checkpoints = $.positionCheckpoints[_positionHash]; /// @dev get checkpoint at period, or last checkpoint before the period (uint256 checkpointIndex, uint256 checkpointPeriod) = getCheckpoint(checkpoints, params.period); /// @dev Return 0s if checkpointPeriod is 0 if (checkpointPeriod == 0) { return 0; } liquidity = checkpoints[checkpointIndex].liquidity; secondsPerLiquidityPeriodStartX128 = $ .positions[_positionHash] .periodRewardInfo[params.period] .secondsPerLiquidityPeriodStartX128; uint160 secondsPerLiquidityInsideX128 = Oracle.periodCumulativesInside( uint32(params.period), params.tickLower, params.tickUpper, params._blockTimestamp ); /// @dev underflow will be protected by sanity check secondsPerLiquidityInsideX128 = uint160( int160(secondsPerLiquidityInsideX128) - secondsPerLiquidityPeriodStartX128 ); RewardInfo storage rewardInfo = $.positions[_positionHash].periodRewardInfo[params.period]; int256 secondsDebtX96 = rewardInfo.secondsDebtX96; /// @dev addDelta checks for under and overflows periodSecondsInsideX96 = FullMath.mulDiv(liquidity, secondsPerLiquidityInsideX128, FixedPoint32.Q32); /// @dev Need to check if secondsDebtX96>periodSecondsInsideX96, since rounding can cause underflows if (secondsDebtX96 < 0 || periodSecondsInsideX96 > uint256(secondsDebtX96)) { periodSecondsInsideX96 = secondsDebtX96 < 0 ? periodSecondsInsideX96 + uint256(-secondsDebtX96) : periodSecondsInsideX96 - uint256(secondsDebtX96); } else { periodSecondsInsideX96 = 0; } /// @dev sanity if (periodSecondsInsideX96 > 1 weeks * FixedPoint96.Q96) { periodSecondsInsideX96 = 0; } } struct UpdatePositionParams { /// @dev the owner of the position address owner; /// @dev the index of the position uint256 index; /// @dev the lower tick of the position's tick range int24 tickLower; /// @dev the upper tick of the position's tick range int24 tickUpper; /// @dev the amount liquidity changes by int128 liquidityDelta; /// @dev the current tick, passed to avoid sloads int24 tick; uint32 _blockTimestamp; int24 tickSpacing; uint128 maxLiquidityPerTick; } /// @dev Gets and updates a position with the given liquidity delta /// @param params the position details and the change to the position's liquidity to effect function _updatePosition(UpdatePositionParams memory params) external returns (PositionInfo storage position) { PoolStorage.PoolState storage $ = PoolStorage.getStorage(); /// @dev calculate the period once, and reuse it uint256 period = params._blockTimestamp / 1 weeks; /// @dev precompute the position hash bytes32 _positionHash = positionHash(params.owner, params.index, params.tickLower, params.tickUpper); /// @dev fetch the position using the precomputed _positionHash position = $.positions[_positionHash]; /// @dev SLOAD for gas optimization uint256 _feeGrowthGlobal0X128 = $.feeGrowthGlobal0X128; uint256 _feeGrowthGlobal1X128 = $.feeGrowthGlobal1X128; /// @dev use the tick from `$.slot0` instead of `params.tick` for consistency int24 currentTick = $.slot0.tick; /// @dev check and update ticks if needed bool flippedLower; bool flippedUpper; if (params.liquidityDelta != 0) { /// @dev directly use params._blockTimestamp instead of creating a new `time` variable (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = Oracle.observeSingle( $.observations, params._blockTimestamp, 0, currentTick, /// @dev use `currentTick` consistently $.slot0.observationIndex, $.liquidity, $.slot0.observationCardinality ); flippedLower = Tick.update( $._ticks, params.tickLower, currentTick, /// @dev use `currentTick` consistently params.liquidityDelta, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128, secondsPerLiquidityCumulativeX128, tickCumulative, params._blockTimestamp, false, params.maxLiquidityPerTick ); flippedUpper = Tick.update( $._ticks, params.tickUpper, currentTick, /// @dev use `currentTick` consistently params.liquidityDelta, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128, secondsPerLiquidityCumulativeX128, tickCumulative, params._blockTimestamp, true, params.maxLiquidityPerTick ); /// @dev flip ticks if needed if (flippedLower) { TickBitmap.flipTick($.tickBitmap, params.tickLower, params.tickSpacing); } if (flippedUpper) { TickBitmap.flipTick($.tickBitmap, params.tickUpper, params.tickSpacing); } } /// @dev calculate the fee growth inside (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = Tick.getFeeGrowthInside( $._ticks, params.tickLower, params.tickUpper, currentTick, /// @dev use `currentTick` consistently _feeGrowthGlobal0X128, _feeGrowthGlobal1X128 ); /// @dev get the seconds per liquidity period cumulatives uint160 secondsPerLiquidityPeriodX128 = Oracle.periodCumulativesInside( uint32(period), params.tickLower, params.tickUpper, params._blockTimestamp ); /// @dev initialize position reward info if needed if (!position.periodRewardInfo[period].initialized || position.liquidity == 0) { initializeSecondsStart( position, PositionPeriodSecondsInRangeParams({ period: period, owner: params.owner, index: params.index, tickLower: params.tickLower, tickUpper: params.tickUpper, _blockTimestamp: params._blockTimestamp }), secondsPerLiquidityPeriodX128 ); } /// @dev update the position update( position, params.liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128, _positionHash, period, secondsPerLiquidityPeriodX128 ); /// @dev clear tick data if liquidity delta is negative and the ticks no longer hold liquidity if (params.liquidityDelta < 0) { if (flippedLower) { Tick.clear($._ticks, params.tickLower, period); } if (flippedUpper) { Tick.clear($._ticks, params.tickUpper, period); } } } /// @notice Initializes secondsPerLiquidityPeriodStartX128 for a position /// @param position The individual position /// @param secondsInRangeParams Parameters used to find the seconds in range /// @param secondsPerLiquidityPeriodX128 The seconds in range gained per unit of liquidity, inside the position's tick boundaries for this period function initializeSecondsStart( PositionInfo storage position, PositionPeriodSecondsInRangeParams memory secondsInRangeParams, uint160 secondsPerLiquidityPeriodX128 ) internal { /// @dev record initialized position.periodRewardInfo[secondsInRangeParams.period].initialized = true; /// @dev record owed tokens if liquidity > 0 (means position existed before period change) if (position.liquidity > 0) { uint256 periodSecondsInsideX96 = positionPeriodSecondsInRange(secondsInRangeParams); position.periodRewardInfo[secondsInRangeParams.period].secondsDebtX96 = -int256(periodSecondsInsideX96); } /// @dev convert uint to int /// @dev negative expected sometimes, which is allowed int160 secondsPerLiquidityPeriodIntX128 = int160(secondsPerLiquidityPeriodX128); position .periodRewardInfo[secondsInRangeParams.period] .secondsPerLiquidityPeriodStartX128 = secondsPerLiquidityPeriodIntX128; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; /// @title Contains 512-bit math functions /// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision /// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits library FullMath { /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 /// @param a The multiplicand /// @param b The multiplier /// @param denominator The divisor /// @return result The 256-bit result /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = a * b // Compute the product mod 2**256 and mod 2**256 - 1 // then use the Chinese Remainder Theorem to reconstruct // the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2**256 + prod0 uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(a, b, not(0)) prod0 := mul(a, b) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division if (prod1 == 0) { require(denominator > 0); assembly { result := div(prod0, denominator) } return result; } // Make sure the result is less than 2**256. // Also prevents denominator == 0 require(denominator > prod1); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0] // Compute remainder using mulmod uint256 remainder; assembly { remainder := mulmod(a, b, denominator) } // Subtract 256 bit number from 512 bit number assembly { prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator // Compute largest power of two divisor of denominator. // Always >= 1. uint256 twos = (0 - denominator) & denominator; // Divide denominator by power of two assembly { denominator := div(denominator, twos) } // Divide [prod1 prod0] by the factors of two assembly { prod0 := div(prod0, twos) } // Shift in bits from prod1 into prod0. For this we need // to flip `twos` such that it is 2**256 / twos. // If twos is zero, then it becomes one assembly { twos := add(div(sub(0, twos), twos), 1) } prod0 |= prod1 * twos; // Invert denominator mod 2**256 // Now that denominator is an odd number, it has an inverse // modulo 2**256 such that denominator * inv = 1 mod 2**256. // Compute the inverse by starting with a seed that is correct // correct for four bits. That is, denominator * inv = 1 mod 2**4 uint256 inv = (3 * denominator) ^ 2; // Now use Newton-Raphson iteration to improve the precision. // Thanks to Hensel's lifting lemma, this also works in modular // arithmetic, doubling the correct bits in each step. inv *= 2 - denominator * inv; // inverse mod 2**8 inv *= 2 - denominator * inv; // inverse mod 2**16 inv *= 2 - denominator * inv; // inverse mod 2**32 inv *= 2 - denominator * inv; // inverse mod 2**64 inv *= 2 - denominator * inv; // inverse mod 2**128 inv *= 2 - denominator * inv; // inverse mod 2**256 // Because the division is now exact we can divide by multiplying // with the modular inverse of denominator. This will give us the // correct result modulo 2**256. Since the precoditions guarantee // that the outcome is less than 2**256, this is the final result. // We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inv; return result; } } /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 /// @param a The multiplicand /// @param b The multiplier /// @param denominator The divisor /// @return result The 256-bit result function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { unchecked { result = mulDiv(a, b, denominator); if (mulmod(a, b, denominator) > 0) { require(result < type(uint256).max); result++; } } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.4.0; /// @title FixedPoint128 /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) library FixedPoint128 { uint256 internal constant Q128 = 0x100000000000000000000000000000000; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.4.0; /// @title FixedPoint32 /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) library FixedPoint32 { uint8 internal constant RESOLUTION = 32; uint256 internal constant Q32 = 0x100000000; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.4.0; /// @title FixedPoint96 /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) /// @dev Used in SqrtPriceMath.sol library FixedPoint96 { uint8 internal constant RESOLUTION = 96; uint256 internal constant Q96 = 0x1000000000000000000000000; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; import {PoolStorage, Observation, TickInfo, Slot0} from './PoolStorage.sol'; /// @title Oracle /// @notice Provides price and liquidity data useful for a wide variety of system designs /// @dev Instances of stored oracle data, "observations", are collected in the oracle array /// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the /// maximum length of the oracle array. New slots will be added when the array is fully populated. /// Observations are overwritten when the full length of the oracle array is populated. /// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe() library Oracle { error I(); error OLD(); /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows /// @param last The specified observation to be transformed /// @param blockTimestamp The timestamp of the new observation /// @param tick The active tick at the time of the new observation /// @param liquidity The total in-range liquidity at the time of the new observation /// @return Observation The newly populated observation function transform( Observation memory last, uint32 blockTimestamp, int24 tick, uint128 liquidity ) private pure returns (Observation memory) { unchecked { uint32 delta = blockTimestamp - last.blockTimestamp; return Observation({ blockTimestamp: blockTimestamp, tickCumulative: last.tickCumulative + int56(tick) * int56(uint56(delta)), secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128 + ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)), initialized: true }); } } /// @notice Initialize the oracle array by writing the first slot. Called once for the lifecycle of the observations array /// @param self The stored oracle array /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32 /// @return cardinality The number of populated elements in the oracle array /// @return cardinalityNext The new length of the oracle array, independent of population function initialize( Observation[65535] storage self, uint32 time ) internal returns (uint16 cardinality, uint16 cardinalityNext) { self[0] = Observation({ blockTimestamp: time, tickCumulative: 0, secondsPerLiquidityCumulativeX128: 0, initialized: true }); return (1, 1); } /// @notice Writes an oracle observation to the array /// @dev Writable at most once per block. Index represents the most recently written element. cardinality and index must be tracked externally. /// If the index is at the end of the allowable array length (according to cardinality), and the next cardinality /// is greater than the current one, cardinality may be increased. This restriction is created to preserve ordering. /// @param self The stored oracle array /// @param index The index of the observation that was most recently written to the observations array /// @param blockTimestamp The timestamp of the new observation /// @param tick The active tick at the time of the new observation /// @param liquidity The total in-range liquidity at the time of the new observation /// @param cardinality The number of populated elements in the oracle array /// @param cardinalityNext The new length of the oracle array, independent of population /// @return indexUpdated The new index of the most recently written element in the oracle array /// @return cardinalityUpdated The new cardinality of the oracle array function write( Observation[65535] storage self, uint16 index, uint32 blockTimestamp, int24 tick, uint128 liquidity, uint16 cardinality, uint16 cardinalityNext ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) { unchecked { Observation memory last = self[index]; /// @dev early return if we've already written an observation this block if (last.blockTimestamp == blockTimestamp) return (index, cardinality); /// @dev if the conditions are right, we can bump the cardinality if (cardinalityNext > cardinality && index == (cardinality - 1)) { cardinalityUpdated = cardinalityNext; } else { cardinalityUpdated = cardinality; } indexUpdated = (index + 1) % cardinalityUpdated; self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity); } } /// @notice Prepares the oracle array to store up to `next` observations /// @param self The stored oracle array /// @param current The current next cardinality of the oracle array /// @param next The proposed next cardinality which will be populated in the oracle array /// @return next The next cardinality which will be populated in the oracle array function grow(Observation[65535] storage self, uint16 current, uint16 next) internal returns (uint16) { unchecked { if (current <= 0) revert I(); /// @dev no-op if the passed next value isn't greater than the current next value if (next <= current) return current; /// @dev store in each slot to prevent fresh SSTOREs in swaps /// @dev this data will not be used because the initialized boolean is still false for (uint16 i = current; i < next; i++) self[i].blockTimestamp = 1; return next; } } /// @notice comparator for 32-bit timestamps /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to time /// @param time A timestamp truncated to 32 bits /// @param a A comparison timestamp from which to determine the relative position of `time` /// @param b From which to determine the relative position of `time` /// @return Whether `a` is chronologically <= `b` function lte(uint32 time, uint32 a, uint32 b) private pure returns (bool) { unchecked { /// @dev if there hasn't been overflow, no need to adjust if (a <= time && b <= time) return a <= b; uint256 aAdjusted = a > time ? a : a + 2 ** 32; uint256 bAdjusted = b > time ? b : b + 2 ** 32; return aAdjusted <= bAdjusted; } } /// @notice Fetches the observations beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied. /// The result may be the same observation, or adjacent observations. /// @dev The answer must be contained in the array, used when the target is located within the stored observation /// boundaries: older than the most recent observation and younger, or the same age as, the oldest observation /// @param self The stored oracle array /// @param time The current block.timestamp /// @param target The timestamp at which the reserved observation should be for /// @param index The index of the observation that was most recently written to the observations array /// @param cardinality The number of populated elements in the oracle array /// @return beforeOrAt The observation recorded before, or at, the target /// @return atOrAfter The observation recorded at, or after, the target function binarySearch( Observation[65535] storage self, uint32 time, uint32 target, uint16 index, uint16 cardinality ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { unchecked { /// @dev oldest observation uint256 l = (index + 1) % cardinality; /// @dev newest observation uint256 r = l + cardinality - 1; uint256 i; while (true) { i = (l + r) / 2; beforeOrAt = self[i % cardinality]; /// @dev we've landed on an uninitialized tick, keep searching higher (more recently) if (!beforeOrAt.initialized) { l = i + 1; continue; } atOrAfter = self[(i + 1) % cardinality]; bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target); /// @dev check if we've found the answer! if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break; if (!targetAtOrAfter) r = i - 1; else l = i + 1; } } } /// @notice Fetches the observations beforeOrAt and atOrAfter a given target, i.e. where [beforeOrAt, atOrAfter] is satisfied /// @dev Assumes there is at least 1 initialized observation. /// Used by observeSingle() to compute the counterfactual accumulator values as of a given block timestamp. /// @param self The stored oracle array /// @param time The current block.timestamp /// @param target The timestamp at which the reserved observation should be for /// @param tick The active tick at the time of the returned or simulated observation /// @param index The index of the observation that was most recently written to the observations array /// @param liquidity The total pool liquidity at the time of the call /// @param cardinality The number of populated elements in the oracle array /// @return beforeOrAt The observation which occurred at, or before, the given timestamp /// @return atOrAfter The observation which occurred at, or after, the given timestamp function getSurroundingObservations( Observation[65535] storage self, uint32 time, uint32 target, int24 tick, uint16 index, uint128 liquidity, uint16 cardinality ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { unchecked { /// @dev optimistically set before to the newest observation beforeOrAt = self[index]; /// @dev if the target is chronologically at or after the newest observation, we can early return if (lte(time, beforeOrAt.blockTimestamp, target)) { if (beforeOrAt.blockTimestamp == target) { /// @dev if newest observation equals target, we're in the same block, so we can ignore atOrAfter return (beforeOrAt, atOrAfter); } else { /// @dev otherwise, we need to transform return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); } } /// @dev now, set before to the oldest observation beforeOrAt = self[(index + 1) % cardinality]; if (!beforeOrAt.initialized) beforeOrAt = self[0]; /// @dev ensure that the target is chronologically at or after the oldest observation if (!lte(time, beforeOrAt.blockTimestamp, target)) revert OLD(); /// @dev if we've reached this point, we have to binary search return binarySearch(self, time, target, index, cardinality); } } /// @dev Reverts if an observation at or before the desired observation timestamp does not exist. /// 0 may be passed as `secondsAgo' to return the current cumulative values. /// If called with a timestamp falling between two observations, returns the counterfactual accumulator values /// at exactly the timestamp between the two observations. /// @param self The stored oracle array /// @param time The current block timestamp /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an observation /// @param tick The current tick /// @param index The index of the observation that was most recently written to the observations array /// @param liquidity The current in-range pool liquidity /// @param cardinality The number of populated elements in the oracle array /// @return tickCumulative The tick * time elapsed since the pool was first initialized, as of `secondsAgo` /// @return secondsPerLiquidityCumulativeX128 The time elapsed / max(1, liquidity) since the pool was first initialized, as of `secondsAgo` function observeSingle( Observation[65535] storage self, uint32 time, uint32 secondsAgo, int24 tick, uint16 index, uint128 liquidity, uint16 cardinality ) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) { unchecked { if (secondsAgo == 0) { Observation memory last = self[index]; if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity); return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128); } uint32 target = time - secondsAgo; (Observation memory beforeOrAt, Observation memory atOrAfter) = getSurroundingObservations( self, time, target, tick, index, liquidity, cardinality ); if (target == beforeOrAt.blockTimestamp) { /// @dev we're at the left boundary return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); } else if (target == atOrAfter.blockTimestamp) { /// @dev we're at the right boundary return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); } else { /// @dev we're in the middle uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp; uint32 targetDelta = target - beforeOrAt.blockTimestamp; return ( beforeOrAt.tickCumulative + ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int56(uint56(observationTimeDelta))) * int56(uint56(targetDelta)), beforeOrAt.secondsPerLiquidityCumulativeX128 + uint160( (uint256( atOrAfter.secondsPerLiquidityCumulativeX128 - beforeOrAt.secondsPerLiquidityCumulativeX128 ) * targetDelta) / observationTimeDelta ) ); } } } /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` /// @dev Reverts if `secondsAgos` > oldest observation /// @param self The stored oracle array /// @param time The current block.timestamp /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation /// @param tick The current tick /// @param index The index of the observation that was most recently written to the observations array /// @param liquidity The current in-range pool liquidity /// @param cardinality The number of populated elements in the oracle array /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo` /// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo` function observe( Observation[65535] storage self, uint32 time, uint32[] memory secondsAgos, int24 tick, uint16 index, uint128 liquidity, uint16 cardinality ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) { unchecked { if (cardinality <= 0) revert I(); tickCumulatives = new int56[](secondsAgos.length); secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length); for (uint256 i = 0; i < secondsAgos.length; i++) { (tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = observeSingle( self, time, secondsAgos[i], tick, index, liquidity, cardinality ); } } } function newPeriod( Observation[65535] storage self, uint16 index, uint256 period ) external returns (uint160 secondsPerLiquidityCumulativeX128) { Observation memory last = self[index]; PoolStorage.PoolState storage $ = PoolStorage.getStorage(); unchecked { uint32 delta = uint32(period) * 1 weeks - 1 - last.blockTimestamp; secondsPerLiquidityCumulativeX128 = last.secondsPerLiquidityCumulativeX128 + ((uint160(delta) << 128) / ($.liquidity > 0 ? $.liquidity : 1)); self[index] = Observation({ blockTimestamp: uint32(period) * 1 weeks - 1, tickCumulative: last.tickCumulative + int56($.slot0.tick) * int56(uint56(delta)), secondsPerLiquidityCumulativeX128: secondsPerLiquidityCumulativeX128, initialized: last.initialized }); } } struct SnapShot { int56 tickCumulativeLower; int56 tickCumulativeUpper; uint160 secondsPerLiquidityOutsideLowerX128; uint160 secondsPerLiquidityOutsideUpperX128; uint32 secondsOutsideLower; uint32 secondsOutsideUpper; } struct SnapshotCumulativesInsideCache { uint32 time; int56 tickCumulative; uint160 secondsPerLiquidityCumulativeX128; } /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first /// snapshot is taken and the second snapshot is taken. Boosted data is only valid if it's within the same period /// @param tickLower The lower tick of the range /// @param tickUpper The upper tick of the range /// @return tickCumulativeInside The snapshot of the tick accumulator for the range /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range /// @return secondsInside The snapshot of seconds per liquidity for the range function snapshotCumulativesInside( int24 tickLower, int24 tickUpper, uint32 _blockTimestamp ) external view returns (int56 tickCumulativeInside, uint160 secondsPerLiquidityInsideX128, uint32 secondsInside) { PoolStorage.PoolState storage $ = PoolStorage.getStorage(); TickInfo storage lower = $._ticks[tickLower]; TickInfo storage upper = $._ticks[tickUpper]; SnapShot memory snapshot; bool initializedLower; ( snapshot.tickCumulativeLower, snapshot.secondsPerLiquidityOutsideLowerX128, snapshot.secondsOutsideLower, initializedLower ) = ( lower.tickCumulativeOutside, lower.secondsPerLiquidityOutsideX128, lower.secondsOutside, lower.initialized ); require(initializedLower); bool initializedUpper; ( snapshot.tickCumulativeUpper, snapshot.secondsPerLiquidityOutsideUpperX128, snapshot.secondsOutsideUpper, initializedUpper ) = ( upper.tickCumulativeOutside, upper.secondsPerLiquidityOutsideX128, upper.secondsOutside, upper.initialized ); require(initializedUpper); Slot0 memory _slot0 = $.slot0; unchecked { if (_slot0.tick < tickLower) { return ( snapshot.tickCumulativeLower - snapshot.tickCumulativeUpper, snapshot.secondsPerLiquidityOutsideLowerX128 - snapshot.secondsPerLiquidityOutsideUpperX128, snapshot.secondsOutsideLower - snapshot.secondsOutsideUpper ); } else if (_slot0.tick < tickUpper) { SnapshotCumulativesInsideCache memory cache; cache.time = _blockTimestamp; (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observeSingle( $.observations, cache.time, 0, _slot0.tick, _slot0.observationIndex, $.liquidity, _slot0.observationCardinality ); return ( cache.tickCumulative - snapshot.tickCumulativeLower - snapshot.tickCumulativeUpper, cache.secondsPerLiquidityCumulativeX128 - snapshot.secondsPerLiquidityOutsideLowerX128 - snapshot.secondsPerLiquidityOutsideUpperX128, cache.time - snapshot.secondsOutsideLower - snapshot.secondsOutsideUpper ); } else { return ( snapshot.tickCumulativeUpper - snapshot.tickCumulativeLower, snapshot.secondsPerLiquidityOutsideUpperX128 - snapshot.secondsPerLiquidityOutsideLowerX128, snapshot.secondsOutsideUpper - snapshot.secondsOutsideLower ); } } } /// @notice Returns the seconds per liquidity and seconds inside a tick range for a period /// @dev This does not ensure the range is a valid range /// @param period The timestamp of the period /// @param tickLower The lower tick of the range /// @param tickUpper The upper tick of the range /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range function periodCumulativesInside( uint32 period, int24 tickLower, int24 tickUpper, uint32 _blockTimestamp ) external view returns (uint160 secondsPerLiquidityInsideX128) { PoolStorage.PoolState storage $ = PoolStorage.getStorage(); TickInfo storage lower = $._ticks[tickLower]; TickInfo storage upper = $._ticks[tickUpper]; SnapShot memory snapshot; { int24 startTick = $.periods[period].startTick; uint256 previousPeriod = $.periods[period].previousPeriod; snapshot.secondsPerLiquidityOutsideLowerX128 = uint160(lower.periodSecondsPerLiquidityOutsideX128[period]); if (tickLower <= startTick && snapshot.secondsPerLiquidityOutsideLowerX128 == 0) { snapshot.secondsPerLiquidityOutsideLowerX128 = $ .periods[previousPeriod] .endSecondsPerLiquidityPeriodX128; } snapshot.secondsPerLiquidityOutsideUpperX128 = uint160(upper.periodSecondsPerLiquidityOutsideX128[period]); if (tickUpper <= startTick && snapshot.secondsPerLiquidityOutsideUpperX128 == 0) { snapshot.secondsPerLiquidityOutsideUpperX128 = $ .periods[previousPeriod] .endSecondsPerLiquidityPeriodX128; } } int24 lastTick; uint256 currentPeriod = $.lastPeriod; { /// @dev if period is already finalized, use period's last tick, if not, use current tick if (currentPeriod > period) { lastTick = $.periods[period].lastTick; } else { lastTick = $.slot0.tick; } } unchecked { if (lastTick < tickLower) { return snapshot.secondsPerLiquidityOutsideLowerX128 - snapshot.secondsPerLiquidityOutsideUpperX128; } else if (lastTick < tickUpper) { SnapshotCumulativesInsideCache memory cache; /// @dev if period's on-going, observeSingle, if finalized, use endSecondsPerLiquidityPeriodX128 if (currentPeriod <= period) { cache.time = _blockTimestamp; /// @dev limit to the end of period if (cache.time >= currentPeriod * 1 weeks + 1 weeks) { cache.time = uint32(currentPeriod * 1 weeks + 1 weeks - 1); } Slot0 memory _slot0 = $.slot0; (, cache.secondsPerLiquidityCumulativeX128) = observeSingle( $.observations, cache.time, 0, _slot0.tick, _slot0.observationIndex, $.liquidity, _slot0.observationCardinality ); } else { cache.secondsPerLiquidityCumulativeX128 = $.periods[period].endSecondsPerLiquidityPeriodX128; } return cache.secondsPerLiquidityCumulativeX128 - snapshot.secondsPerLiquidityOutsideLowerX128 - snapshot.secondsPerLiquidityOutsideUpperX128; } else { return snapshot.secondsPerLiquidityOutsideUpperX128 - snapshot.secondsPerLiquidityOutsideLowerX128; } } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.5.0; /// @title Safe casting methods /// @notice Contains methods for safely casting between types library SafeCast { /// @notice Cast a uint256 to a uint160, revert on overflow /// @param y The uint256 to be downcasted /// @return z The downcasted integer, now type uint160 function toUint160(uint256 y) internal pure returns (uint160 z) { require((z = uint160(y)) == y); } /// @notice Cast a int256 to a int128, revert on overflow or underflow /// @param y The int256 to be downcasted /// @return z The downcasted integer, now type int128 function toInt128(int256 y) internal pure returns (int128 z) { require((z = int128(y)) == y); } /// @notice Cast a uint256 to a int256, revert on overflow /// @param y The uint256 to be casted /// @return z The casted integer, now type int256 function toInt256(uint256 y) internal pure returns (int256 z) { require(y < 2 ** 255); z = int256(y); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; import {SafeCast} from './SafeCast.sol'; import {TickMath} from './TickMath.sol'; import {TickInfo} from './PoolStorage.sol'; /// @title Tick /// @notice Contains functions for managing tick processes and relevant calculations library Tick { error LO(); using SafeCast for int256; /// @notice Derives max liquidity per tick from given tick spacing /// @dev Executed within the pool constructor /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` /// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ... /// @return The max liquidity per tick function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) { unchecked { int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; return type(uint128).max / numTicks; } } /// @notice Retrieves fee growth data /// @param self The mapping containing all tick information for initialized ticks /// @param tickLower The lower tick boundary of the position /// @param tickUpper The upper tick boundary of the position /// @param tickCurrent The current tick /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 /// @return feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries /// @return feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries function getFeeGrowthInside( mapping(int24 => TickInfo) storage self, int24 tickLower, int24 tickUpper, int24 tickCurrent, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128 ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { unchecked { TickInfo storage lower = self[tickLower]; TickInfo storage upper = self[tickUpper]; /// @dev calculate fee growth below uint256 feeGrowthBelow0X128; uint256 feeGrowthBelow1X128; if (tickCurrent >= tickLower) { feeGrowthBelow0X128 = lower.feeGrowthOutside0X128; feeGrowthBelow1X128 = lower.feeGrowthOutside1X128; } else { feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128; feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128; } /// @dev calculate fee growth above uint256 feeGrowthAbove0X128; uint256 feeGrowthAbove1X128; if (tickCurrent < tickUpper) { feeGrowthAbove0X128 = upper.feeGrowthOutside0X128; feeGrowthAbove1X128 = upper.feeGrowthOutside1X128; } else { feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128; feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128; } feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128; feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128; } } /// @notice Updates a tick and returns true if the tick was flipped from initialized to uninitialized, or vice versa /// @param self The mapping containing all tick information for initialized ticks /// @param tick The tick that will be updated /// @param tickCurrent The current tick /// @param liquidityDelta A new amount of liquidity to be added (subtracted) when tick is crossed from left to right (right to left) /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 /// @param secondsPerLiquidityCumulativeX128 The all-time seconds per max(1, liquidity) of the pool /// @param tickCumulative The tick * time elapsed since the pool was first initialized /// @param time The current block timestamp cast to a uint32 /// @param upper true for updating a position's upper tick, or false for updating a position's lower tick /// @param maxLiquidity The maximum liquidity allocation for a single tick /// @return flipped Whether the tick was flipped from initialized to uninitialized, or vice versa function update( mapping(int24 => TickInfo) storage self, int24 tick, int24 tickCurrent, int128 liquidityDelta, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128, uint160 secondsPerLiquidityCumulativeX128, int56 tickCumulative, uint32 time, bool upper, uint128 maxLiquidity ) internal returns (bool flipped) { TickInfo storage info = self[tick]; uint128 liquidityGrossBefore = info.liquidityGross; uint128 liquidityGrossAfter = liquidityDelta < 0 ? liquidityGrossBefore - uint128(-liquidityDelta) : liquidityGrossBefore + uint128(liquidityDelta); if (liquidityGrossAfter > maxLiquidity) revert LO(); flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); if (liquidityGrossBefore == 0) { /// @dev by convention, we assume that all growth before a tick was initialized happened _below_ the tick if (tick <= tickCurrent) { info.feeGrowthOutside0X128 = feeGrowthGlobal0X128; info.feeGrowthOutside1X128 = feeGrowthGlobal1X128; info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128; info.tickCumulativeOutside = tickCumulative; info.secondsOutside = time; } info.initialized = true; } info.liquidityGross = liquidityGrossAfter; /// @dev when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) info.liquidityNet = upper ? info.liquidityNet - liquidityDelta : info.liquidityNet + liquidityDelta; } /// @notice Clears tick data /// @param self The mapping containing all initialized tick information for initialized ticks /// @param tick The tick that will be cleared /// @param period The period the tick was cleared on function clear(mapping(int24 => TickInfo) storage self, int24 tick, uint256 period) internal { delete self[tick].periodSecondsPerLiquidityOutsideX128[period]; delete self[tick]; } /// @notice Transitions to next tick as needed by price movement /// @param self The mapping containing all tick information for initialized ticks /// @param tick The destination tick of the transition /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 /// @param secondsPerLiquidityCumulativeX128 The current seconds per liquidity /// @param tickCumulative The tick * time elapsed since the pool was first initialized /// @param time The current block.timestamp /// @return liquidityNet The amount of liquidity added (subtracted) when tick is crossed from left to right (right to left) function cross( mapping(int24 => TickInfo) storage self, int24 tick, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128, uint160 secondsPerLiquidityCumulativeX128, int56 tickCumulative, uint32 time, uint256 endSecondsPerLiquidityPeriodX128, int24 periodStartTick ) internal returns (int128 liquidityNet) { unchecked { TickInfo storage info = self[tick]; uint256 period = time / 1 weeks; info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128; info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128; info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128 - info.secondsPerLiquidityOutsideX128; info.tickCumulativeOutside = tickCumulative - info.tickCumulativeOutside; info.secondsOutside = time - info.secondsOutside; liquidityNet = info.liquidityNet; uint256 periodSecondsPerLiquidityOutsideX128; uint256 periodSecondsPerLiquidityOutsideBeforeX128 = info.periodSecondsPerLiquidityOutsideX128[period]; if (tick <= periodStartTick && periodSecondsPerLiquidityOutsideBeforeX128 == 0) { periodSecondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128 - /* periodSecondsPerLiquidityOutsideBeforeX128 - */ endSecondsPerLiquidityPeriodX128; } else { periodSecondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128 - periodSecondsPerLiquidityOutsideBeforeX128; } info.periodSecondsPerLiquidityOutsideX128[period] = periodSecondsPerLiquidityOutsideX128; } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; import {BitMath} from './BitMath.sol'; /// @title Packed tick initialized state library /// @notice Stores a packed mapping of tick index to its initialized state /// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. library TickBitmap { /// @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)); } } /// @notice Flips the initialized state for a given tick from false to true, or vice versa /// @param self The mapping in which to flip the tick /// @param tick The tick to flip /// @param tickSpacing The spacing between usable ticks function flipTick(mapping(int16 => uint256) storage self, int24 tick, int24 tickSpacing) internal { unchecked { /// @dev ensure that the tick is spaced require(tick % tickSpacing == 0); (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); uint256 mask = 1 << bitPos; self[wordPos] ^= mask; } } /// @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 self 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 nextInitializedTickWithinOneWord( mapping(int16 => uint256) storage self, 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--; /// @dev round towards negative infinity if (lte) { (int16 wordPos, uint8 bitPos) = position(compressed); /// @dev all the 1s at or to the right of the current bitPos uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); uint256 masked = self[wordPos] & mask; /// @dev if there are no initialized ticks to the right of or at the current tick, return rightmost in the word initialized = masked != 0; /// @dev 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 { /// @dev start from the word of the next tick, since the current tick state doesn't matter (int16 wordPos, uint8 bitPos) = position(compressed + 1); /// @dev all the 1s at or to the left of the bitPos uint256 mask = ~((1 << bitPos) - 1); uint256 masked = self[wordPos] & mask; /// @dev if there are no initialized ticks to the left of the current tick, return leftmost in the word initialized = masked != 0; /// @dev 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; } } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; struct Slot0 { /// @dev the current price uint160 sqrtPriceX96; /// @dev the current tick int24 tick; /// @dev the most-recently updated index of the observations array uint16 observationIndex; /// @dev the current maximum number of observations that are being stored uint16 observationCardinality; /// @dev the next maximum number of observations to store, triggered in observations.write uint16 observationCardinalityNext; /// @dev the current protocol fee as a percentage of the swap fee taken on withdrawal /// @dev represented as an integer denominator (1/x)% uint8 feeProtocol; /// @dev whether the pool is locked bool unlocked; } struct Observation { /// @dev the block timestamp of the observation uint32 blockTimestamp; /// @dev the tick accumulator, i.e. tick * time elapsed since the pool was first initialized int56 tickCumulative; /// @dev the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized uint160 secondsPerLiquidityCumulativeX128; /// @dev whether or not the observation is initialized bool initialized; } struct RewardInfo { /// @dev used to account for changes in the deposit amount int256 secondsDebtX96; /// @dev used to check if starting seconds have already been written bool initialized; /// @dev used to account for changes in secondsPerLiquidity int160 secondsPerLiquidityPeriodStartX128; } /// @dev info stored for each user's position struct PositionInfo { /// @dev the amount of liquidity owned by this position uint128 liquidity; /// @dev fee growth per unit of liquidity as of the last update to liquidity or fees owed uint256 feeGrowthInside0LastX128; uint256 feeGrowthInside1LastX128; /// @dev the fees owed to the position owner in token0/token1 uint128 tokensOwed0; uint128 tokensOwed1; mapping(uint256 => RewardInfo) periodRewardInfo; } /// @dev info stored for each initialized individual tick struct TickInfo { /// @dev the total position liquidity that references this tick uint128 liquidityGross; /// @dev amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), int128 liquidityNet; /// @dev fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) /// @dev only has relative meaning, not absolute — the value depends on when the tick is initialized uint256 feeGrowthOutside0X128; uint256 feeGrowthOutside1X128; /// @dev the cumulative tick value on the other side of the tick int56 tickCumulativeOutside; /// @dev the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) /// @dev only has relative meaning, not absolute — the value depends on when the tick is initialized uint160 secondsPerLiquidityOutsideX128; /// @dev the seconds spent on the other side of the tick (relative to the current tick) /// @dev only has relative meaning, not absolute — the value depends on when the tick is initialized uint32 secondsOutside; /// @dev true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 /// @dev these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks bool initialized; /// @dev secondsPerLiquidityOutsideX128 separated into periods, placed here to preserve struct slots mapping(uint256 => uint256) periodSecondsPerLiquidityOutsideX128; } /// @dev info stored for each period struct PeriodInfo { uint32 previousPeriod; int24 startTick; int24 lastTick; uint160 endSecondsPerLiquidityPeriodX128; } /// @dev accumulated protocol fees in token0/token1 units struct ProtocolFees { uint128 token0; uint128 token1; } /// @dev Position period and liquidity struct PositionCheckpoint { uint256 period; uint256 liquidity; } library PoolStorage { /// @dev keccak256(abi.encode(uint256(keccak256("pool.storage")) - 1)) & ~bytes32(uint256(0xff)); bytes32 public constant POOL_STORAGE_LOCATION = 0xf047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2800; /// @custom꞉storage‑location erc7201꞉pool.storage struct PoolState { Slot0 slot0; uint24 fee; uint256 feeGrowthGlobal0X128; uint256 feeGrowthGlobal1X128; ProtocolFees protocolFees; uint128 liquidity; mapping(int24 => TickInfo) _ticks; mapping(int16 => uint256) tickBitmap; mapping(bytes32 => PositionInfo) positions; Observation[65535] observations; mapping(uint256 => PeriodInfo) periods; uint256 lastPeriod; mapping(bytes32 => PositionCheckpoint[]) positionCheckpoints; bool initialized; address nfpManager; } /// @dev Return state storage struct for reading and writing function getStorage() internal pure returns (PoolState storage $) { assembly { $.slot := POOL_STORAGE_LOCATION } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; /// @title Math library for computing sqrt prices from ticks and vice versa /// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports /// prices between 2**-128 and 2**128 library TickMath { error T(); error R(); /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 int24 internal constant MIN_TICK = -887272; /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 int24 internal constant MAX_TICK = -MIN_TICK; /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) uint160 internal constant MIN_SQRT_RATIO = 4295128739; /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; /// @notice Calculates sqrt(1.0001^tick) * 2^96 /// @dev Throws if |tick| > max tick /// @param tick The input tick for the above formula /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) /// at the given tick function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { unchecked { uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); if (absTick > uint256(int256(MAX_TICK))) revert T(); uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; if (tick > 0) ratio = type(uint256).max / ratio; /// @dev this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. /// @dev we then downcast because we know the result always fits within 160 bits due to our tick input constraint /// @dev we round up in the division so getTickAtSqrtRatio of the output price is always consistent sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); } } /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may /// ever return. /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { unchecked { /// @dev second inequality must be < because the price can never reach the price at the max tick if (!(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO)) revert R(); uint256 ratio = uint256(sqrtPriceX96) << 32; uint256 r = ratio; uint256 msb = 0; assembly { let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(5, gt(r, 0xFFFFFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(4, gt(r, 0xFFFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(3, gt(r, 0xFF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(2, gt(r, 0xF)) msb := or(msb, f) r := shr(f, r) } assembly { let f := shl(1, gt(r, 0x3)) msb := or(msb, f) r := shr(f, r) } assembly { let f := gt(r, 0x1) msb := or(msb, f) } if (msb >= 128) r = ratio >> (msb - 127); else r = ratio << (127 - msb); int256 log_2 = (int256(msb) - 128) << 64; assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(63, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(62, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(61, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(60, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(59, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(58, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(57, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(56, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(55, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(54, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(53, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(52, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(51, f)) r := shr(f, r) } assembly { r := shr(127, mul(r, r)) let f := shr(128, r) log_2 := or(log_2, shl(50, f)) } int256 log_sqrt10001 = log_2 * 255738958999603826347141; /// @dev 128.128 number int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.26; /// @title BitMath /// @dev This library provides functionality for computing bit properties of an unsigned integer library BitMath { /// @notice Returns the index of the most significant bit of the number, /// where the least significant bit is at index 0 and the most significant bit is at index 255 /// @dev The function satisfies the property: /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) /// @param x the value for which to compute the most significant bit, must be greater than 0 /// @return r the index of the most significant bit function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { require(x > 0); unchecked { if (x >= 0x100000000000000000000000000000000) { x >>= 128; r += 128; } if (x >= 0x10000000000000000) { x >>= 64; r += 64; } if (x >= 0x100000000) { x >>= 32; r += 32; } if (x >= 0x10000) { x >>= 16; r += 16; } if (x >= 0x100) { x >>= 8; r += 8; } if (x >= 0x10) { x >>= 4; r += 4; } if (x >= 0x4) { x >>= 2; r += 2; } if (x >= 0x2) r += 1; } } /// @notice Returns the index of the least significant bit of the number, /// where the least significant bit is at index 0 and the most significant bit is at index 255 /// @dev The function satisfies the property: /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) /// @param x the value for which to compute the least significant bit, must be greater than 0 /// @return r the index of the least significant bit function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { require(x > 0); unchecked { r = 255; if (x & type(uint128).max > 0) { r -= 128; } else { x >>= 128; } if (x & type(uint64).max > 0) { r -= 64; } else { x >>= 64; } if (x & type(uint32).max > 0) { r -= 32; } else { x >>= 32; } if (x & type(uint16).max > 0) { r -= 16; } else { x >>= 16; } if (x & type(uint8).max > 0) { r -= 8; } else { x >>= 8; } if (x & 0xf > 0) { r -= 4; } else { x >>= 4; } if (x & 0x3 > 0) { r -= 2; } else { x >>= 2; } if (x & 0x1 > 0) r -= 1; } } }
{ "remappings": [ "@openzeppelin-contracts-upgradeable-5.1.0/=dependencies/@openzeppelin-contracts-upgradeable-5.1.0/", "@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.1.0/", "forge-std/=dependencies/forge-std-1.9.4/src/", "permit2/=lib/permit2/", "@openzeppelin-3.4.2/=node_modules/@openzeppelin-3.4.2/", "@openzeppelin-contracts-5.1.0/=dependencies/@openzeppelin-contracts-5.1.0/", "@uniswap/=node_modules/@uniswap/", "base64-sol/=node_modules/base64-sol/", "eth-gas-reporter/=node_modules/eth-gas-reporter/", "forge-std-1.9.4/=dependencies/forge-std-1.9.4/src/", "hardhat/=node_modules/hardhat/", "solmate/=node_modules/solmate/" ], "optimizer": { "enabled": true, "runs": 100 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "cancun", "viaIR": true, "libraries": { "contracts/CL/core/libraries/Oracle.sol": { "Oracle": "0x739CB2b30fC47c19262aB023eaE966A15c2F9A28" }, "contracts/CL/core/libraries/Position.sol": { "Position": "0x0bE5821b66b82676a550fdfF0b686b3e2D85Ac62" }, "contracts/CL/core/libraries/ProtocolActions.sol": { "ProtocolActions": "0x2D26aa011df96ab6Ff66f7cB8EF1ed13Afb8A43B" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"name":"FTR","type":"error"},{"inputs":[],"name":"LO","type":"error"},{"inputs":[],"name":"NP","type":"error"},{"inputs":[],"name":"OLD","type":"error"},{"inputs":[{"components":[{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint32","name":"_blockTimestamp","type":"uint32"}],"internalType":"struct Position.PositionPeriodSecondsInRangeParams","name":"params","type":"tuple"}],"name":"positionPeriodSecondsInRange","outputs":[{"internalType":"uint256","name":"periodSecondsInsideX96","type":"uint256"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
60808060405234601b576115a290816100208239308160350152f35b5f80fdfe60a0806040526004361015610012575f80fd5b5f3560e01c908163d2e6311b14610cbe575063fc32287914610032575f80fd5b307f0000000000000000000000000000000000000000000000000000000000000000146105b1576101203660031901126105b157604051610120810181811067ffffffffffffffff82111761061e576040526004356001600160a01b03811681036105b157815260243560208201526044358060020b81036105b15760408201526100bb610d9a565b606082015260843580600f0b81036105b157608082015260a4358060020b81036105b15760a082015260c4359063ffffffff8216918281036105b15760c082015260e4358060020b81036105b15760e0820152610104356001600160801b03811681036105b15761010082015261015160018060a01b038251166020830151604084015160020b90606085015160020b92611076565b91825f525f51602061150d5f395f51905f5260205260405f20917ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2802547ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280354907ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2800546080525f905f926080850151600f0b6108d6575b604085015160020b9763ffffffff606087015160020b6102058b6113c0565b9061020f816113c0565b9b60805160a01c60020b12155f146108c25760026001830154920154905b60805160a01c60020b12156108ae57600260018d01549c0154905b602062093a808c6102898d604081015160020b908960c0606083015160020b92015116916040519a8b96879663feb0d71760e01b8852041660048601610df6565b038173739cb2b30fc47c19262ab023eae966a15c2f9a285af49586156108a3578c945f97610872575b5063ffffffff62093a808d04165f52600485016020528a8c60ff600160405f20015416158015610860575b610775575b50608001519d8e9460018060801b038754169686600f0b1596875f1461072a575050861561071b578e91600191889b5b838060801b0361032b8b868801548686860303036111a8565b1699610347858060801b039160028801548b8b8b0303036111a8565b169815610701575b0303910155030360028b015581158015906106f8575b6106b9575b5050805f525f51602061154d5f395f51905f5260205260405f205480158015610677575b1561063257505f525f51602061154d5f395f51905f5260205260405f20604051906040820182811067ffffffffffffffff82111761061e5760405262093a80880463ffffffff1682526001600160801b03909316602082019081528354909390600160401b81101561061e5761040991600182018155610daa565b91909161060b5761045393600191518355519101555b60018060a01b031660130b63ffffffff62093a808704165f5260048701602052600160405f20015460081c60130b90610e22565b5f8160130b12610604575b5f87600f0b1396875f146105c6576001600160801b031690640100000000906001600160a01b031661049081846111f8565b92096105b5575b600160ff1b8110156105b157955b156105795763ffffffff62093a808504165f526004850160205260405f20545f8782019788129112908015821691151617610565576020955b63ffffffff62093a808604165f5260048601875260405f20555f6080840151600f0b12610510575b8585604051908152f35b610544575b610521575b8080610506565b606063ffffffff62093a8061053d94041691015160020b61148f565b5f8061051a565b61056063ffffffff62093a80850416604084015160020b61148f565b610515565b634e487b7160e01b5f52601160045260245ffd5b63ffffffff62093a808504165f526004850160205260405f20545f8782039712818812811691881390151617610565576020956104de565b5f80fd5b5f198110156105b157600101610497565b6105f291906001600160801b03906105e090600f0b6113f6565b6001600160a01b0390921691166111f8565b600160ff1b8110156105b157956104a5565b505f61045e565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b92905f525f51602061154d5f395f51905f5260205260405f20925f1981019081116105655761066660019161045395610daa565b5091818060801b031691015561041f565b50815f525f51602061154d5f395f51905f5260205260405f205f198201828111610565576106a491610daa565b505462093a80890463ffffffff16141561038e565b60038a0180546001600160801b03808216949094019093166001600160801b03199384168117608090811c9390930190921b9092161790555f8061036a565b50801515610365565b838060801b038d16848060801b031986541617855561034f565b632aabfc9960e11b5f5260045ffd5b909260019290915f600f82900b121561075f5761074c61075991600f0b6113f6565b848060801b03168a61140d565b9b610312565b61077090848060801b03168a6113dd565b610759565b62093a8063ffffffff9160018060a01b0384511693602081015190604081015160020b8560c0606084015160020b9301511692604051976107b589610d40565b87878704168952602089015260408801526060870152608086015260a085015204165f5260048601602052600160405f2001600160ff1982541617905560018060801b0386541661083a575b515f90815260048601602052604090206001018054610100600160a81b031916600889901b610100600160a81b03161790558a8c6102e2565b61084b61084682610e64565b610e54565b81515f526004870160205260405f2055610801565b5086546001600160801b0316156102dd565b61089591975060203d60201161089c575b61088d8183610d78565b810190610dd7565b955f6102b2565b503d610883565b6040513d5f823e3d90fd5b600260018d015487039c0154850390610248565b60026001830154870392015485039061022d565b9150915061093763ffffffff60c08501511660018060801b037ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2805541661ffff60805160c81c169161ffff60805160b81c169060805160a01c60020b90611247565b929091604085015160020b93608086015160c08701519060018060801b0361010089015116875f525f51602061152d5f395f51905f5260205260405f209260018060801b03845416985f84600f0b125f14610ca5576109aa61099b85600f0b6113f6565b6001600160801b03168b61140d565b925b6001600160801b03841611610bf7576001600160801b038316159915998a141599610c1e575b505082546001600160801b0319166001600160801b03919091161780835560801d600f90810b91900b0160016001607f1b03811360016001607f1b03198212176105655781546001600160801b03908116608092831b6001600160801b0319161790925560608801519088015160c08901516101008a015160029390930b5f8181525f51602061152d5f395f51905f526020526040812080548c9b93989197600f87900b9781169692949391169190871215610c065750610aa4610a95876113f6565b6001600160801b03168261140d565b6001600160801b0316948511610bf7571596878515141597610b64575b505084546001600160801b0319169290921780855560801d600f0b9290920391505060016001607f1b0319811260016001607f1b038213176105655781546001600160801b031660809190911b6001600160801b031916179055938490610b46575b156101e657610b41606086015160020b60e087015160020b90611426565b6101e6565b610b5f604087015160020b60e088015160020b90611426565b610b23565b60805160a01c60020b1215610b97575b5050506003830180546001600160f81b0316600160f81b1790555f808080610ac1565b600186018990556002860188905560038601805466ffffffffffffff929092166001600160f81b031990921660389390931b600160381b600160d81b0316929092171760d89290921b63ffffffff60d81b169190911790555f8080610b74565b6368d2be8f60e01b5f5260045ffd5b610c19906001600160801b0316826113dd565b610aa4565b60805160a01c60020b1215610c4d575b506003830180546001600160f81b0316600160f81b1790555f806109d2565b600184018790556002840186905560038401805466ffffffffffffff8a166001600160f81b0319909116603888901b600160381b600160d81b0316171760d89290921b63ffffffff60d81b169190911790555f610c2e565b610cb86001600160801b0385168b6113dd565b926109ac565b60c03660031901126105b157610cd381610d40565b60043581526024356001600160a01b03811681036105b15760208201526044356040820152610d00610d9a565b60608201526084358060020b81036105b157608082015260a43563ffffffff811681036105b15781610d389160a06020940152610e64565b604051908152f35b60c0810190811067ffffffffffffffff82111761061e57604052565b6080810190811067ffffffffffffffff82111761061e57604052565b90601f8019910116810190811067ffffffffffffffff82111761061e57604052565b606435908160020b82036105b157565b8054821015610dc3575f5260205f209060011b01905f90565b634e487b7160e01b5f52603260045260245ffd5b908160209103126105b157516001600160a01b03811681036105b15790565b929363ffffffff91959460609383608087019816865260020b602086015260020b604085015216910152565b601391820b910b039060016001609f1b0319821260016001609f1b0383131761056557565b9190820391821161056557565b600160ff1b8114610565575f0390565b7ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc2809548151116110675760018060a01b03602082015116604082015190610ebd6060840191825160020b6080860194855160020b92611076565b92835f525f51602061154d5f395f51905f5260205260405f20610ee18251826110c1565b9190911561105d57600191610ef591610daa565b50015491845f525f51602061150d5f395f51905f52602052600460405f200182515f526020526020600160405f20015460081c60130b9463ffffffff845116925160020b905160020b92610f6863ffffffff60a087015116604051958694859463feb0d71760e01b865260048601610df6565b038173739cb2b30fc47c19262ab023eae966a15c2f9a285af49384156108a357610fdf94610fa9925f9161103e575b506001600160a01b031660130b610e22565b935f525f51602061150d5f395f51905f52602052600460405f200190515f5260205260405f20549260018060a01b0316906111f8565b905f8112808015611035575b1561102b571561101d57610ffe90610e54565b8101809111610565575b905b61127560671b821161101857565b5f9150565b61102691610e47565b611008565b5050505f9061100a565b50818311610feb565b611057915060203d60201161089c5761088d8183610d78565b5f610f97565b5050505050505f90565b634789d90160e01b5f5260045ffd5b9290916040519260208401946bffffffffffffffffffffffff199060601b168552603484015260e81b605483015260e81b6057820152603a81526110bb605a82610d78565b51902090565b919091805490811561119e57805415610dc357805f528360205f20541161119e575f19820191821161056557816110f88183610daa565b50549480861161110a575b5050509190565b9194505f92505b828111611130575050806111259193610daa565b5054915f8080611103565b61114a611141848397949597610e47565b60011c82610e47565b926111558484610daa565b50549581870361116757505050509190565b90809396949294105f1461117c575091611111565b92505f1981019081111561111157634e487b7160e01b5f52601160045260245ffd5b505090505f905f90565b5f91905f1982820991808202938480851094039380850394146111ee57600160801b8410156111eb5750600160801b910990828211900360801b910360801c1790565b80fd5b5050505060801c90565b5f915f19818309918181029384808510940393808503941461123d578364010000000011156111eb575090640100000000910990828211900360e01b910360201c1790565b5050505060201c90565b92919094935061ffff811015610dc3577ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280901906040519161128783610d5c565b5463ffffffff811683528060201c60060b602084015260018060a01b038160581c16604084015260f81c15156060830152819463ffffffff83511663ffffffff8516036112ec575b50505050602082015160060b91604060018060a01b039101511690565b9091929394506112fa6114e8565b508251602084015160409094015163ffffffff9091168503936001600160a01b039091169260069190910b906001600160801b038116156113b8575b6001600160801b03169182156113a45763ffffffff6040519661135888610d5c565b16865263ffffffff851660060b9060020b0260060b0160060b602085015260018060a01b039263ffffffff60801b9060801b16040116604082015260016060820152905f8080806112cf565b634e487b7160e01b5f52601260045260245ffd5b506001611336565b60020b5f525f51602061152d5f395f51905f5260205260405f2090565b6001600160801b03918216908216019190821161056557565b600f0b60016001607f1b03198114610565575f0390565b6001600160801b03918216908216039190821161056557565b9060020b9081156113a457818160020b0760020b6105b15781156113a45760020b0560020b61010081079060081d60010b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2807602052600160ff60405f2092161b8154189055565b60049060020b91825f525f51602061152d5f395f51905f5260205260405f20905f52016020525f60408120555f525f51602061152d5f395f51905f526020525f6003604082208281558260018201558260028201550155565b604051906114f582610d5c565b5f606083828152826020820152826040820152015256fef047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2808f047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2806f047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280aa2646970667358221220b697fe6f46f97272b9f4c88e07a910b1cc18b9adda1162f276c62dac4b34129d64736f6c634300081c0033
Deployed Bytecode
0x60a0806040526004361015610012575f80fd5b5f3560e01c908163d2e6311b14610cbe575063fc32287914610032575f80fd5b307f0000000000000000000000000be5821b66b82676a550fdff0b686b3e2d85ac62146105b1576101203660031901126105b157604051610120810181811067ffffffffffffffff82111761061e576040526004356001600160a01b03811681036105b157815260243560208201526044358060020b81036105b15760408201526100bb610d9a565b606082015260843580600f0b81036105b157608082015260a4358060020b81036105b15760a082015260c4359063ffffffff8216918281036105b15760c082015260e4358060020b81036105b15760e0820152610104356001600160801b03811681036105b15761010082015261015160018060a01b038251166020830151604084015160020b90606085015160020b92611076565b91825f525f51602061150d5f395f51905f5260205260405f20917ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2802547ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280354907ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2800546080525f905f926080850151600f0b6108d6575b604085015160020b9763ffffffff606087015160020b6102058b6113c0565b9061020f816113c0565b9b60805160a01c60020b12155f146108c25760026001830154920154905b60805160a01c60020b12156108ae57600260018d01549c0154905b602062093a808c6102898d604081015160020b908960c0606083015160020b92015116916040519a8b96879663feb0d71760e01b8852041660048601610df6565b038173739cb2b30fc47c19262ab023eae966a15c2f9a285af49586156108a3578c945f97610872575b5063ffffffff62093a808d04165f52600485016020528a8c60ff600160405f20015416158015610860575b610775575b50608001519d8e9460018060801b038754169686600f0b1596875f1461072a575050861561071b578e91600191889b5b838060801b0361032b8b868801548686860303036111a8565b1699610347858060801b039160028801548b8b8b0303036111a8565b169815610701575b0303910155030360028b015581158015906106f8575b6106b9575b5050805f525f51602061154d5f395f51905f5260205260405f205480158015610677575b1561063257505f525f51602061154d5f395f51905f5260205260405f20604051906040820182811067ffffffffffffffff82111761061e5760405262093a80880463ffffffff1682526001600160801b03909316602082019081528354909390600160401b81101561061e5761040991600182018155610daa565b91909161060b5761045393600191518355519101555b60018060a01b031660130b63ffffffff62093a808704165f5260048701602052600160405f20015460081c60130b90610e22565b5f8160130b12610604575b5f87600f0b1396875f146105c6576001600160801b031690640100000000906001600160a01b031661049081846111f8565b92096105b5575b600160ff1b8110156105b157955b156105795763ffffffff62093a808504165f526004850160205260405f20545f8782019788129112908015821691151617610565576020955b63ffffffff62093a808604165f5260048601875260405f20555f6080840151600f0b12610510575b8585604051908152f35b610544575b610521575b8080610506565b606063ffffffff62093a8061053d94041691015160020b61148f565b5f8061051a565b61056063ffffffff62093a80850416604084015160020b61148f565b610515565b634e487b7160e01b5f52601160045260245ffd5b63ffffffff62093a808504165f526004850160205260405f20545f8782039712818812811691881390151617610565576020956104de565b5f80fd5b5f198110156105b157600101610497565b6105f291906001600160801b03906105e090600f0b6113f6565b6001600160a01b0390921691166111f8565b600160ff1b8110156105b157956104a5565b505f61045e565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b92905f525f51602061154d5f395f51905f5260205260405f20925f1981019081116105655761066660019161045395610daa565b5091818060801b031691015561041f565b50815f525f51602061154d5f395f51905f5260205260405f205f198201828111610565576106a491610daa565b505462093a80890463ffffffff16141561038e565b60038a0180546001600160801b03808216949094019093166001600160801b03199384168117608090811c9390930190921b9092161790555f8061036a565b50801515610365565b838060801b038d16848060801b031986541617855561034f565b632aabfc9960e11b5f5260045ffd5b909260019290915f600f82900b121561075f5761074c61075991600f0b6113f6565b848060801b03168a61140d565b9b610312565b61077090848060801b03168a6113dd565b610759565b62093a8063ffffffff9160018060a01b0384511693602081015190604081015160020b8560c0606084015160020b9301511692604051976107b589610d40565b87878704168952602089015260408801526060870152608086015260a085015204165f5260048601602052600160405f2001600160ff1982541617905560018060801b0386541661083a575b515f90815260048601602052604090206001018054610100600160a81b031916600889901b610100600160a81b03161790558a8c6102e2565b61084b61084682610e64565b610e54565b81515f526004870160205260405f2055610801565b5086546001600160801b0316156102dd565b61089591975060203d60201161089c575b61088d8183610d78565b810190610dd7565b955f6102b2565b503d610883565b6040513d5f823e3d90fd5b600260018d015487039c0154850390610248565b60026001830154870392015485039061022d565b9150915061093763ffffffff60c08501511660018060801b037ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2805541661ffff60805160c81c169161ffff60805160b81c169060805160a01c60020b90611247565b929091604085015160020b93608086015160c08701519060018060801b0361010089015116875f525f51602061152d5f395f51905f5260205260405f209260018060801b03845416985f84600f0b125f14610ca5576109aa61099b85600f0b6113f6565b6001600160801b03168b61140d565b925b6001600160801b03841611610bf7576001600160801b038316159915998a141599610c1e575b505082546001600160801b0319166001600160801b03919091161780835560801d600f90810b91900b0160016001607f1b03811360016001607f1b03198212176105655781546001600160801b03908116608092831b6001600160801b0319161790925560608801519088015160c08901516101008a015160029390930b5f8181525f51602061152d5f395f51905f526020526040812080548c9b93989197600f87900b9781169692949391169190871215610c065750610aa4610a95876113f6565b6001600160801b03168261140d565b6001600160801b0316948511610bf7571596878515141597610b64575b505084546001600160801b0319169290921780855560801d600f0b9290920391505060016001607f1b0319811260016001607f1b038213176105655781546001600160801b031660809190911b6001600160801b031916179055938490610b46575b156101e657610b41606086015160020b60e087015160020b90611426565b6101e6565b610b5f604087015160020b60e088015160020b90611426565b610b23565b60805160a01c60020b1215610b97575b5050506003830180546001600160f81b0316600160f81b1790555f808080610ac1565b600186018990556002860188905560038601805466ffffffffffffff929092166001600160f81b031990921660389390931b600160381b600160d81b0316929092171760d89290921b63ffffffff60d81b169190911790555f8080610b74565b6368d2be8f60e01b5f5260045ffd5b610c19906001600160801b0316826113dd565b610aa4565b60805160a01c60020b1215610c4d575b506003830180546001600160f81b0316600160f81b1790555f806109d2565b600184018790556002840186905560038401805466ffffffffffffff8a166001600160f81b0319909116603888901b600160381b600160d81b0316171760d89290921b63ffffffff60d81b169190911790555f610c2e565b610cb86001600160801b0385168b6113dd565b926109ac565b60c03660031901126105b157610cd381610d40565b60043581526024356001600160a01b03811681036105b15760208201526044356040820152610d00610d9a565b60608201526084358060020b81036105b157608082015260a43563ffffffff811681036105b15781610d389160a06020940152610e64565b604051908152f35b60c0810190811067ffffffffffffffff82111761061e57604052565b6080810190811067ffffffffffffffff82111761061e57604052565b90601f8019910116810190811067ffffffffffffffff82111761061e57604052565b606435908160020b82036105b157565b8054821015610dc3575f5260205f209060011b01905f90565b634e487b7160e01b5f52603260045260245ffd5b908160209103126105b157516001600160a01b03811681036105b15790565b929363ffffffff91959460609383608087019816865260020b602086015260020b604085015216910152565b601391820b910b039060016001609f1b0319821260016001609f1b0383131761056557565b9190820391821161056557565b600160ff1b8114610565575f0390565b7ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc2809548151116110675760018060a01b03602082015116604082015190610ebd6060840191825160020b6080860194855160020b92611076565b92835f525f51602061154d5f395f51905f5260205260405f20610ee18251826110c1565b9190911561105d57600191610ef591610daa565b50015491845f525f51602061150d5f395f51905f52602052600460405f200182515f526020526020600160405f20015460081c60130b9463ffffffff845116925160020b905160020b92610f6863ffffffff60a087015116604051958694859463feb0d71760e01b865260048601610df6565b038173739cb2b30fc47c19262ab023eae966a15c2f9a285af49384156108a357610fdf94610fa9925f9161103e575b506001600160a01b031660130b610e22565b935f525f51602061150d5f395f51905f52602052600460405f200190515f5260205260405f20549260018060a01b0316906111f8565b905f8112808015611035575b1561102b571561101d57610ffe90610e54565b8101809111610565575b905b61127560671b821161101857565b5f9150565b61102691610e47565b611008565b5050505f9061100a565b50818311610feb565b611057915060203d60201161089c5761088d8183610d78565b5f610f97565b5050505050505f90565b634789d90160e01b5f5260045ffd5b9290916040519260208401946bffffffffffffffffffffffff199060601b168552603484015260e81b605483015260e81b6057820152603a81526110bb605a82610d78565b51902090565b919091805490811561119e57805415610dc357805f528360205f20541161119e575f19820191821161056557816110f88183610daa565b50549480861161110a575b5050509190565b9194505f92505b828111611130575050806111259193610daa565b5054915f8080611103565b61114a611141848397949597610e47565b60011c82610e47565b926111558484610daa565b50549581870361116757505050509190565b90809396949294105f1461117c575091611111565b92505f1981019081111561111157634e487b7160e01b5f52601160045260245ffd5b505090505f905f90565b5f91905f1982820991808202938480851094039380850394146111ee57600160801b8410156111eb5750600160801b910990828211900360801b910360801c1790565b80fd5b5050505060801c90565b5f915f19818309918181029384808510940393808503941461123d578364010000000011156111eb575090640100000000910990828211900360e01b910360201c1790565b5050505060201c90565b92919094935061ffff811015610dc3577ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280901906040519161128783610d5c565b5463ffffffff811683528060201c60060b602084015260018060a01b038160581c16604084015260f81c15156060830152819463ffffffff83511663ffffffff8516036112ec575b50505050602082015160060b91604060018060a01b039101511690565b9091929394506112fa6114e8565b508251602084015160409094015163ffffffff9091168503936001600160a01b039091169260069190910b906001600160801b038116156113b8575b6001600160801b03169182156113a45763ffffffff6040519661135888610d5c565b16865263ffffffff851660060b9060020b0260060b0160060b602085015260018060a01b039263ffffffff60801b9060801b16040116604082015260016060820152905f8080806112cf565b634e487b7160e01b5f52601260045260245ffd5b506001611336565b60020b5f525f51602061152d5f395f51905f5260205260405f2090565b6001600160801b03918216908216019190821161056557565b600f0b60016001607f1b03198114610565575f0390565b6001600160801b03918216908216039190821161056557565b9060020b9081156113a457818160020b0760020b6105b15781156113a45760020b0560020b61010081079060081d60010b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2807602052600160ff60405f2092161b8154189055565b60049060020b91825f525f51602061152d5f395f51905f5260205260405f20905f52016020525f60408120555f525f51602061152d5f395f51905f526020525f6003604082208281558260018201558260028201550155565b604051906114f582610d5c565b5f606083828152826020820152826040820152015256fef047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2808f047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2806f047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280aa2646970667358221220b697fe6f46f97272b9f4c88e07a910b1cc18b9adda1162f276c62dac4b34129d64736f6c634300081c0033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.