Overview
S Balance
0 S
S Value
-More Info
Private Name Tags
ContractCreator
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
935505 | 8 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 933 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": 933 }, "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": "0x5699bE61618D2386C6F5Feb9843490c08507E33F" }, "contracts/CL/core/libraries/Position.sol": { "Position": "0xd940FA70289a13075BfeF3b5cECa8Beb6568aeDd" }, "contracts/CL/core/libraries/ProtocolActions.sol": { "ProtocolActions": "0x1a161022Bf556AAa9b0e0281Ec03e19b809e0aE8" } } }
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
60808060405234601b5761181d90816100208239308160350152f35b5f80fdfe60a0806040526004361015610012575f80fd5b5f3560e01c908163d2e6311b14610f69575063fc32287914610032575f80fd5b307f0000000000000000000000000000000000000000000000000000000000000000146107575761012036600319011261075757604051610120810181811067ffffffffffffffff8211176107c1576040526004356001600160a01b038116810361075757815260243560208201526044358060020b81036107575760408201526100bb611045565b606082015260843580600f0b810361075757608082015260a4358060020b81036107575760a082015260c4359063ffffffff8216918281036107575760c082015260e4358060020b81036107575760e0820152610104356001600160801b0381168103610757576101008201526101526001600160a01b038251166020830151604084015160020b90606085015160020b92611384565b91825f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280860205260405f20917ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2802547ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2803547ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2800546080525f905f926080850151600f0b610b00575b604085015160020b9763ffffffff606087015160020b6102438b60020b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f2090565b906102788160020b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f2090565b9b60805160a01c60020b12155f14610aec5760026001830154920154905b60805160a01c60020b1215610ad857600260018d01549c0154905b602062093a808c6103198d604081015160020b908960c0606083015160020b92015116916040519a8b96879663feb0d71760e01b8852041660048601929363ffffffff91959460609383608087019816865260020b602086015260020b604085015216910152565b0381735699be61618d2386c6f5feb9843490c08507e33f5af4968715610acd578c945f98610a9c575b5063ffffffff62093a808d04165f52600485016020528a8c60ff600160405f20015416158015610a8a575b61097b575b50608001519d8e946001600160801b038754169686600f0b1596875f1461092c5750508615610904578e91600191889a5b6001600160801b036103d4816103c28d888a01548888880303036114b6565b169b60028801548b8b8b0303036114b6565b1698156108dd575b0303910155030360028b015581158015906108d4575b61088d575b5050805f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f205480158015610838575b156107d557505f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f2091604051926040840184811067ffffffffffffffff8211176107c15760405263ffffffff62093a8089041684526001600160801b0360208501931683528054680100000000000000008110156107c1576104bd91600182018155611055565b6107ae5760016001600160a01b039361050595518355519101555b1660130b63ffffffff62093a808704165f5260048701602052600160405f20015460081c60130b906110a1565b5f8160130b126107a7575b5f87600f0b1396875f1461076c576001600160a01b036001600160801b03640100000000921692166105428184611520565b920961075b575b600160ff1b81101561075757955b1561071f5763ffffffff62093a808504165f526004850160205260405f20545f878201978812911290801582169115161761070b576020955b63ffffffff62093a808604165f5260048601875260405f20555f6080840151600f0b126105c2575b8585604051908152f35b610670575b6105d3575b80806105b8565b606063ffffffff62093a8061066994041691015160020b60049060020b91825f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20905f52016020525f60408120555f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb28066020525f6003604082208281558260018201558260028201550155565b5f806105cc565b61070663ffffffff62093a80850416604084015160020b60049060020b91825f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20905f52016020525f60408120555f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb28066020525f6003604082208281558260018201558260028201550155565b6105c7565b634e487b7160e01b5f52601160045260245ffd5b63ffffffff62093a808504165f526004850160205260405f20545f878203971281881281169188139015161761070b57602095610590565b5f80fd5b5f1981101561075757600101610549565b906001600160a01b036001600160801b0361078c61079594600f0b61171a565b16911690611520565b600160ff1b8110156107575795610557565b505f610510565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b9192905f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f20925f19830192831161070b5760016001600160801b0361082d6001600160a01b039561050597611055565b5092169101556104d8565b50815f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f205f19820182811161070b5761087891611055565b505462093a80890463ffffffff16141561042e565b6001600160801b038060038c0193818554160116166001600160801b03198354161782556001600160801b038254918119908360801c0160801b1691161790555f806103f7565b508015156103f2565b84546fffffffffffffffffffffffffffffffff19166001600160801b038d161785556103dc565b7f5557f932000000000000000000000000000000000000000000000000000000005f5260045ffd5b909260019290915f600f82900b12156109635761094e61095d91600f0b61171a565b6001600160801b03168a61173a565b9a6103a3565b610976906001600160801b03168a6116fa565b61095d565b62093a8063ffffffff916001600160a01b0384511693602081015190604081015160020b8560c0606084015160020b9301511692604051976109bc89610feb565b87878704168952602089015260408801526060870152608086015260a085015204165f5260048601602052600160405f2001600160ff198254161790556001600160801b03865416610a64575b515f5260048501602052600160405f200180547fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff008b60081b1691161790558a8c610372565b610a75610a7082611109565b6110f9565b81515f526004870160205260405f2055610a09565b506001600160801b038754161561036d565b610abf91985060203d602011610ac6575b610ab78183611023565b810190611082565b965f610342565b503d610aad565b6040513d5f823e3d90fd5b600260018d015486039c01548603906102b1565b600260018301548603920154860390610296565b91509150610b6263ffffffff60c0850151166001600160801b037ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2805541661ffff60805160c81c169161ffff60805160b81c169060805160a01c60020b9061156f565b929091604085015160020b9360808601519460c0870151956001600160801b036101008901511691805f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20926001600160801b03845416985f84600f0b125f14610f5057610bec610bdd85600f0b61171a565b6001600160801b03168b61173a565b915b6001600160801b03831611610e68576001600160801b03928a158484161514159a15610ea9575b5050166001600160801b0319835416178255600f0b815460801d600f0b016f7fffffffffffffffffffffffffffffff1981126f7fffffffffffffffffffffffffffffff82131761070b576001600160801b0382549181199060801b1691161790558493606087015160020b9160808801519182600f0b9160c08a0151906001600160801b036101008c01511692865f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20956001600160801b03875416905f87125f14610e9057506001600160801b03610d00610cf88861171a565b82168361173a565b16948511610e68571596878515141597610dbc575b505050506001600160801b0319835416178255815460801d600f0b036f7fffffffffffffffffffffffffffffff81136f7fffffffffffffffffffffffffffffff1982121761070b576001600160801b0382549181199060801b1691161790558094610d9e575b156101f957610d99606086015160020b60e087015160020b9061175a565b6101f9565b610db7604087015160020b60e088015160020b9061175a565b610d7b565b60805160a01c60020b1215610e07575b5050506003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555f808080610d15565b87600187015588600287015566ffffffffffffff60038701937affffffffffffffffffffffffffffffffffffffff0000000000000063ffffffff60d81b8087549360d81b16169460381b169060ff60f81b16179116171790555f8080610dcc565b7f68d2be8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b610ea46001600160801b03918216836116fa565b610d00565b60805160a01c60020b1215610ef0575b506003840180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555f80610c15565b866001860155876002860155600385019063ffffffff60d81b8083549260d81b1616907affffffffffffffffffffffffffffffffffffffff000000000000008860381b169060ff60f81b161766ffffffffffffff8b16171790555f610eb9565b610f63846001600160801b03168b6116fa565b91610bee565b60c036600319011261075757610f7e81610feb565b60043581526024356001600160a01b03811681036107575760208201526044356040820152610fab611045565b60608201526084358060020b810361075757608082015260a43563ffffffff811681036107575781610fe39160a06020940152611109565b604051908152f35b60c0810190811067ffffffffffffffff8211176107c157604052565b6080810190811067ffffffffffffffff8211176107c157604052565b90601f8019910116810190811067ffffffffffffffff8211176107c157604052565b606435908160020b820361075757565b805482101561106e575f5260205f209060011b01905f90565b634e487b7160e01b5f52603260045260245ffd5b9081602091031261075757516001600160a01b03811681036107575790565b9060130b9060130b0390737fffffffffffffffffffffffffffffffffffffff82137fffffffffffffffffffffffff800000000000000000000000000000000000000083121761070b57565b9190820391821161070b57565b600160ff1b811461070b575f0390565b7ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc28095481511161135c576001600160a01b0360208201511660408201516111626060840192835160020b6080860193845160020b92611384565b92835f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f206111998251826113cf565b91909115611352576001916111ad91611055565b5001545f8581527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2808602090815260408083208551845260049081018352928190206001015485519751965160a0870151925163feb0d71760e01b815263ffffffff998a1695810195909552600297880b602486015290960b604484015295909516606482015293909260081c60130b9084608481735699be61618d2386c6f5feb9843490c08507e33f5af4908115610acd576001600160a01b03611280926112ca965f91611333575b501660130b6110a1565b935f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2808602052600460405f200190515f526020526001600160a01b0360405f2054931690611520565b905f811280801561132a575b156113205715611312576112e9906110f9565b810180911161070b575b905b6e093a80000000000000000000000000821161130d57565b5f9150565b61131b916110ec565b6112f3565b5050505f906112f5565b508183116112d6565b61134c915060203d602011610ac657610ab78183611023565b5f611276565b5050505050505f90565b7f4789d901000000000000000000000000000000000000000000000000000000005f5260045ffd5b9290916040519260208401946bffffffffffffffffffffffff199060601b168552603484015260e81b605483015260e81b6057820152603a81526113c9605a82611023565b51902090565b91909180549081156114ac5780541561106e57805f528360205f2054116114ac575f19820191821161070b57816114068183611055565b505494808611611418575b5050509190565b9194505f92505b82811161143e575050806114339193611055565b5054915f8080611411565b61145861144f8483979495976110ec565b60011c826110ec565b926114638484611055565b50549581870361147557505050509190565b90809396949294105f1461148a57509161141f565b92505f1981019081111561141f57634e487b7160e01b5f52601160045260245ffd5b505090505f905f90565b5f915f19818309918181029384808510940393808503941461151657837001000000000000000000000000000000001115611513575090700100000000000000000000000000000000910990828211900360801b910360801c1790565b80fd5b5050505060801c90565b5f915f19818309918181029384808510940393808503941461156557836401000000001115611513575090640100000000910990828211900360e01b910360201c1790565b5050505060201c90565b92919094935061ffff81101561106e577ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb28090190604051916115af83611007565b5463ffffffff811683528060201c60060b60208401526001600160a01b038160581c16604084015260f81c15156060830152819463ffffffff83511663ffffffff851603611615575b505050506001600160a01b036040602084015160060b9301511690565b9091929394506116236117c3565b5063ffffffff8351168403906001600160a01b036040602086015160060b95015116926001600160801b03811615155f146116ea576001600160801b03905b169182156116d6576001600160a01b039473ffffffff000000000000000000000000000000009263ffffffff6040519861169b8a611007565b16885263ffffffff831660060b9060020b0260060b0160060b602087015260801b16040116604082015260016060820152905f8080806115f8565b634e487b7160e01b5f52601260045260245ffd5b506001600160801b036001611662565b906001600160801b03809116911601906001600160801b03821161070b57565b600f0b6f7fffffffffffffffffffffffffffffff19811461070b575f0390565b906001600160801b03809116911603906001600160801b03821161070b57565b9060020b9081156116d657818160020b0760020b6107575781156116d65760020b0560020b61010081079060081d60010b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2807602052600160ff60405f2092161b8154189055565b604051906117d082611007565b5f606083828152826020820152826040820152015256fea264697066735822122093cb3d856b952be4cc07eaea8e06bb0f7185758b481b691c34b99daf6fcabcc964736f6c634300081c0033
Deployed Bytecode
0x60a0806040526004361015610012575f80fd5b5f3560e01c908163d2e6311b14610f69575063fc32287914610032575f80fd5b307f000000000000000000000000d940fa70289a13075bfef3b5ceca8beb6568aedd146107575761012036600319011261075757604051610120810181811067ffffffffffffffff8211176107c1576040526004356001600160a01b038116810361075757815260243560208201526044358060020b81036107575760408201526100bb611045565b606082015260843580600f0b810361075757608082015260a4358060020b81036107575760a082015260c4359063ffffffff8216918281036107575760c082015260e4358060020b81036107575760e0820152610104356001600160801b0381168103610757576101008201526101526001600160a01b038251166020830151604084015160020b90606085015160020b92611384565b91825f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280860205260405f20917ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2802547ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2803547ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2800546080525f905f926080850151600f0b610b00575b604085015160020b9763ffffffff606087015160020b6102438b60020b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f2090565b906102788160020b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f2090565b9b60805160a01c60020b12155f14610aec5760026001830154920154905b60805160a01c60020b1215610ad857600260018d01549c0154905b602062093a808c6103198d604081015160020b908960c0606083015160020b92015116916040519a8b96879663feb0d71760e01b8852041660048601929363ffffffff91959460609383608087019816865260020b602086015260020b604085015216910152565b0381735699be61618d2386c6f5feb9843490c08507e33f5af4968715610acd578c945f98610a9c575b5063ffffffff62093a808d04165f52600485016020528a8c60ff600160405f20015416158015610a8a575b61097b575b50608001519d8e946001600160801b038754169686600f0b1596875f1461092c5750508615610904578e91600191889a5b6001600160801b036103d4816103c28d888a01548888880303036114b6565b169b60028801548b8b8b0303036114b6565b1698156108dd575b0303910155030360028b015581158015906108d4575b61088d575b5050805f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f205480158015610838575b156107d557505f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f2091604051926040840184811067ffffffffffffffff8211176107c15760405263ffffffff62093a8089041684526001600160801b0360208501931683528054680100000000000000008110156107c1576104bd91600182018155611055565b6107ae5760016001600160a01b039361050595518355519101555b1660130b63ffffffff62093a808704165f5260048701602052600160405f20015460081c60130b906110a1565b5f8160130b126107a7575b5f87600f0b1396875f1461076c576001600160a01b036001600160801b03640100000000921692166105428184611520565b920961075b575b600160ff1b81101561075757955b1561071f5763ffffffff62093a808504165f526004850160205260405f20545f878201978812911290801582169115161761070b576020955b63ffffffff62093a808604165f5260048601875260405f20555f6080840151600f0b126105c2575b8585604051908152f35b610670575b6105d3575b80806105b8565b606063ffffffff62093a8061066994041691015160020b60049060020b91825f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20905f52016020525f60408120555f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb28066020525f6003604082208281558260018201558260028201550155565b5f806105cc565b61070663ffffffff62093a80850416604084015160020b60049060020b91825f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20905f52016020525f60408120555f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb28066020525f6003604082208281558260018201558260028201550155565b6105c7565b634e487b7160e01b5f52601160045260245ffd5b63ffffffff62093a808504165f526004850160205260405f20545f878203971281881281169188139015161761070b57602095610590565b5f80fd5b5f1981101561075757600101610549565b906001600160a01b036001600160801b0361078c61079594600f0b61171a565b16911690611520565b600160ff1b8110156107575795610557565b505f610510565b634e487b7160e01b5f525f60045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b9192905f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f20925f19830192831161070b5760016001600160801b0361082d6001600160a01b039561050597611055565b5092169101556104d8565b50815f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f205f19820182811161070b5761087891611055565b505462093a80890463ffffffff16141561042e565b6001600160801b038060038c0193818554160116166001600160801b03198354161782556001600160801b038254918119908360801c0160801b1691161790555f806103f7565b508015156103f2565b84546fffffffffffffffffffffffffffffffff19166001600160801b038d161785556103dc565b7f5557f932000000000000000000000000000000000000000000000000000000005f5260045ffd5b909260019290915f600f82900b12156109635761094e61095d91600f0b61171a565b6001600160801b03168a61173a565b9a6103a3565b610976906001600160801b03168a6116fa565b61095d565b62093a8063ffffffff916001600160a01b0384511693602081015190604081015160020b8560c0606084015160020b9301511692604051976109bc89610feb565b87878704168952602089015260408801526060870152608086015260a085015204165f5260048601602052600160405f2001600160ff198254161790556001600160801b03865416610a64575b515f5260048501602052600160405f200180547fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff008b60081b1691161790558a8c610372565b610a75610a7082611109565b6110f9565b81515f526004870160205260405f2055610a09565b506001600160801b038754161561036d565b610abf91985060203d602011610ac6575b610ab78183611023565b810190611082565b965f610342565b503d610aad565b6040513d5f823e3d90fd5b600260018d015486039c01548603906102b1565b600260018301548603920154860390610296565b91509150610b6263ffffffff60c0850151166001600160801b037ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2805541661ffff60805160c81c169161ffff60805160b81c169060805160a01c60020b9061156f565b929091604085015160020b9360808601519460c0870151956001600160801b036101008901511691805f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20926001600160801b03845416985f84600f0b125f14610f5057610bec610bdd85600f0b61171a565b6001600160801b03168b61173a565b915b6001600160801b03831611610e68576001600160801b03928a158484161514159a15610ea9575b5050166001600160801b0319835416178255600f0b815460801d600f0b016f7fffffffffffffffffffffffffffffff1981126f7fffffffffffffffffffffffffffffff82131761070b576001600160801b0382549181199060801b1691161790558493606087015160020b9160808801519182600f0b9160c08a0151906001600160801b036101008c01511692865f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb280660205260405f20956001600160801b03875416905f87125f14610e9057506001600160801b03610d00610cf88861171a565b82168361173a565b16948511610e68571596878515141597610dbc575b505050506001600160801b0319835416178255815460801d600f0b036f7fffffffffffffffffffffffffffffff81136f7fffffffffffffffffffffffffffffff1982121761070b576001600160801b0382549181199060801b1691161790558094610d9e575b156101f957610d99606086015160020b60e087015160020b9061175a565b6101f9565b610db7604087015160020b60e088015160020b9061175a565b610d7b565b60805160a01c60020b1215610e07575b5050506003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555f808080610d15565b87600187015588600287015566ffffffffffffff60038701937affffffffffffffffffffffffffffffffffffffff0000000000000063ffffffff60d81b8087549360d81b16169460381b169060ff60f81b16179116171790555f8080610dcc565b7f68d2be8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b610ea46001600160801b03918216836116fa565b610d00565b60805160a01c60020b1215610ef0575b506003840180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555f80610c15565b866001860155876002860155600385019063ffffffff60d81b8083549260d81b1616907affffffffffffffffffffffffffffffffffffffff000000000000008860381b169060ff60f81b161766ffffffffffffff8b16171790555f610eb9565b610f63846001600160801b03168b6116fa565b91610bee565b60c036600319011261075757610f7e81610feb565b60043581526024356001600160a01b03811681036107575760208201526044356040820152610fab611045565b60608201526084358060020b810361075757608082015260a43563ffffffff811681036107575781610fe39160a06020940152611109565b604051908152f35b60c0810190811067ffffffffffffffff8211176107c157604052565b6080810190811067ffffffffffffffff8211176107c157604052565b90601f8019910116810190811067ffffffffffffffff8211176107c157604052565b606435908160020b820361075757565b805482101561106e575f5260205f209060011b01905f90565b634e487b7160e01b5f52603260045260245ffd5b9081602091031261075757516001600160a01b03811681036107575790565b9060130b9060130b0390737fffffffffffffffffffffffffffffffffffffff82137fffffffffffffffffffffffff800000000000000000000000000000000000000083121761070b57565b9190820391821161070b57565b600160ff1b811461070b575f0390565b7ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc28095481511161135c576001600160a01b0360208201511660408201516111626060840192835160020b6080860193845160020b92611384565b92835f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccc280a60205260405f206111998251826113cf565b91909115611352576001916111ad91611055565b5001545f8581527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2808602090815260408083208551845260049081018352928190206001015485519751965160a0870151925163feb0d71760e01b815263ffffffff998a1695810195909552600297880b602486015290960b604484015295909516606482015293909260081c60130b9084608481735699be61618d2386c6f5feb9843490c08507e33f5af4908115610acd576001600160a01b03611280926112ca965f91611333575b501660130b6110a1565b935f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2808602052600460405f200190515f526020526001600160a01b0360405f2054931690611520565b905f811280801561132a575b156113205715611312576112e9906110f9565b810180911161070b575b905b6e093a80000000000000000000000000821161130d57565b5f9150565b61131b916110ec565b6112f3565b5050505f906112f5565b508183116112d6565b61134c915060203d602011610ac657610ab78183611023565b5f611276565b5050505050505f90565b7f4789d901000000000000000000000000000000000000000000000000000000005f5260045ffd5b9290916040519260208401946bffffffffffffffffffffffff199060601b168552603484015260e81b605483015260e81b6057820152603a81526113c9605a82611023565b51902090565b91909180549081156114ac5780541561106e57805f528360205f2054116114ac575f19820191821161070b57816114068183611055565b505494808611611418575b5050509190565b9194505f92505b82811161143e575050806114339193611055565b5054915f8080611411565b61145861144f8483979495976110ec565b60011c826110ec565b926114638484611055565b50549581870361147557505050509190565b90809396949294105f1461148a57509161141f565b92505f1981019081111561141f57634e487b7160e01b5f52601160045260245ffd5b505090505f905f90565b5f915f19818309918181029384808510940393808503941461151657837001000000000000000000000000000000001115611513575090700100000000000000000000000000000000910990828211900360801b910360801c1790565b80fd5b5050505060801c90565b5f915f19818309918181029384808510940393808503941461156557836401000000001115611513575090640100000000910990828211900360e01b910360201c1790565b5050505060201c90565b92919094935061ffff81101561106e577ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb28090190604051916115af83611007565b5463ffffffff811683528060201c60060b60208401526001600160a01b038160581c16604084015260f81c15156060830152819463ffffffff83511663ffffffff851603611615575b505050506001600160a01b036040602084015160060b9301511690565b9091929394506116236117c3565b5063ffffffff8351168403906001600160a01b036040602086015160060b95015116926001600160801b03811615155f146116ea576001600160801b03905b169182156116d6576001600160a01b039473ffffffff000000000000000000000000000000009263ffffffff6040519861169b8a611007565b16885263ffffffff831660060b9060020b0260060b0160060b602087015260801b16040116604082015260016060820152905f8080806115f8565b634e487b7160e01b5f52601260045260245ffd5b506001600160801b036001611662565b906001600160801b03809116911601906001600160801b03821161070b57565b600f0b6f7fffffffffffffffffffffffffffffff19811461070b575f0390565b906001600160801b03809116911603906001600160801b03821161070b57565b9060020b9081156116d657818160020b0760020b6107575781156116d65760020b0560020b61010081079060081d60010b5f527ff047b0c59244a0faf8e48cb6b6fde518e6717176152b6dd953628cd9dccb2807602052600160ff60405f2092161b8154189055565b604051906117d082611007565b5f606083828152826020820152826040820152015256fea264697066735822122093cb3d856b952be4cc07eaea8e06bb0f7185758b481b691c34b99daf6fcabcc964736f6c634300081c0033
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.