From bbfb85d6d34e12d144349f8c5017e89bfa381316 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 14:42:55 +0200 Subject: [PATCH] tests: Update semantic tests for EOF --- test/libsolidity/Metadata.cpp | 2 +- .../semanticTests/UniswapV3Flattened.sol | 5191 +++++++++++++++++ .../semanticTests/UniswapV3Flattened_eof.sol | 5191 +++++++++++++++++ .../keccak256_packed_complex_types.sol | 2 + .../keccak256_packed_complex_types_eof.sol | 17 + .../constructor/callvalue_check.sol | 35 +- .../constructor/no_callvalue_check.sol | 2 + .../constructor/no_callvalue_check_eof.sol | 28 + .../deployedCodeExclusion/bound_function.sol | 2 + .../library_function.sol | 2 + .../deployedCodeExclusion/module_function.sol | 2 + .../static_base_function.sol | 2 + .../subassembly_deduplication.sol | 2 + .../deployedCodeExclusion/super_function.sol | 2 + .../virtual_function.sol | 2 + .../errors/errors_by_parameter_type.sol | 1 + .../errors/errors_by_parameter_type_eof.sol | 46 + ...quire_error_function_pointer_parameter.sol | 2 + ...e_error_function_pointer_parameter_eof.sol | 21 + .../events/event_emit_from_other_contract.sol | 2 + .../event_emit_from_other_contract_eof.sol | 28 + .../events/event_indexed_function.sol | 2 + .../events/event_indexed_function2.sol | 2 + .../events/event_indexed_function2_eof.sol | 18 + .../events/event_indexed_function_eof.sol | 12 + .../_stringutils/stringutils.sol | 2 +- .../externalContracts/deposit_contract.sol | 2 + .../deposit_contract_eof.sol | 216 + .../semanticTests/externalContracts/snark.sol | 13 +- .../externalContracts/snark_eof.sol | 312 + .../freeFunctions/free_runtimecode.sol | 2 + .../calling_nonexisting_contract_throws.sol | 2 + .../external_call_at_construction_time.sol | 1 + .../external_call_to_nonexisting.sol | 2 + ...ernal_call_to_nonexisting_debugstrings.sol | 1 + .../functionCall/failed_create.sol | 1 + .../functionCall/failed_create_eof.sol | 38 + .../functionCall/gas_and_value_basic.sol | 2 + .../functionCall/gas_and_value_basic_eof.sol | 52 + .../gas_and_value_brace_syntax.sol | 2 + .../gas_and_value_brace_syntax_eof.sol | 51 + .../functionTypes/address_member.sol | 2 + .../functionTypes/address_member_eof.sol | 13 + .../function_external_delete_storage.sol | 2 + .../function_external_delete_storage_eof.sol | 40 + .../immutable/multi_creation.sol | 2 + .../immutable/multi_creation_eof.sol | 40 + .../address_overload_resolution.sol | 2 + .../address_overload_resolution_eof.sol | 32 + .../inheritance/member_notation_ctor.sol | 2 + .../inheritance/member_notation_ctor_eof.sol | 29 + .../external_function_pointer_address.sol | 2 + .../external_function_pointer_address_eof.sol | 20 + .../transient_storage_low_level_calls.sol | 1 + .../transient_storage_low_level_calls_eof.sol | 78 + .../transient_storage_selfdestruct.sol | 1 + .../interface_inheritance_conversions.sol | 2 + .../interface_inheritance_conversions_eof.sol | 47 + .../balance_other_contract.sol | 2 + .../balance_other_contract_eof.sol | 33 + .../operator_making_pure_external_call.sol | 2 + ...operator_making_pure_external_call_eof.sol | 68 + .../operator_making_view_external_call.sol | 2 + ...operator_making_view_external_call_eof.sol | 74 + .../called_contract_has_code.sol | 1 + .../reverts/revert_return_area.sol | 1 + .../reverts/revert_return_area_eof.sol | 21 + .../salted_create/prediction_example.sol | 22 +- .../salted_create_with_value.sol | 1 + .../salted_create_with_value_eof.sol | 31 + .../semanticTests/shanghai/evmone_support.sol | 1 + .../semanticTests/state/gasleft.sol | 2 + .../semanticTests/tryCatch/create.sol | 1 + .../semanticTests/tryCatch/create_eof.sol | 34 + .../tryCatch/return_function.sol | 2 + .../tryCatch/return_function_eof.sol | 20 + .../semanticTests/various/address_code.sol | 2 + .../various/address_code_complex.sol | 2 + .../various/code_access_content.sol | 2 + .../various/code_access_create.sol | 2 + .../various/code_access_padding.sol | 2 + .../various/code_access_runtime.sol | 1 + .../semanticTests/various/code_length.sol | 2 + .../various/code_length_contract_member.sol | 2 + .../semanticTests/various/codehash.sol | 1 + .../various/codehash_assembly.sol | 1 + .../semanticTests/various/create_calldata.sol | 2 + .../semanticTests/various/create_random.sol | 7 +- .../various/gasleft_decrease.sol | 2 + .../various/many_subassemblies.sol | 2 + .../various/many_subassemblies_eof.sol | 41 + .../various/selfdestruct_post_cancun.sol | 1 + ...uct_post_cancun_multiple_beneficiaries.sol | 1 + .../selfdestruct_post_cancun_redeploy.sol | 1 + .../viaYul/conversion/function_cast.sol | 3 + .../viaYul/conversion/function_cast_eof.sol | 25 + .../semanticTests/viaYul/function_address.sol | 2 + .../viaYul/function_address_eof.sol | 18 + 98 files changed, 12041 insertions(+), 29 deletions(-) create mode 100644 test/libsolidity/semanticTests/UniswapV3Flattened.sol create mode 100644 test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol create mode 100644 test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol create mode 100644 test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol create mode 100644 test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol create mode 100644 test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol create mode 100644 test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol create mode 100644 test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol create mode 100644 test/libsolidity/semanticTests/events/event_indexed_function_eof.sol create mode 100644 test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol create mode 100644 test/libsolidity/semanticTests/externalContracts/snark_eof.sol create mode 100644 test/libsolidity/semanticTests/functionCall/failed_create_eof.sol create mode 100644 test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol create mode 100644 test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol create mode 100644 test/libsolidity/semanticTests/functionTypes/address_member_eof.sol create mode 100644 test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol create mode 100644 test/libsolidity/semanticTests/immutable/multi_creation_eof.sol create mode 100644 test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol create mode 100644 test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol create mode 100644 test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol create mode 100644 test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol create mode 100644 test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol create mode 100644 test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol create mode 100644 test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol create mode 100644 test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol create mode 100644 test/libsolidity/semanticTests/tryCatch/create_eof.sol create mode 100644 test/libsolidity/semanticTests/tryCatch/return_function_eof.sol create mode 100644 test/libsolidity/semanticTests/various/many_subassemblies_eof.sol create mode 100644 test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol create mode 100644 test/libsolidity/semanticTests/viaYul/function_address_eof.sol diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index 94687f99006d..9c0fba3e1447 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE(metadata_eof_experimental) CompilerStack compilerStack; compilerStack.setMetadataFormat(metadataFormat); compilerStack.setSources({{"", sourceCode}}); - compilerStack.setEVMVersion({}); + compilerStack.setEVMVersion(langutil::EVMVersion::prague()); compilerStack.setViaIR(true); compilerStack.setEOFVersion(1); compilerStack.setOptimiserSettings(true); diff --git a/test/libsolidity/semanticTests/UniswapV3Flattened.sol b/test/libsolidity/semanticTests/UniswapV3Flattened.sol new file mode 100644 index 000000000000..319cf73da2a1 --- /dev/null +++ b/test/libsolidity/semanticTests/UniswapV3Flattened.sol @@ -0,0 +1,5191 @@ +// Sources flattened with hardhat v2.2.0 https://hardhat.org + +// File contracts/interfaces/pool/IUniswapV3PoolImmutables.sol + + +pragma solidity >=0.0; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions(bytes32 key) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations(uint256 index) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @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. + /// @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) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolActions.sol + + +pragma solidity >=0.0; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} + + +// File contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol + + +pragma solidity >=0.0; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolEvents.sol + + +pragma solidity >=0.0; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/IUniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} + + +// File contracts/libraries/FullMath.sol + + +pragma solidity >=0.0; + +/// @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 = uint256(-int256(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++; + } + } + } +} + + +// File contracts/libraries/FixedPoint128.sol + + +pragma solidity >=0.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; +} + + +// File contracts/libraries/LiquidityMath.sol + + +pragma solidity >=0.0; + +/// @title Math library for liquidity +library LiquidityMath { + /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows + /// @param x The liquidity before change + /// @param y The delta by which liquidity should be changed + /// @return z The liquidity delta + function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { + if (y < 0) { + unchecked { + require((z = x - uint128(-y)) < x, 'LS'); + } + } else { + unchecked { + require((z = x + uint128(y)) >= x, 'LA'); + } + } + } +} + + +// File contracts/libraries/Position.sol + + +pragma solidity >=0.0; + + + +/// @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 { + // info stored for each user's position + struct Info { + // the amount of liquidity owned by this position + uint128 liquidity; + // fee growth per unit of liquidity as of the last update to liquidity or fees owed + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // the fees owed to the position owner in token0/token1 + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @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 => Info) storage self, + address owner, + int24 tickLower, + int24 tickUpper + ) internal view returns (Position.Info storage position) { + position = self[keccak256(abi.encodePacked(owner, 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( + Info storage self, + int128 liquidityDelta, + uint256 feeGrowthInside0X128, + uint256 feeGrowthInside1X128 + ) internal { + unchecked { + Info memory _self = self; + + uint128 liquidityNext; + if (liquidityDelta == 0) { + require(_self.liquidity > 0, 'NP'); // disallow pokes for 0 liquidity positions + liquidityNext = _self.liquidity; + } else { + liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta); + } + + // calculate accumulated fees + uint128 tokensOwed0 = + uint128( + FullMath.mulDiv( + feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + uint128 tokensOwed1 = + uint128( + FullMath.mulDiv( + feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + + // update the position + if (liquidityDelta != 0) self.liquidity = liquidityNext; + self.feeGrowthInside0LastX128 = feeGrowthInside0X128; + self.feeGrowthInside1LastX128 = feeGrowthInside1X128; + if (tokensOwed0 > 0 || tokensOwed1 > 0) { + // overflow is acceptable, have to withdraw before you hit type(uint128).max fees + self.tokensOwed0 += tokensOwed0; + self.tokensOwed1 += tokensOwed1; + } + } + } +} + + +// File contracts/libraries/LowGasSafeMath.sol + + +pragma solidity >=0.0; + +/// @title Optimized overflow and underflow safe math operations +/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost +library LowGasSafeMath { + /// @notice Returns x + y, reverts if sum overflows uint256 + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + + /// @notice Returns x - y, reverts if underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + + /// @notice Returns x * y, reverts if overflows + /// @param x The multiplicand + /// @param y The multiplier + /// @return z The product of x and y + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(x == 0 || (z = x * y) / x == y); + } + + /// @notice Returns x + y, reverts if overflows or underflows + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x == (y >= 0)); + } + + /// @notice Returns x - y, reverts if overflows or underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x - y) <= x == (y >= 0)); + } +} + + +// File contracts/libraries/SafeCast.sol + + +pragma solidity >=0.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); + } +} + + +// File contracts/libraries/UnsafeMath.sol + + +pragma solidity >=0.0; + +/// @title Math functions that do not check inputs or outputs +/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks +library UnsafeMath { + /// @notice Returns ceil(x / y) + /// @dev division by 0 has unspecified behavior, and must be checked externally + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, ceil(x / y) + function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := add(div(x, y), gt(mod(x, y), 0)) + } + } +} + + +// File contracts/libraries/FixedPoint96.sol + + +pragma solidity >=0.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; +} + + +// File contracts/libraries/SqrtPriceMath.sol + + +pragma solidity >=0.0; + + + + +/// @title Functions based on Q64.96 sqrt price and liquidity +/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas +library SqrtPriceMath { + using LowGasSafeMath for uint256; + using SafeCast for uint256; + + /// @notice Gets the next sqrt price given a delta of token0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of token0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + uint256 product; + if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); + } else { + uint256 product; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + /// @notice Gets the next sqrt price given a delta of token1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of token1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = + ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return uint256(sqrtPX96).add(quotient).toUint160(); + } else { + uint256 quotient = + ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + require(sqrtPX96 > quotient); + // always fits 160 bits + return uint160(sqrtPX96 - quotient); + } + } + } + + /// @notice Gets the next sqrt price given an input amount of token0 or token1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of token0, or token1, is being swapped in + /// @param zeroForOne Whether the amount in is token0 or token1 + /// @return sqrtQX96 The price after adding the input amount to token0 or token1 + function getNextSqrtPriceFromInput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we don't pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of token0 or token1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of token0, or token1, is being swapped out + /// @param zeroForOne Whether the amount out is token0 or token1 + /// @return sqrtQX96 The price after removing the output amount of token0 or token1 + function getNextSqrtPriceFromOutput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return + roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed token0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount0) { + return + liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + + /// @notice Helper that gets signed token1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount1) { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } +} + + +// File contracts/libraries/SwapMath.sol + + +pragma solidity >=0.0; + + +/// @title Computes the result of a swap within ticks +/// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. +library SwapMath { + /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap + /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive + /// @param sqrtRatioCurrentX96 The current sqrt price of the pool + /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @param liquidity The usable liquidity + /// @param amountRemaining How much input or output amount is remaining to be swapped in/out + /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip + /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap + /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap + /// @return feeAmount The amount of input that will be taken as a fee + function computeSwapStep( + uint160 sqrtRatioCurrentX96, + uint160 sqrtRatioTargetX96, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + internal + pure + returns ( + uint160 sqrtRatioNextX96, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + bool exactIn = amountRemaining >= 0; + + if (exactIn) { + uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); + if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne + ); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); + if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + uint256(-amountRemaining), + zeroForOne + ); + } + + bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; + + // get the input/output amounts + if (zeroForOne) { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); + } else { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); + } + + // cap the output amount to not exceed the remaining output amount + if (!exactIn && amountOut > uint256(-amountRemaining)) { + amountOut = uint256(-amountRemaining); + } + + if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = uint256(amountRemaining) - amountIn; + } else { + feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); + } + } +} + + +// File contracts/libraries/TickMath.sol + + +pragma solidity >=0.0; + +/// @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 { + /// @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) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(int256(MAX_TICK)), '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; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // 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) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, '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; // 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; + } +} + + +// File contracts/libraries/Tick.sol + + +pragma solidity >=0.0; + + + +/// @title Tick +/// @notice Contains functions for managing tick processes and relevant calculations +library Tick { + using LowGasSafeMath for int256; + using SafeCast for int256; + + // info stored for each initialized individual tick + struct Info { + // the total position liquidity that references this tick + uint128 liquidityGross; + // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), + int128 liquidityNet; + // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint256 feeGrowthOutside0X128; + uint256 feeGrowthOutside1X128; + // the cumulative tick value on the other side of the tick + int56 tickCumulativeOutside; + // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint160 secondsPerLiquidityOutsideX128; + // the seconds spent on the other side of the tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint32 secondsOutside; + // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 + // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks + bool initialized; + } + + /// @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) { + 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 => Tick.Info) storage self, + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + Info storage lower = self[tickLower]; + Info storage upper = self[tickUpper]; + + // calculate fee growth below + uint256 feeGrowthBelow0X128; + uint256 feeGrowthBelow1X128; + unchecked { + if (tickCurrent >= tickLower) { + feeGrowthBelow0X128 = lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = lower.feeGrowthOutside1X128; + } else { + feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128; + } + + // 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 => Tick.Info) 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) { + Tick.Info storage info = self[tick]; + + uint128 liquidityGrossBefore = info.liquidityGross; + uint128 liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta); + + require(liquidityGrossAfter <= maxLiquidity, 'LO'); + + flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); + + if (liquidityGrossBefore == 0) { + // 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; + + // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) + info.liquidityNet = upper + ? int256(info.liquidityNet).sub(liquidityDelta).toInt128() + : int256(info.liquidityNet).add(liquidityDelta).toInt128(); + } + + /// @notice Clears tick data + /// @param self The mapping containing all initialized tick information for initialized ticks + /// @param tick The tick that will be cleared + function clear(mapping(int24 => Tick.Info) storage self, int24 tick) internal { + 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 => Tick.Info) storage self, + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) internal returns (int128 liquidityNet) { + Tick.Info storage info = self[tick]; + 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; + } +} + + +// File contracts/libraries/BitMath.sol + + +pragma solidity >=0.0; + +/// @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); + + 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); + + 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; + } +} + + +// File contracts/libraries/TickBitmap.sol + + +pragma solidity >=0.0; + +/// @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) { + wordPos = int16(tick >> 8); + bitPos = uint8(uint24(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 { + require(tick % tickSpacing == 0); // ensure that the tick is spaced + (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) { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + } + } +} + + +// File contracts/interfaces/IERC20Minimal.sol + + +pragma solidity >=0.0; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns the balance of a token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +// File contracts/libraries/TransferHelper.sol + + +pragma solidity >=0.0; + +/// @title TransferHelper +/// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false +library TransferHelper { + /// @notice Transfers tokens from msg.sender to a recipient + /// @dev Calls transfer on token contract, errors with TF if transfer fails + /// @param token The contract address of the token which will be transferred + /// @param to The recipient of the transfer + /// @param value The value of the transfer + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = + token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'TF'); + } +} + + +// File contracts/test/BitMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract BitMathEchidnaTest { + function mostSignificantBitInvariant(uint256 input) external pure { + uint8 msb = BitMath.mostSignificantBit(input); + assert(input >= (uint256(2)**msb)); + assert(msb == 255 || input < uint256(2)**(msb + 1)); + } + + function leastSignificantBitInvariant(uint256 input) external pure { + uint8 lsb = BitMath.leastSignificantBit(input); + assert(input & (uint256(2)**lsb) != 0); + assert(input & (uint256(2)**lsb - 1) == 0); + } +} + + +// File contracts/test/BitMathTest.sol + + +pragma solidity >=0.0; + +contract BitMathTest { + function mostSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.mostSignificantBit(x); + } + + function getGasCostOfMostSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.mostSignificantBit(x); + return gasBefore - gasleft(); + } + + function leastSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.leastSignificantBit(x); + } + + function getGasCostOfLeastSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.leastSignificantBit(x); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/FullMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract FullMathEchidnaTest { + function checkMulDivRounding( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + + uint256 ceiled = FullMath.mulDivRoundingUp(x, y, d); + uint256 floored = FullMath.mulDiv(x, y, d); + + if (mulmod(x, y, d) > 0) { + assert(ceiled - floored == 1); + } else { + assert(ceiled == floored); + } + } + + function checkMulDiv( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDiv(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 <= x); + assert(y2 <= y); + + assert(x - x2 < d); + assert(y - y2 < d); + } + + function checkMulDivRoundingUp( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDivRoundingUp(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 >= x); + assert(y2 >= y); + + assert(x2 - x < d); + assert(y2 - y < d); + } +} + + +// File contracts/test/FullMathTest.sol + + +pragma solidity >=0.0; + +contract FullMathTest { + function mulDiv( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDiv(x, y, z); + } + + function mulDivRoundingUp( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDivRoundingUp(x, y, z); + } +} + + +// File contracts/test/LiquidityMathTest.sol + + +pragma solidity >=0.0; + +contract LiquidityMathTest { + function addDelta(uint128 x, int128 y) external pure returns (uint128 z) { + return LiquidityMath.addDelta(x, y); + } + + function getGasCostOfAddDelta(uint128 x, int128 y) external view returns (uint256) { + uint256 gasBefore = gasleft(); + LiquidityMath.addDelta(x, y); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/LowGasSafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract LowGasSafeMathEchidnaTest { + function checkAdd(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(z >= x && z >= y); + } + + function checkSub(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(z <= x); + } + + function checkMul(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.mul(x, y); + assert(z == x * y); + assert(x == 0 || y == 0 || (z >= x && z >= y)); + } + + function checkAddi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(y < 0 ? z < x : z >= x); + } + + function checkSubi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(y < 0 ? z > x : z <= x); + } +} + + +// File contracts/NoDelegateCall.sol + + +pragma solidity >=0.0; + +/// @title Prevents delegatecall to a contract +/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract +abstract contract NoDelegateCall { + /// @dev The original address of this contract + address private immutable original; + + constructor() { + // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. + // In other words, this variable won't change when it's checked at runtime. + original = address(this); + } + + /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, + /// and the use of immutable means the address bytes are copied in every place the modifier is used. + function checkNotDelegateCall() private view { + require(address(this) == original); + } + + /// @notice Prevents delegatecall into the modified method + modifier noDelegateCall() { + checkNotDelegateCall(); + _; + } +} + + +// File contracts/libraries/Oracle.sol + + +pragma solidity >=0.0; + +/// @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 { + struct Observation { + // the block timestamp of the observation + uint32 blockTimestamp; + // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized + int56 tickCumulative; + // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized + uint160 secondsPerLiquidityCumulativeX128; + // whether or not the observation is initialized + bool initialized; + } + + /// @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) { + Observation memory last = self[index]; + + // early return if we've already written an observation this block + if (last.blockTimestamp == blockTimestamp) return (index, cardinality); + + // 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) { + require(current > 0, 'I'); + // no-op if the passed next value isn't greater than the current next value + if (next <= current) return current; + // store in each slot to prevent fresh SSTOREs in swaps + // 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 bool Whether `a` is chronologically <= `b` + function lte( + uint32 time, + uint32 a, + uint32 b + ) private pure returns (bool) { + // 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) { + uint256 l = (index + 1) % cardinality; // oldest observation + uint256 r = l + cardinality - 1; // newest observation + uint256 i; + while (true) { + i = (l + r) / 2; + + beforeOrAt = self[i % cardinality]; + + // 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); + + // 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) { + // optimistically set before to the newest observation + beforeOrAt = self[index]; + + // 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) { + // if newest observation equals target, we're in the same block, so we can ignore atOrAfter + return (beforeOrAt, atOrAfter); + } else { + // otherwise, we need to transform + return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); + } + } + + // now, set before to the oldest observation + beforeOrAt = self[(index + 1) % cardinality]; + if (!beforeOrAt.initialized) beforeOrAt = self[0]; + + // ensure that the target is chronologically at or after the oldest observation + require(lte(time, beforeOrAt.blockTimestamp, target), 'OLD'); + + // 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) { + // we're at the left boundary + return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); + } else if (target == atOrAfter.blockTimestamp) { + // we're at the right boundary + return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); + } else { + // 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) { + require(cardinality > 0, '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 + ); + } + } +} + + +// File contracts/interfaces/IUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +/// @title An interface for a contract that is capable of deploying Uniswap V3 Pools +/// @notice A contract that constructs a pool must implement this to pass arguments to the pool +/// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash +/// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain +interface IUniswapV3PoolDeployer { + /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. + /// @dev Called by the pool constructor to fetch the parameters of the pool + /// Returns factory The factory address + /// Returns token0 The first token of the pool by address sort order + /// Returns token1 The second token of the pool by address sort order + /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// Returns tickSpacing The minimum number of ticks between initialized ticks + function parameters() + external + view + returns ( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ); +} + + +// File contracts/interfaces/IUniswapV3Factory.sol + + +pragma solidity >=0.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} + + +// File contracts/interfaces/callback/IUniswapV3MintCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#mint +/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface +interface IUniswapV3MintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint. + /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity + /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3SwapCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3FlashCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#flash +/// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface +interface IUniswapV3FlashCallback { + /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. + /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param fee0 The fee amount in token0 due to the pool by the end of the flash + /// @param fee1 The fee amount in token1 due to the pool by the end of the flash + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + + +// File contracts/UniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + + + + + + + + + + + + +contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + using Tick for mapping(int24 => Tick.Info); + using TickBitmap for mapping(int16 => uint256); + using Position for mapping(bytes32 => Position.Info); + using Position for Position.Info; + using Oracle for Oracle.Observation[65535]; + + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override factory; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token0; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token1; + /// @inheritdoc IUniswapV3PoolImmutables + uint24 public immutable override fee; + + /// @inheritdoc IUniswapV3PoolImmutables + int24 public immutable override tickSpacing; + + /// @inheritdoc IUniswapV3PoolImmutables + uint128 public immutable override maxLiquidityPerTick; + + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the most-recently updated index of the observations array + uint16 observationIndex; + // the current maximum number of observations that are being stored + uint16 observationCardinality; + // the next maximum number of observations to store, triggered in observations.write + uint16 observationCardinalityNext; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + // whether the pool is locked + bool unlocked; + } + /// @inheritdoc IUniswapV3PoolState + Slot0 public override slot0; + + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal0X128; + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal1X128; + + // accumulated protocol fees in token0/token1 units + struct ProtocolFees { + uint128 token0; + uint128 token1; + } + /// @inheritdoc IUniswapV3PoolState + ProtocolFees public override protocolFees; + + /// @inheritdoc IUniswapV3PoolState + uint128 public override liquidity; + + /// @inheritdoc IUniswapV3PoolState + mapping(int24 => Tick.Info) public override ticks; + /// @inheritdoc IUniswapV3PoolState + mapping(int16 => uint256) public override tickBitmap; + /// @inheritdoc IUniswapV3PoolState + mapping(bytes32 => Position.Info) public override positions; + /// @inheritdoc IUniswapV3PoolState + Oracle.Observation[65535] public override observations; + + /// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance + /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because + /// we use balance checks to determine the payment status of interactions such as mint, swap and flash. + modifier lock() { + require(slot0.unlocked, 'LOK'); + slot0.unlocked = false; + _; + slot0.unlocked = true; + } + + /// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner() + modifier onlyFactoryOwner() { + require(msg.sender == IUniswapV3Factory(factory).owner()); + _; + } + + constructor() { + int24 _tickSpacing; + (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters(); + tickSpacing = _tickSpacing; + + maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing); + } + + /// @dev Common checks for valid tick inputs. + function checkTicks(int24 tickLower, int24 tickUpper) private pure { + require(tickLower < tickUpper, 'TLU'); + require(tickLower >= TickMath.MIN_TICK, 'TLM'); + require(tickUpper <= TickMath.MAX_TICK, 'TUM'); + } + + /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests. + function _blockTimestamp() internal view virtual returns (uint32) { + return uint32(block.timestamp); // truncation is desired + } + + /// @dev Get the pool's balance of token0 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance0() private view returns (uint256) { + (bool success, bytes memory data) = + token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @dev Get the pool's balance of token1 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance1() private view returns (uint256) { + (bool success, bytes memory data) = + token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + override + noDelegateCall + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ) + { + unchecked { + checkTicks(tickLower, tickUpper); + + int56 tickCumulativeLower; + int56 tickCumulativeUpper; + uint160 secondsPerLiquidityOutsideLowerX128; + uint160 secondsPerLiquidityOutsideUpperX128; + uint32 secondsOutsideLower; + uint32 secondsOutsideUpper; + + { + Tick.Info storage lower = ticks[tickLower]; + Tick.Info storage upper = ticks[tickUpper]; + bool initializedLower; + (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = ( + lower.tickCumulativeOutside, + lower.secondsPerLiquidityOutsideX128, + lower.secondsOutside, + lower.initialized + ); + require(initializedLower); + + bool initializedUpper; + (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = ( + upper.tickCumulativeOutside, + upper.secondsPerLiquidityOutsideX128, + upper.secondsOutside, + upper.initialized + ); + require(initializedUpper); + } + + Slot0 memory _slot0 = slot0; + + if (_slot0.tick < tickLower) { + return ( + tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128, + secondsOutsideLower - secondsOutsideUpper + ); + } else if (_slot0.tick < tickUpper) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + _slot0.tick, + _slot0.observationIndex, + liquidity, + _slot0.observationCardinality + ); + return ( + tickCumulative - tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityCumulativeX128 - + secondsPerLiquidityOutsideLowerX128 - + secondsPerLiquidityOutsideUpperX128, + time - secondsOutsideLower - secondsOutsideUpper + ); + } else { + return ( + tickCumulativeUpper - tickCumulativeLower, + secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128, + secondsOutsideUpper - secondsOutsideLower + ); + } + } + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function observe(uint32[] calldata secondsAgos) + external + view + override + noDelegateCall + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return + observations.observe( + _blockTimestamp(), + secondsAgos, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + } + + /// @inheritdoc IUniswapV3PoolActions + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) + external + override + lock + noDelegateCall + { + uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event + uint16 observationCardinalityNextNew = + observations.grow(observationCardinalityNextOld, observationCardinalityNext); + slot0.observationCardinalityNext = observationCardinalityNextNew; + if (observationCardinalityNextOld != observationCardinalityNextNew) + emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew); + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev not locked because it initializes unlocked + function initialize(uint160 sqrtPriceX96) external override { + require(slot0.sqrtPriceX96 == 0, 'AI'); + + int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); + + (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp()); + + slot0 = Slot0({ + sqrtPriceX96: sqrtPriceX96, + tick: tick, + observationIndex: 0, + observationCardinality: cardinality, + observationCardinalityNext: cardinalityNext, + feeProtocol: 0, + unlocked: true + }); + + emit Initialize(sqrtPriceX96, tick); + } + + struct ModifyPositionParams { + // the address that owns the position + address owner; + // the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + // any change in liquidity + int128 liquidityDelta; + } + + /// @dev Effect some changes to a position + /// @param params the position details and the change to the position's liquidity to effect + /// @return position a storage pointer referencing the position with the given owner and tick range + /// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient + /// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient + function _modifyPosition(ModifyPositionParams memory params) + private + noDelegateCall + returns ( + Position.Info storage position, + int256 amount0, + int256 amount1 + ) + { + checkTicks(params.tickLower, params.tickUpper); + + Slot0 memory _slot0 = slot0; // SLOAD for gas optimization + + position = _updatePosition( + params.owner, + params.tickLower, + params.tickUpper, + params.liquidityDelta, + _slot0.tick + ); + + if (params.liquidityDelta != 0) { + if (_slot0.tick < params.tickLower) { + // current tick is below the passed range; liquidity can only become in range by crossing from left to + // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it + amount0 = SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } else if (_slot0.tick < params.tickUpper) { + // current tick is inside the passed range + uint128 liquidityBefore = liquidity; // SLOAD for gas optimization + + // write an oracle entry + (slot0.observationIndex, slot0.observationCardinality) = observations.write( + _slot0.observationIndex, + _blockTimestamp(), + _slot0.tick, + liquidityBefore, + _slot0.observationCardinality, + _slot0.observationCardinalityNext + ); + + amount0 = SqrtPriceMath.getAmount0Delta( + _slot0.sqrtPriceX96, + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + _slot0.sqrtPriceX96, + params.liquidityDelta + ); + + liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta); + } else { + // current tick is above the passed range; liquidity can only become in range by crossing from right to + // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } + } + } + + /// @dev Gets and updates a position with the given liquidity delta + /// @param owner the owner of the position + /// @param tickLower the lower tick of the position's tick range + /// @param tickUpper the upper tick of the position's tick range + /// @param tick the current tick, passed to avoid sloads + function _updatePosition( + address owner, + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta, + int24 tick + ) private returns (Position.Info storage position) { + position = positions.get(owner, tickLower, tickUpper); + + uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization + uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization + + // if we need to update the ticks, do it + bool flippedLower; + bool flippedUpper; + if (liquidityDelta != 0) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + + flippedLower = ticks.update( + tickLower, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + false, + maxLiquidityPerTick + ); + flippedUpper = ticks.update( + tickUpper, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + true, + maxLiquidityPerTick + ); + + if (flippedLower) { + tickBitmap.flipTick(tickLower, tickSpacing); + } + if (flippedUpper) { + tickBitmap.flipTick(tickUpper, tickSpacing); + } + } + + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128); + + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + + // clear any tick data that is no longer needed + if (liquidityDelta < 0) { + if (flippedLower) { + ticks.clear(tickLower); + } + if (flippedUpper) { + ticks.clear(tickUpper); + } + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external override lock returns (uint256 amount0, uint256 amount1) { + require(amount > 0); + (, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: recipient, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(amount0Int); + amount1 = uint256(amount1Int); + + uint256 balance0Before; + uint256 balance1Before; + if (amount0 > 0) balance0Before = balance0(); + if (amount1 > 0) balance1Before = balance1(); + IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data); + if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0'); + if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1'); + + emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1); + } + + /// @inheritdoc IUniswapV3PoolActions + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock returns (uint128 amount0, uint128 amount1) { + unchecked { + // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1} + Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper); + + amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested; + amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested; + + if (amount0 > 0) { + position.tokensOwed0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + position.tokensOwed1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1); + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external override lock returns (uint256 amount0, uint256 amount1) { + (Position.Info storage position, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: msg.sender, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: -int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(-amount0Int); + amount1 = uint256(-amount1Int); + + if (amount0 > 0 || amount1 > 0) { + (position.tokensOwed0, position.tokensOwed1) = ( + position.tokensOwed0 + uint128(amount0), + position.tokensOwed1 + uint128(amount1) + ); + } + + emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1); + } + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + // the timestamp of the current block + uint32 blockTimestamp; + // the current value of the tick accumulator, computed only if we cross an initialized tick + int56 tickCumulative; + // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick + uint160 secondsPerLiquidityCumulativeX128; + // whether we've computed and cached the above two accumulators + bool computedLatestObservation; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the global fee growth of the input token + uint256 feeGrowthGlobalX128; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + /// @inheritdoc IUniswapV3PoolActions + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external override noDelegateCall returns (int256 amount0, int256 amount1) { + unchecked { + require(amountSpecified != 0, 'AS'); + + Slot0 memory slot0Start = slot0; + + require(slot0Start.unlocked, 'LOK'); + require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + 'SPL' + ); + + slot0.unlocked = false; + + SwapCache memory cache = + SwapCache({ + liquidityStart: liquidity, + blockTimestamp: _blockTimestamp(), + feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4), + secondsPerLiquidityCumulativeX128: 0, + tickCumulative: 0, + computedLatestObservation: false + }); + + bool exactInput = amountSpecified > 0; + + SwapState memory state = + SwapState({ + amountSpecifiedRemaining: amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128, + protocolFee: 0, + liquidity: cache.liquidityStart + }); + + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord( + state.tick, + tickSpacing, + zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted + (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( + state.sqrtPriceX96, + (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + fee + ); + + if (exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256()); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256()); + } + + // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee + if (cache.feeProtocol > 0) { + uint256 delta = step.feeAmount / cache.feeProtocol; + step.feeAmount -= delta; + state.protocolFee += uint128(delta); + } + + // update global fee tracker + if (state.liquidity > 0) + state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity); + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + // check for the placeholder value, which we replace with the actual value the first time the swap + // crosses an initialized tick + if (!cache.computedLatestObservation) { + (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle( + cache.blockTimestamp, + 0, + slot0Start.tick, + slot0Start.observationIndex, + cache.liquidityStart, + slot0Start.observationCardinality + ); + cache.computedLatestObservation = true; + } + int128 liquidityNet = + ticks.cross( + step.tickNext, + (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128), + (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128), + cache.secondsPerLiquidityCumulativeX128, + cache.tickCumulative, + cache.blockTimestamp + ); + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + } + + // update tick and write an oracle entry if the tick change + if (state.tick != slot0Start.tick) { + (uint16 observationIndex, uint16 observationCardinality) = + observations.write( + slot0Start.observationIndex, + cache.blockTimestamp, + slot0Start.tick, + cache.liquidityStart, + slot0Start.observationCardinality, + slot0Start.observationCardinalityNext + ); + (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = ( + state.sqrtPriceX96, + state.tick, + observationIndex, + observationCardinality + ); + } else { + // otherwise just update the price + slot0.sqrtPriceX96 = state.sqrtPriceX96; + } + + // update liquidity if it changed + if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity; + + // update fee growth global and, if necessary, protocol fees + // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees + if (zeroForOne) { + feeGrowthGlobal0X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee; + } else { + feeGrowthGlobal1X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee; + } + + (amount0, amount1) = zeroForOne == exactInput + ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated) + : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining); + + // do the transfers and collect payment + if (zeroForOne) { + if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1)); + + uint256 balance0Before = balance0(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA'); + } else { + if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0)); + + uint256 balance1Before = balance1(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA'); + } + + emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick); + slot0.unlocked = true; + } + } + + /// @inheritdoc IUniswapV3PoolActions + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external override lock noDelegateCall { + unchecked { + uint128 _liquidity = liquidity; + require(_liquidity > 0, 'L'); + + uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6); + uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6); + uint256 balance0Before = balance0(); + uint256 balance1Before = balance1(); + + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; + + if (paid0 > 0) { + uint8 feeProtocol0 = slot0.feeProtocol % 16; + uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0; + if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0); + feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity); + } + if (paid1 > 0) { + uint8 feeProtocol1 = slot0.feeProtocol >> 4; + uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1; + if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1); + feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity); + } + + emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); + } + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner { + require( + (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) && + (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)) + ); + uint8 feeProtocolOld = slot0.feeProtocol; + slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4); + emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1); + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) { + amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested; + amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested; + + if (amount0 > 0) { + if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings + protocolFees.token0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings + protocolFees.token1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit CollectProtocol(msg.sender, recipient, amount0, amount1); + } +} + + +// File contracts/test/MockTimeUniswapV3Pool.sol + + +pragma solidity >=0.0; + +// used for testing time dependent behavior +contract MockTimeUniswapV3Pool is UniswapV3Pool { + // Monday, October 5, 2020 9:00:00 AM GMT-05:00 + uint256 public time = 1601906400; + + function setFeeGrowthGlobal0X128(uint256 _feeGrowthGlobal0X128) external { + feeGrowthGlobal0X128 = _feeGrowthGlobal0X128; + } + + function setFeeGrowthGlobal1X128(uint256 _feeGrowthGlobal1X128) external { + feeGrowthGlobal1X128 = _feeGrowthGlobal1X128; + } + + function advanceTime(uint256 by) external { + time += by; + } + + function _blockTimestamp() internal view override returns (uint32) { + return uint32(time); + } +} + + +// File contracts/test/MockTimeUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract MockTimeUniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + Parameters public override parameters; + + event PoolDeployed(address pool); + + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) external returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address( + new MockTimeUniswapV3Pool{salt: keccak256(abi.encodePacked(token0, token1, fee, tickSpacing))}() + ); + emit PoolDeployed(pool); + delete parameters; + } +} + + +// File contracts/test/NoDelegateCallTest.sol + + +pragma solidity >=0.0; + +contract NoDelegateCallTest is NoDelegateCall { + function canBeDelegateCalled() public view returns (uint256) { + return block.timestamp / 5; + } + + function cannotBeDelegateCalled() public view noDelegateCall returns (uint256) { + return block.timestamp / 5; + } + + function getGasCostOfCanBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + canBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function getGasCostOfCannotBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + cannotBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function callsIntoNoDelegateCallFunction() external view { + noDelegateCallPrivate(); + } + + function noDelegateCallPrivate() private view noDelegateCall {} +} + + +// File contracts/test/OracleTest.sol + + +pragma solidity >=0.0; +pragma abicoder v2; + +contract OracleTest { + using Oracle for Oracle.Observation[65535]; + + Oracle.Observation[65535] public observations; + + uint32 public time; + int24 public tick; + uint128 public liquidity; + uint16 public index; + uint16 public cardinality; + uint16 public cardinalityNext; + + struct InitializeParams { + uint32 time; + int24 tick; + uint128 liquidity; + } + + function initialize(InitializeParams calldata params) external { + require(cardinality == 0, 'already initialized'); + time = params.time; + tick = params.tick; + liquidity = params.liquidity; + (cardinality, cardinalityNext) = observations.initialize(params.time); + } + + function advanceTime(uint32 by) public { + unchecked { + time += by; + } + } + + struct UpdateParams { + uint32 advanceTimeBy; + int24 tick; + uint128 liquidity; + } + + // write an observation, then change tick and liquidity + function update(UpdateParams calldata params) external { + advanceTime(params.advanceTimeBy); + (index, cardinality) = observations.write(index, time, tick, liquidity, cardinality, cardinalityNext); + tick = params.tick; + liquidity = params.liquidity; + } + + function batchUpdate(UpdateParams[] calldata params) external { + // sload everything + int24 _tick = tick; + uint128 _liquidity = liquidity; + uint16 _index = index; + uint16 _cardinality = cardinality; + uint16 _cardinalityNext = cardinalityNext; + uint32 _time = time; + + for (uint256 i = 0; i < params.length; i++) { + _time += params[i].advanceTimeBy; + (_index, _cardinality) = observations.write( + _index, + _time, + _tick, + _liquidity, + _cardinality, + _cardinalityNext + ); + _tick = params[i].tick; + _liquidity = params[i].liquidity; + } + + // sstore everything + tick = _tick; + liquidity = _liquidity; + index = _index; + cardinality = _cardinality; + time = _time; + } + + function grow(uint16 _cardinalityNext) external { + cardinalityNext = observations.grow(cardinalityNext, _cardinalityNext); + } + + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return observations.observe(time, secondsAgos, tick, index, liquidity, cardinality); + } + + function getGasCostOfObserve(uint32[] calldata secondsAgos) external view returns (uint256) { + (uint32 _time, int24 _tick, uint128 _liquidity, uint16 _index) = (time, tick, liquidity, index); + uint256 gasBefore = gasleft(); + observations.observe(_time, secondsAgos, _tick, _index, _liquidity, cardinality); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/OracleEchidnaTest.sol + + +pragma solidity >=0.0; + +contract OracleEchidnaTest { + OracleTest private oracle; + + bool private initialized; + uint32 private timePassed; + + constructor() { + oracle = new OracleTest(); + } + + function initialize( + uint32 time, + int24 tick, + uint128 liquidity + ) external { + oracle.initialize(OracleTest.InitializeParams({time: time, tick: tick, liquidity: liquidity})); + initialized = true; + } + + function limitTimePassed(uint32 by) private { + require(timePassed + by >= timePassed); + timePassed += by; + } + + function advanceTime(uint32 by) public { + limitTimePassed(by); + oracle.advanceTime(by); + } + + // write an observation, then change tick and liquidity + function update( + uint32 advanceTimeBy, + int24 tick, + uint128 liquidity + ) external { + limitTimePassed(advanceTimeBy); + oracle.update(OracleTest.UpdateParams({advanceTimeBy: advanceTimeBy, tick: tick, liquidity: liquidity})); + } + + function grow(uint16 cardinality) external { + oracle.grow(cardinality); + } + + function checkTimeWeightedResultAssertions(uint32 secondsAgo0, uint32 secondsAgo1) private view { + require(secondsAgo0 != secondsAgo1); + require(initialized); + // secondsAgo0 should be the larger one + if (secondsAgo0 < secondsAgo1) (secondsAgo0, secondsAgo1) = (secondsAgo1, secondsAgo0); + + uint32 timeElapsed = secondsAgo0 - secondsAgo1; + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo0; + secondsAgos[1] = secondsAgo1; + + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + int56 timeWeightedTick = (tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(timeElapsed)); + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(timeElapsed) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + assert(timeWeightedTick <= type(int24).max); + assert(timeWeightedTick >= type(int24).min); + } + + function echidna_indexAlwaysLtCardinality() external view returns (bool) { + return oracle.index() < oracle.cardinality() || !initialized; + } + + function echidna_AlwaysInitialized() external view returns (bool) { + (, , , bool isInitialized) = oracle.observations(0); + return oracle.cardinality() == 0 || isInitialized; + } + + function echidna_cardinalityAlwaysLteNext() external view returns (bool) { + return oracle.cardinality() <= oracle.cardinalityNext(); + } + + function echidna_canAlwaysObserve0IfInitialized() external view returns (bool) { + if (!initialized) { + return true; + } + uint32[] memory arr = new uint32[](1); + arr[0] = 0; + (bool success, ) = address(oracle).staticcall(abi.encodeWithSelector(OracleTest.observe.selector, arr)); + return success; + } + + function checkTwoAdjacentObservationsTickCumulativeModTimeElapsedAlways0(uint16 index) external view { + uint16 cardinality = oracle.cardinality(); + // check that the observations are initialized, and that the index is not the oldest observation + require(index < cardinality && index != (oracle.index() + 1) % cardinality); + + (uint32 blockTimestamp0, int56 tickCumulative0, , bool initialized0) = + oracle.observations(index == 0 ? cardinality - 1 : index - 1); + (uint32 blockTimestamp1, int56 tickCumulative1, , bool initialized1) = oracle.observations(index); + + require(initialized0); + require(initialized1); + + uint32 timeElapsed = blockTimestamp1 - blockTimestamp0; + assert(timeElapsed > 0); + assert((tickCumulative1 - tickCumulative0) % int56(uint56(timeElapsed)) == 0); + } + + function checkTimeWeightedAveragesAlwaysFitsType(uint32 secondsAgo) external view { + require(initialized); + require(secondsAgo > 0); + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + + // compute the time weighted tick, rounded towards negative infinity + int56 numerator = tickCumulatives[1] - tickCumulatives[0]; + int56 timeWeightedTick = numerator / int56(uint56(secondsAgo)); + if (numerator < 0 && numerator % int56(uint56(secondsAgo)) != 0) { + timeWeightedTick--; + } + + // the time weighted averages fit in their respective accumulated types + assert(timeWeightedTick <= type(int24).max && timeWeightedTick >= type(int24).min); + + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(secondsAgo) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + } +} + + +// File contracts/test/SqrtPriceMathEchidnaTest.sol + + +pragma solidity >=0.0; + + + +contract SqrtPriceMathEchidnaTest { + function mulDivRoundingUpInvariants( + uint256 x, + uint256 y, + uint256 z + ) external pure { + require(z > 0); + uint256 notRoundedUp = FullMath.mulDiv(x, y, z); + uint256 roundedUp = FullMath.mulDivRoundingUp(x, y, z); + assert(roundedUp >= notRoundedUp); + assert(roundedUp - notRoundedUp < 2); + if (roundedUp - notRoundedUp == 1) { + assert(mulmod(x, y, z) > 0); + } else { + assert(mulmod(x, y, z) == 0); + } + } + + function getNextSqrtPriceFromInputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true)); + } else { + assert(sqrtQ >= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true)); + } + } + + function getNextSqrtPriceFromOutputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + } else { + assert(sqrtQ > 0); // this has to be true, otherwise we need another require + assert(sqrtQ >= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + } + } + + function getNextSqrtPriceFromAmount0RoundingUpInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 <= sqrtPX96); + } else { + assert(sqrtQX96 >= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getNextSqrtPriceFromAmount1RoundingDownInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 >= sqrtPX96); + } else { + assert(sqrtQX96 <= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getAmount0DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount0Down = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, false); + assert(amount0Down == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + + uint256 amount0Up = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true); + assert(amount0Up == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, true)); + + assert(amount0Down <= amount0Up); + // diff is 0 or 1 + assert(amount0Up - amount0Down < 2); + } + + // ensure that chained division is always equal to the full-precision case for + // liquidity * (sqrt(P) - sqrt(Q)) / (sqrt(P) * sqrt(Q)) + function getAmount0DeltaEquivalency( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity, + bool roundUp + ) external pure { + require(sqrtP >= sqrtQ); + require(sqrtP > 0 && sqrtQ > 0); + require((sqrtP * sqrtQ) / sqrtP == sqrtQ); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtP - sqrtQ; + uint256 denominator = uint256(sqrtP) * sqrtQ; + + uint256 safeResult = + roundUp + ? FullMath.mulDivRoundingUp(numerator1, numerator2, denominator) + : FullMath.mulDiv(numerator1, numerator2, denominator); + uint256 fullResult = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, roundUp); + + assert(safeResult == fullResult); + } + + function getAmount1DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount1Down = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, false); + assert(amount1Down == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + + uint256 amount1Up = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true); + assert(amount1Up == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, true)); + + assert(amount1Down <= amount1Up); + // diff is 0 or 1 + assert(amount1Up - amount1Down < 2); + } + + function getAmount0DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity); + if (liquidity < 0) assert(amount0 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount0 == 0); + else assert(amount0 > 0); + } + if (liquidity == 0) assert(amount0 == 0); + } + + function getAmount1DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity); + if (liquidity < 0) assert(amount1 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount1 == 0); + else assert(amount1 > 0); + } + if (liquidity == 0) assert(amount1 == 0); + } + + function getOutOfRangeMintInvariants( + uint160 sqrtA, + uint160 sqrtB, + int128 liquidity + ) external pure { + require(sqrtA > 0 && sqrtB > 0); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtA, sqrtB, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtA, sqrtB, liquidity); + + if (sqrtA == sqrtB) { + assert(amount0 == 0); + assert(amount1 == 0); + } else { + assert(amount0 > 0); + assert(amount1 > 0); + } + } + + function getInRangeMintInvariants( + uint160 sqrtLower, + uint160 sqrtCurrent, + uint160 sqrtUpper, + int128 liquidity + ) external pure { + require(sqrtLower > 0); + require(sqrtLower < sqrtUpper); + require(sqrtLower <= sqrtCurrent && sqrtCurrent <= sqrtUpper); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtCurrent, sqrtUpper, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtCurrent, liquidity); + + assert(amount0 > 0 || amount1 > 0); + } +} + + +// File contracts/test/SqrtPriceMathTest.sol + + +pragma solidity >=0.0; + +contract SqrtPriceMathTest { + function getNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + return gasBefore - gasleft(); + } + + function getNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + return gasBefore - gasleft(); + } + + function getAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount0) { + return SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount1) { + return SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getGasCostOfGetAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } + + function getGasCostOfGetAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/SwapMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract SwapMathEchidnaTest { + function checkComputeSwapStepInvariants( + uint160 sqrtPriceRaw, + uint160 sqrtPriceTargetRaw, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external pure { + require(sqrtPriceRaw > 0); + require(sqrtPriceTargetRaw > 0); + require(feePips > 0); + require(feePips < 1e6); + + (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = + SwapMath.computeSwapStep(sqrtPriceRaw, sqrtPriceTargetRaw, liquidity, amountRemaining, feePips); + + assert(amountIn <= type(uint256).max - feeAmount); + + if (amountRemaining < 0) { + assert(amountOut <= uint256(-amountRemaining)); + } else { + assert(amountIn + feeAmount <= uint256(amountRemaining)); + } + + if (sqrtPriceRaw == sqrtPriceTargetRaw) { + assert(amountIn == 0); + assert(amountOut == 0); + assert(feeAmount == 0); + assert(sqrtQ == sqrtPriceTargetRaw); + } + + // didn't reach price target, entire amount must be consumed + if (sqrtQ != sqrtPriceTargetRaw) { + if (amountRemaining < 0) assert(amountOut == uint256(-amountRemaining)); + else assert(amountIn + feeAmount == uint256(amountRemaining)); + } + + // next price is between price and price target + if (sqrtPriceTargetRaw <= sqrtPriceRaw) { + assert(sqrtQ <= sqrtPriceRaw); + assert(sqrtQ >= sqrtPriceTargetRaw); + } else { + assert(sqrtQ >= sqrtPriceRaw); + assert(sqrtQ <= sqrtPriceTargetRaw); + } + } +} + + +// File contracts/test/SwapMathTest.sol + + +pragma solidity >=0.0; + +contract SwapMathTest { + function computeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + external + pure + returns ( + uint160 sqrtQ, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + return SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + } + + function getGasCostOfComputeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/TestERC20.sol + + +pragma solidity >=0.0; + +contract TestERC20 is IERC20Minimal { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + + constructor(uint256 amountToMint) { + mint(msg.sender, amountToMint); + } + + function mint(address to, uint256 amount) public { + uint256 balanceNext = balanceOf[to] + amount; + require(balanceNext >= amount, 'overflow balance'); + balanceOf[to] = balanceNext; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + uint256 balanceBefore = balanceOf[msg.sender]; + require(balanceBefore >= amount, 'insufficient balance'); + balanceOf[msg.sender] = balanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow'); + balanceOf[recipient] = balanceRecipient + amount; + + emit Transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external override returns (bool) { + uint256 allowanceBefore = allowance[sender][msg.sender]; + require(allowanceBefore >= amount, 'allowance insufficient'); + + allowance[sender][msg.sender] = allowanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient'); + balanceOf[recipient] = balanceRecipient + amount; + uint256 balanceSender = balanceOf[sender]; + require(balanceSender >= amount, 'underflow balance sender'); + balanceOf[sender] = balanceSender - amount; + + emit Transfer(sender, recipient, amount); + return true; + } +} + + +// File contracts/test/TestUniswapV3Callee.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Callee is IUniswapV3MintCallback, IUniswapV3SwapCallback, IUniswapV3FlashCallback { + using SafeCast for uint256; + + function swapExact0For1( + address pool, + uint256 amount0In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, amount0In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap0ForExact1( + address pool, + uint256 amount1Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, -amount1Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapExact1For0( + address pool, + uint256 amount1In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, amount1In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap1ForExact0( + address pool, + uint256 amount0Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, -amount0Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapToLowerSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + function swapToHigherSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit SwapCallback(amount0Delta, amount1Delta); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } else { + // if both are not gt 0, both must be 0. + assert(amount0Delta == 0 && amount1Delta == 0); + } + } + + function mint( + address pool, + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external { + IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender)); + } + + event MintCallback(uint256 amount0Owed, uint256 amount1Owed); + + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit MintCallback(amount0Owed, amount1Owed); + if (amount0Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed); + if (amount1Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed); + } + + event FlashCallback(uint256 fee0, uint256 fee1); + + function flash( + address pool, + address recipient, + uint256 amount0, + uint256 amount1, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).flash(recipient, amount0, amount1, abi.encode(msg.sender, pay0, pay1)); + } + + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + emit FlashCallback(fee0, fee1); + + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, pay0); + if (pay1 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, pay1); + } +} + + +// File contracts/test/TestUniswapV3ReentrantCallee.sol + + +pragma solidity >=0.0; + +contract TestUniswapV3ReentrantCallee is IUniswapV3SwapCallback { + string private constant expectedReason = 'LOK'; + + function swapToReenter(address pool) external { + IUniswapV3Pool(pool).swap(address(0), false, 1, TickMath.MAX_SQRT_RATIO - 1, new bytes(0)); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata + ) external override { + // try to reenter swap + try IUniswapV3Pool(msg.sender).swap(address(0), false, 1, 0, new bytes(0)) {} catch Error( + string memory reason + ) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter mint + try IUniswapV3Pool(msg.sender).mint(address(0), 0, 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collect + try IUniswapV3Pool(msg.sender).collect(address(0), 0, 0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter burn + try IUniswapV3Pool(msg.sender).burn(0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter flash + try IUniswapV3Pool(msg.sender).flash(address(0), 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collectProtocol + try IUniswapV3Pool(msg.sender).collectProtocol(address(0), 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + require(false, 'Unable to reenter'); + } +} + + +// File contracts/test/TestUniswapV3Router.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Router is IUniswapV3SwapCallback { + using SafeCast for uint256; + + // flash swaps for an exact amount of token0 in the output pool + function swapForExact0Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount0Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + false, + -amount0Out.toInt256(), + TickMath.MAX_SQRT_RATIO - 1, + abi.encode(pools, msg.sender) + ); + } + + // flash swaps for an exact amount of token1 in the output pool + function swapForExact1Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount1Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + true, + -amount1Out.toInt256(), + TickMath.MIN_SQRT_RATIO + 1, + abi.encode(pools, msg.sender) + ); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) public override { + emit SwapCallback(amount0Delta, amount1Delta); + + (address[] memory pools, address payer) = abi.decode(data, (address[], address)); + + if (pools.length == 1) { + // get the address and amount of the token that we need to pay + address tokenToBePaid = + amount0Delta > 0 ? IUniswapV3Pool(msg.sender).token0() : IUniswapV3Pool(msg.sender).token1(); + int256 amountToBePaid = amount0Delta > 0 ? amount0Delta : amount1Delta; + + bool zeroForOne = tokenToBePaid == IUniswapV3Pool(pools[0]).token1(); + IUniswapV3Pool(pools[0]).swap( + msg.sender, + zeroForOne, + -amountToBePaid, + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + abi.encode(new address[](0), payer) + ); + } else { + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom( + payer, + msg.sender, + uint256(amount0Delta) + ); + } else { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom( + payer, + msg.sender, + uint256(amount1Delta) + ); + } + } + } +} + + +// File contracts/test/TestUniswapV3SwapPay.sol + + +pragma solidity >=0.0; + + +contract TestUniswapV3SwapPay is IUniswapV3SwapCallback { + function swap( + address pool, + address recipient, + bool zeroForOne, + uint160 sqrtPriceX96, + int256 amountSpecified, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).swap( + recipient, + zeroForOne, + amountSpecified, + sqrtPriceX96, + abi.encode(msg.sender, pay0, pay1) + ); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata data + ) external override { + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(pay0)); + } else if (pay1 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(pay1)); + } + } +} + + +// File contracts/test/TickBitmapEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapEchidnaTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) private bitmap; + + // returns whether the given tick is initialized + function isInitialized(int24 tick) private view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } + + function flipTick(int24 tick) external { + bool before = isInitialized(tick); + bitmap.flipTick(tick, 1); + assert(isInitialized(tick) == !before); + } + + function checkNextInitializedTickWithinOneWordInvariants(int24 tick, bool lte) external view { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + if (lte) { + // type(int24).min + 256 + require(tick >= -8388352); + assert(next <= tick); + assert(tick - next < 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick; i > next; i--) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } else { + // type(int24).max - 256 + require(tick < 8388351); + assert(next > tick); + assert(next - tick <= 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick + 1; i < next; i++) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } + } +} + + +// File contracts/test/TickBitmapTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) public bitmap; + + function flipTick(int24 tick) external { + bitmap.flipTick(tick, 1); + } + + function getGasCostOfFlipTick(int24 tick) external returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.flipTick(tick, 1); + return gasBefore - gasleft(); + } + + function nextInitializedTickWithinOneWord(int24 tick, bool lte) + external + view + returns (int24 next, bool initialized) + { + return bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + } + + function getGasCostOfNextInitializedTickWithinOneWord(int24 tick, bool lte) external view returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + return gasBefore - gasleft(); + } + + // returns whether the given tick is initialized + function isInitialized(int24 tick) external view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } +} + + +// File contracts/test/TickEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickEchidnaTest { + function checkTickSpacingToParametersInvariants(int24 tickSpacing) external pure { + require(tickSpacing <= TickMath.MAX_TICK); + require(tickSpacing > 0); + + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + + uint128 maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + + // symmetry around 0 tick + assert(maxTick == -minTick); + // positive max tick + assert(maxTick > 0); + // divisibility + assert((maxTick - minTick) % tickSpacing == 0); + + uint256 numTicks = uint256(int256((maxTick - minTick) / tickSpacing)) + 1; + // max liquidity at every tick is less than the cap + assert(uint256(maxLiquidityPerTick) * numTicks <= type(uint128).max); + } +} + + +// File contracts/test/TickMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickMathEchidnaTest { + // uniqueness and increasing order + function checkGetSqrtRatioAtTickInvariants(int24 tick) external pure { + uint160 ratio = TickMath.getSqrtRatioAtTick(tick); + assert(TickMath.getSqrtRatioAtTick(tick - 1) < ratio && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(ratio >= TickMath.MIN_SQRT_RATIO); + assert(ratio <= TickMath.MAX_SQRT_RATIO); + } + + // the ratio is always between the returned tick and the returned tick+1 + function checkGetTickAtSqrtRatioInvariants(uint160 ratio) external pure { + int24 tick = TickMath.getTickAtSqrtRatio(ratio); + assert(ratio >= TickMath.getSqrtRatioAtTick(tick) && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(tick >= TickMath.MIN_TICK); + assert(tick < TickMath.MAX_TICK); + } +} + + +// File contracts/test/TickMathTest.sol + + +pragma solidity >=0.0; + +contract TickMathTest { + function getSqrtRatioAtTick(int24 tick) external pure returns (uint160) { + return TickMath.getSqrtRatioAtTick(tick); + } + + function getGasCostOfGetSqrtRatioAtTick(int24 tick) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getSqrtRatioAtTick(tick); + return gasBefore - gasleft(); + } + + function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure returns (int24) { + return TickMath.getTickAtSqrtRatio(sqrtPriceX96); + } + + function getGasCostOfGetTickAtSqrtRatio(uint160 sqrtPriceX96) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getTickAtSqrtRatio(sqrtPriceX96); + return gasBefore - gasleft(); + } + + function MIN_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MIN_SQRT_RATIO; + } + + function MAX_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MAX_SQRT_RATIO; + } +} + + +// File contracts/test/TickOverflowSafetyEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickOverflowSafetyEchidnaTest { + using Tick for mapping(int24 => Tick.Info); + + int24 private constant MIN_TICK = -16; + int24 private constant MAX_TICK = 16; + uint128 private constant MAX_LIQUIDITY = type(uint128).max / 32; + + mapping(int24 => Tick.Info) private ticks; + int24 private tick = 0; + + // used to track how much total liquidity has been added. should never be negative + int256 totalLiquidity = 0; + // half the cap of fee growth has happened, this can overflow + uint256 private feeGrowthGlobal0X128 = type(uint256).max / 2; + uint256 private feeGrowthGlobal1X128 = type(uint256).max / 2; + // how much total growth has happened, this cannot overflow + uint256 private totalGrowth0 = 0; + uint256 private totalGrowth1 = 0; + + function increaseFeeGrowthGlobal0X128(uint256 amount) external { + require(totalGrowth0 + amount > totalGrowth0); // overflow check + feeGrowthGlobal0X128 += amount; // overflow desired + totalGrowth0 += amount; + } + + function increaseFeeGrowthGlobal1X128(uint256 amount) external { + require(totalGrowth1 + amount > totalGrowth1); // overflow check + feeGrowthGlobal1X128 += amount; // overflow desired + totalGrowth1 += amount; + } + + function setPosition( + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta + ) external { + require(tickLower > MIN_TICK); + require(tickUpper < MAX_TICK); + require(tickLower < tickUpper); + bool flippedLower = + ticks.update( + tickLower, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + false, + MAX_LIQUIDITY + ); + bool flippedUpper = + ticks.update( + tickUpper, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + true, + MAX_LIQUIDITY + ); + + if (flippedLower) { + if (liquidityDelta < 0) { + assert(ticks[tickLower].liquidityGross == 0); + ticks.clear(tickLower); + } else assert(ticks[tickLower].liquidityGross > 0); + } + + if (flippedUpper) { + if (liquidityDelta < 0) { + assert(ticks[tickUpper].liquidityGross == 0); + ticks.clear(tickUpper); + } else assert(ticks[tickUpper].liquidityGross > 0); + } + + totalLiquidity += liquidityDelta; + // requires should have prevented this + assert(totalLiquidity >= 0); + + if (totalLiquidity == 0) { + totalGrowth0 = 0; + totalGrowth1 = 0; + } + } + + function moveToTick(int24 target) external { + require(target > MIN_TICK); + require(target < MAX_TICK); + while (tick != target) { + if (tick < target) { + if (ticks[tick + 1].liquidityGross > 0) + ticks.cross(tick + 1, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick++; + } else { + if (ticks[tick].liquidityGross > 0) + ticks.cross(tick, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick--; + } + } + } +} + + +// File contracts/test/TickTest.sol + + +pragma solidity >=0.0; + +contract TickTest { + using Tick for mapping(int24 => Tick.Info); + + mapping(int24 => Tick.Info) public ticks; + + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) external pure returns (uint128) { + return Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + } + + function setTick(int24 tick, Tick.Info memory info) external { + ticks[tick] = info; + } + + function getFeeGrowthInside( + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) external view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + return ticks.getFeeGrowthInside(tickLower, tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + } + + function update( + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) external returns (bool flipped) { + return + ticks.update( + tick, + tickCurrent, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + upper, + maxLiquidity + ); + } + + function clear(int24 tick) external { + ticks.clear(tick); + } + + function cross( + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) external returns (int128 liquidityNet) { + return + ticks.cross( + tick, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time + ); + } +} + + +// File contracts/test/UniswapV3PoolSwapTest.sol + + +pragma solidity >=0.0; + + +contract UniswapV3PoolSwapTest is IUniswapV3SwapCallback { + int256 private _amount0Delta; + int256 private _amount1Delta; + + function getSwapResult( + address pool, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96 + ) + external + returns ( + int256 amount0Delta, + int256 amount1Delta, + uint160 nextSqrtRatio + ) + { + (amount0Delta, amount1Delta) = IUniswapV3Pool(pool).swap( + address(0), + zeroForOne, + amountSpecified, + sqrtPriceLimitX96, + abi.encode(msg.sender) + ); + + (nextSqrtRatio, , , , , , ) = IUniswapV3Pool(pool).slot0(); + } + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } + } +} + + +// File contracts/test/UnsafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract UnsafeMathEchidnaTest { + function checkDivRoundingUp(uint256 x, uint256 d) external pure { + require(d > 0); + uint256 z = UnsafeMath.divRoundingUp(x, d); + uint256 diff = z - (x / d); + if (x % d == 0) { + assert(diff == 0); + } else { + assert(diff == 1); + } + } +} + + +// File contracts/UniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + /// @inheritdoc IUniswapV3PoolDeployer + Parameters public override parameters; + + /// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then + /// clearing it after deploying the pool. + /// @param factory The contract address of the Uniswap V3 factory + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The spacing between usable ticks + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) internal returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); + delete parameters; + } +} + + +// File contracts/UniswapV3Factory.sol + + +pragma solidity >=0.0; + + +/// @title Canonical Uniswap V3 factory +/// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees +contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall { + /// @inheritdoc IUniswapV3Factory + address public override owner; + + /// @inheritdoc IUniswapV3Factory + mapping(uint24 => int24) public override feeAmountTickSpacing; + /// @inheritdoc IUniswapV3Factory + mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; + + constructor() { + owner = msg.sender; + emit OwnerChanged(address(0), msg.sender); + + feeAmountTickSpacing[500] = 10; + emit FeeAmountEnabled(500, 10); + feeAmountTickSpacing[3000] = 60; + emit FeeAmountEnabled(3000, 60); + feeAmountTickSpacing[10000] = 200; + emit FeeAmountEnabled(10000, 200); + } + + /// @inheritdoc IUniswapV3Factory + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) public override noDelegateCall returns (address pool) { + require(tokenA != tokenB); + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0)); + int24 tickSpacing = feeAmountTickSpacing[fee]; + require(tickSpacing != 0); + require(getPool[token0][token1][fee] == address(0)); + pool = deploy(address(this), token0, token1, fee, tickSpacing); + getPool[token0][token1][fee] = pool; + // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses + getPool[token1][token0][fee] = pool; + emit PoolCreated(token0, token1, fee, tickSpacing, pool); + } + + /// @inheritdoc IUniswapV3Factory + function setOwner(address _owner) external override { + require(msg.sender == owner); + emit OwnerChanged(owner, _owner); + owner = _owner; + } + + /// @inheritdoc IUniswapV3Factory + function enableFeeAmount(uint24 fee, int24 tickSpacing) public override { + require(msg.sender == owner); + require(fee < 1000000); + // tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that + // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick + // 16384 ticks represents a >5x price change with ticks of 1 bips + require(tickSpacing > 0 && tickSpacing < 16384); + require(feeAmountTickSpacing[fee] == 0); + + feeAmountTickSpacing[fee] = tickSpacing; + emit FeeAmountEnabled(fee, tickSpacing); + } + + function runTest() public { + TestERC20 t1 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000001}(1000000000); + TestERC20 t2 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000002}(1000000000); + IUniswapV3Pool pool = IUniswapV3Pool(createPool(address(t1), address(t2), 500)); + + TestUniswapV3Callee test = new TestUniswapV3Callee(); + t1.approve(address(test), 1000000000); + t2.approve(address(test), 1000000000); + pool.initialize(761446703485210103287273052203988822378723970342); + + test.mint(address(pool), address(this), int24(-1000), int24(1000), uint128(1000)); + + uint256 beforeSwap = t1.balanceOf(address(this)); + test.swapExact0For1(address(pool), 10, address(this), 4295128740); + uint256 afterSwap = t1.balanceOf(address(this)); + + require(beforeSwap != afterSwap, 'WRO'); + } +} + +// ==== +// compileViaYul: true +// compileToEOF: false +// ---- +// runTest() -> +// ~ emit PoolCreated(address,address,uint24,int24,address): #0x268c98cd4338d80f8a149b8d7bd8944dba9f5d7c, #0xe4b1482254b07c39df789bde7c8bab4edc34e459, #0x01f4, 0x0a, 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88 +// ~ emit Approval(address,address,uint256) from 0x268c98cd4338d80f8a149b8d7bd8944dba9f5d7c: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x3b9aca00 +// ~ emit Approval(address,address,uint256) from 0xe4b1482254b07c39df789bde7c8bab4edc34e459: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x3b9aca00 +// ~ emit Initialize(uint160,int24) from 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88: 0x85607379ff6f79edb3e272aaeae79d5263988d26, 0x0d56f8 +// ~ emit MintCallback(uint256,uint256) from 0x137aa4dfc0911524504fcd4d98501f179bc13b4a: 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe4b1482254b07c39df789bde7c8bab4edc34e459: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88, 0x65 +// ~ emit Mint(address,address,int24,int24,uint128,uint256,uint256) from 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18, #0x03e8, 0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x03e8, 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe4b1482254b07c39df789bde7c8bab4edc34e459: #0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88, #0xc06afe3a8444fc0004668591e8306bfb9968e79e, 0x09 +// ~ emit SwapCallback(int256,int256) from 0x137aa4dfc0911524504fcd4d98501f179bc13b4a: 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7 +// ~ emit Transfer(address,address,uint256) from 0x268c98cd4338d80f8a149b8d7bd8944dba9f5d7c: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88, 0x0a +// ~ emit Swap(address,address,int256,int256,uint160,uint128,int24) from 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88: #0x137aa4dfc0911524504fcd4d98501f179bc13b4a, #0xc06afe3a8444fc0004668591e8306bfb9968e79e, 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7, 0x010a9a2fd9e126942e9c978d07, 0x03e8, 0x032b diff --git a/test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol b/test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol new file mode 100644 index 000000000000..61b8a340ec42 --- /dev/null +++ b/test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol @@ -0,0 +1,5191 @@ +// Sources flattened with hardhat v2.2.0 https://hardhat.org + +// File contracts/interfaces/pool/IUniswapV3PoolImmutables.sol + + +pragma solidity >=0.0; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions(bytes32 key) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations(uint256 index) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @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. + /// @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) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolActions.sol + + +pragma solidity >=0.0; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} + + +// File contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol + + +pragma solidity >=0.0; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolEvents.sol + + +pragma solidity >=0.0; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/IUniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} + + +// File contracts/libraries/FullMath.sol + + +pragma solidity >=0.0; + +/// @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 = uint256(-int256(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++; + } + } + } +} + + +// File contracts/libraries/FixedPoint128.sol + + +pragma solidity >=0.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; +} + + +// File contracts/libraries/LiquidityMath.sol + + +pragma solidity >=0.0; + +/// @title Math library for liquidity +library LiquidityMath { + /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows + /// @param x The liquidity before change + /// @param y The delta by which liquidity should be changed + /// @return z The liquidity delta + function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { + if (y < 0) { + unchecked { + require((z = x - uint128(-y)) < x, 'LS'); + } + } else { + unchecked { + require((z = x + uint128(y)) >= x, 'LA'); + } + } + } +} + + +// File contracts/libraries/Position.sol + + +pragma solidity >=0.0; + + + +/// @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 { + // info stored for each user's position + struct Info { + // the amount of liquidity owned by this position + uint128 liquidity; + // fee growth per unit of liquidity as of the last update to liquidity or fees owed + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // the fees owed to the position owner in token0/token1 + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @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 => Info) storage self, + address owner, + int24 tickLower, + int24 tickUpper + ) internal view returns (Position.Info storage position) { + position = self[keccak256(abi.encodePacked(owner, 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( + Info storage self, + int128 liquidityDelta, + uint256 feeGrowthInside0X128, + uint256 feeGrowthInside1X128 + ) internal { + unchecked { + Info memory _self = self; + + uint128 liquidityNext; + if (liquidityDelta == 0) { + require(_self.liquidity > 0, 'NP'); // disallow pokes for 0 liquidity positions + liquidityNext = _self.liquidity; + } else { + liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta); + } + + // calculate accumulated fees + uint128 tokensOwed0 = + uint128( + FullMath.mulDiv( + feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + uint128 tokensOwed1 = + uint128( + FullMath.mulDiv( + feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + + // update the position + if (liquidityDelta != 0) self.liquidity = liquidityNext; + self.feeGrowthInside0LastX128 = feeGrowthInside0X128; + self.feeGrowthInside1LastX128 = feeGrowthInside1X128; + if (tokensOwed0 > 0 || tokensOwed1 > 0) { + // overflow is acceptable, have to withdraw before you hit type(uint128).max fees + self.tokensOwed0 += tokensOwed0; + self.tokensOwed1 += tokensOwed1; + } + } + } +} + + +// File contracts/libraries/LowGasSafeMath.sol + + +pragma solidity >=0.0; + +/// @title Optimized overflow and underflow safe math operations +/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost +library LowGasSafeMath { + /// @notice Returns x + y, reverts if sum overflows uint256 + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + + /// @notice Returns x - y, reverts if underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + + /// @notice Returns x * y, reverts if overflows + /// @param x The multiplicand + /// @param y The multiplier + /// @return z The product of x and y + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(x == 0 || (z = x * y) / x == y); + } + + /// @notice Returns x + y, reverts if overflows or underflows + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x == (y >= 0)); + } + + /// @notice Returns x - y, reverts if overflows or underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x - y) <= x == (y >= 0)); + } +} + + +// File contracts/libraries/SafeCast.sol + + +pragma solidity >=0.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); + } +} + + +// File contracts/libraries/UnsafeMath.sol + + +pragma solidity >=0.0; + +/// @title Math functions that do not check inputs or outputs +/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks +library UnsafeMath { + /// @notice Returns ceil(x / y) + /// @dev division by 0 has unspecified behavior, and must be checked externally + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, ceil(x / y) + function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := add(div(x, y), gt(mod(x, y), 0)) + } + } +} + + +// File contracts/libraries/FixedPoint96.sol + + +pragma solidity >=0.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; +} + + +// File contracts/libraries/SqrtPriceMath.sol + + +pragma solidity >=0.0; + + + + +/// @title Functions based on Q64.96 sqrt price and liquidity +/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas +library SqrtPriceMath { + using LowGasSafeMath for uint256; + using SafeCast for uint256; + + /// @notice Gets the next sqrt price given a delta of token0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of token0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + uint256 product; + if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); + } else { + uint256 product; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + /// @notice Gets the next sqrt price given a delta of token1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of token1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = + ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return uint256(sqrtPX96).add(quotient).toUint160(); + } else { + uint256 quotient = + ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + require(sqrtPX96 > quotient); + // always fits 160 bits + return uint160(sqrtPX96 - quotient); + } + } + } + + /// @notice Gets the next sqrt price given an input amount of token0 or token1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of token0, or token1, is being swapped in + /// @param zeroForOne Whether the amount in is token0 or token1 + /// @return sqrtQX96 The price after adding the input amount to token0 or token1 + function getNextSqrtPriceFromInput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we don't pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of token0 or token1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of token0, or token1, is being swapped out + /// @param zeroForOne Whether the amount out is token0 or token1 + /// @return sqrtQX96 The price after removing the output amount of token0 or token1 + function getNextSqrtPriceFromOutput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return + roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed token0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount0) { + return + liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + + /// @notice Helper that gets signed token1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount1) { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } +} + + +// File contracts/libraries/SwapMath.sol + + +pragma solidity >=0.0; + + +/// @title Computes the result of a swap within ticks +/// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. +library SwapMath { + /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap + /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive + /// @param sqrtRatioCurrentX96 The current sqrt price of the pool + /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @param liquidity The usable liquidity + /// @param amountRemaining How much input or output amount is remaining to be swapped in/out + /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip + /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap + /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap + /// @return feeAmount The amount of input that will be taken as a fee + function computeSwapStep( + uint160 sqrtRatioCurrentX96, + uint160 sqrtRatioTargetX96, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + internal + pure + returns ( + uint160 sqrtRatioNextX96, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + bool exactIn = amountRemaining >= 0; + + if (exactIn) { + uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); + if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne + ); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); + if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + uint256(-amountRemaining), + zeroForOne + ); + } + + bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; + + // get the input/output amounts + if (zeroForOne) { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); + } else { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); + } + + // cap the output amount to not exceed the remaining output amount + if (!exactIn && amountOut > uint256(-amountRemaining)) { + amountOut = uint256(-amountRemaining); + } + + if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = uint256(amountRemaining) - amountIn; + } else { + feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); + } + } +} + + +// File contracts/libraries/TickMath.sol + + +pragma solidity >=0.0; + +/// @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 { + /// @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) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(int256(MAX_TICK)), '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; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // 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) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, '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; // 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; + } +} + + +// File contracts/libraries/Tick.sol + + +pragma solidity >=0.0; + + + +/// @title Tick +/// @notice Contains functions for managing tick processes and relevant calculations +library Tick { + using LowGasSafeMath for int256; + using SafeCast for int256; + + // info stored for each initialized individual tick + struct Info { + // the total position liquidity that references this tick + uint128 liquidityGross; + // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), + int128 liquidityNet; + // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint256 feeGrowthOutside0X128; + uint256 feeGrowthOutside1X128; + // the cumulative tick value on the other side of the tick + int56 tickCumulativeOutside; + // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint160 secondsPerLiquidityOutsideX128; + // the seconds spent on the other side of the tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint32 secondsOutside; + // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 + // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks + bool initialized; + } + + /// @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) { + 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 => Tick.Info) storage self, + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + Info storage lower = self[tickLower]; + Info storage upper = self[tickUpper]; + + // calculate fee growth below + uint256 feeGrowthBelow0X128; + uint256 feeGrowthBelow1X128; + unchecked { + if (tickCurrent >= tickLower) { + feeGrowthBelow0X128 = lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = lower.feeGrowthOutside1X128; + } else { + feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128; + } + + // 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 => Tick.Info) 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) { + Tick.Info storage info = self[tick]; + + uint128 liquidityGrossBefore = info.liquidityGross; + uint128 liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta); + + require(liquidityGrossAfter <= maxLiquidity, 'LO'); + + flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); + + if (liquidityGrossBefore == 0) { + // 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; + + // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) + info.liquidityNet = upper + ? int256(info.liquidityNet).sub(liquidityDelta).toInt128() + : int256(info.liquidityNet).add(liquidityDelta).toInt128(); + } + + /// @notice Clears tick data + /// @param self The mapping containing all initialized tick information for initialized ticks + /// @param tick The tick that will be cleared + function clear(mapping(int24 => Tick.Info) storage self, int24 tick) internal { + 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 => Tick.Info) storage self, + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) internal returns (int128 liquidityNet) { + Tick.Info storage info = self[tick]; + 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; + } +} + + +// File contracts/libraries/BitMath.sol + + +pragma solidity >=0.0; + +/// @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); + + 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); + + 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; + } +} + + +// File contracts/libraries/TickBitmap.sol + + +pragma solidity >=0.0; + +/// @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) { + wordPos = int16(tick >> 8); + bitPos = uint8(uint24(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 { + require(tick % tickSpacing == 0); // ensure that the tick is spaced + (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) { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + } + } +} + + +// File contracts/interfaces/IERC20Minimal.sol + + +pragma solidity >=0.0; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns the balance of a token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +// File contracts/libraries/TransferHelper.sol + + +pragma solidity >=0.0; + +/// @title TransferHelper +/// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false +library TransferHelper { + /// @notice Transfers tokens from msg.sender to a recipient + /// @dev Calls transfer on token contract, errors with TF if transfer fails + /// @param token The contract address of the token which will be transferred + /// @param to The recipient of the transfer + /// @param value The value of the transfer + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = + token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'TF'); + } +} + + +// File contracts/test/BitMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract BitMathEchidnaTest { + function mostSignificantBitInvariant(uint256 input) external pure { + uint8 msb = BitMath.mostSignificantBit(input); + assert(input >= (uint256(2)**msb)); + assert(msb == 255 || input < uint256(2)**(msb + 1)); + } + + function leastSignificantBitInvariant(uint256 input) external pure { + uint8 lsb = BitMath.leastSignificantBit(input); + assert(input & (uint256(2)**lsb) != 0); + assert(input & (uint256(2)**lsb - 1) == 0); + } +} + + +// File contracts/test/BitMathTest.sol + + +pragma solidity >=0.0; + +contract BitMathTest { + function mostSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.mostSignificantBit(x); + } + + function getGasCostOfMostSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.mostSignificantBit(x); + return gasBefore - gasleft(); + } + + function leastSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.leastSignificantBit(x); + } + + function getGasCostOfLeastSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.leastSignificantBit(x); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/FullMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract FullMathEchidnaTest { + function checkMulDivRounding( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + + uint256 ceiled = FullMath.mulDivRoundingUp(x, y, d); + uint256 floored = FullMath.mulDiv(x, y, d); + + if (mulmod(x, y, d) > 0) { + assert(ceiled - floored == 1); + } else { + assert(ceiled == floored); + } + } + + function checkMulDiv( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDiv(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 <= x); + assert(y2 <= y); + + assert(x - x2 < d); + assert(y - y2 < d); + } + + function checkMulDivRoundingUp( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDivRoundingUp(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 >= x); + assert(y2 >= y); + + assert(x2 - x < d); + assert(y2 - y < d); + } +} + + +// File contracts/test/FullMathTest.sol + + +pragma solidity >=0.0; + +contract FullMathTest { + function mulDiv( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDiv(x, y, z); + } + + function mulDivRoundingUp( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDivRoundingUp(x, y, z); + } +} + + +// File contracts/test/LiquidityMathTest.sol + + +pragma solidity >=0.0; + +contract LiquidityMathTest { + function addDelta(uint128 x, int128 y) external pure returns (uint128 z) { + return LiquidityMath.addDelta(x, y); + } + + function getGasCostOfAddDelta(uint128 x, int128 y) external view returns (uint256) { + uint256 gasBefore = gasleft(); + LiquidityMath.addDelta(x, y); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/LowGasSafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract LowGasSafeMathEchidnaTest { + function checkAdd(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(z >= x && z >= y); + } + + function checkSub(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(z <= x); + } + + function checkMul(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.mul(x, y); + assert(z == x * y); + assert(x == 0 || y == 0 || (z >= x && z >= y)); + } + + function checkAddi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(y < 0 ? z < x : z >= x); + } + + function checkSubi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(y < 0 ? z > x : z <= x); + } +} + + +// File contracts/NoDelegateCall.sol + + +pragma solidity >=0.0; + +/// @title Prevents delegatecall to a contract +/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract +abstract contract NoDelegateCall { + /// @dev The original address of this contract + address private immutable original; + + constructor() { + // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. + // In other words, this variable won't change when it's checked at runtime. + original = address(this); + } + + /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, + /// and the use of immutable means the address bytes are copied in every place the modifier is used. + function checkNotDelegateCall() private view { + require(address(this) == original); + } + + /// @notice Prevents delegatecall into the modified method + modifier noDelegateCall() { + checkNotDelegateCall(); + _; + } +} + + +// File contracts/libraries/Oracle.sol + + +pragma solidity >=0.0; + +/// @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 { + struct Observation { + // the block timestamp of the observation + uint32 blockTimestamp; + // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized + int56 tickCumulative; + // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized + uint160 secondsPerLiquidityCumulativeX128; + // whether or not the observation is initialized + bool initialized; + } + + /// @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) { + Observation memory last = self[index]; + + // early return if we've already written an observation this block + if (last.blockTimestamp == blockTimestamp) return (index, cardinality); + + // 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) { + require(current > 0, 'I'); + // no-op if the passed next value isn't greater than the current next value + if (next <= current) return current; + // store in each slot to prevent fresh SSTOREs in swaps + // 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 bool Whether `a` is chronologically <= `b` + function lte( + uint32 time, + uint32 a, + uint32 b + ) private pure returns (bool) { + // 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) { + uint256 l = (index + 1) % cardinality; // oldest observation + uint256 r = l + cardinality - 1; // newest observation + uint256 i; + while (true) { + i = (l + r) / 2; + + beforeOrAt = self[i % cardinality]; + + // 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); + + // 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) { + // optimistically set before to the newest observation + beforeOrAt = self[index]; + + // 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) { + // if newest observation equals target, we're in the same block, so we can ignore atOrAfter + return (beforeOrAt, atOrAfter); + } else { + // otherwise, we need to transform + return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); + } + } + + // now, set before to the oldest observation + beforeOrAt = self[(index + 1) % cardinality]; + if (!beforeOrAt.initialized) beforeOrAt = self[0]; + + // ensure that the target is chronologically at or after the oldest observation + require(lte(time, beforeOrAt.blockTimestamp, target), 'OLD'); + + // 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) { + // we're at the left boundary + return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); + } else if (target == atOrAfter.blockTimestamp) { + // we're at the right boundary + return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); + } else { + // 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) { + require(cardinality > 0, '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 + ); + } + } +} + + +// File contracts/interfaces/IUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +/// @title An interface for a contract that is capable of deploying Uniswap V3 Pools +/// @notice A contract that constructs a pool must implement this to pass arguments to the pool +/// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash +/// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain +interface IUniswapV3PoolDeployer { + /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. + /// @dev Called by the pool constructor to fetch the parameters of the pool + /// Returns factory The factory address + /// Returns token0 The first token of the pool by address sort order + /// Returns token1 The second token of the pool by address sort order + /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// Returns tickSpacing The minimum number of ticks between initialized ticks + function parameters() + external + view + returns ( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ); +} + + +// File contracts/interfaces/IUniswapV3Factory.sol + + +pragma solidity >=0.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} + + +// File contracts/interfaces/callback/IUniswapV3MintCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#mint +/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface +interface IUniswapV3MintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint. + /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity + /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3SwapCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3FlashCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#flash +/// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface +interface IUniswapV3FlashCallback { + /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. + /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param fee0 The fee amount in token0 due to the pool by the end of the flash + /// @param fee1 The fee amount in token1 due to the pool by the end of the flash + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + + +// File contracts/UniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + + + + + + + + + + + + +contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + using Tick for mapping(int24 => Tick.Info); + using TickBitmap for mapping(int16 => uint256); + using Position for mapping(bytes32 => Position.Info); + using Position for Position.Info; + using Oracle for Oracle.Observation[65535]; + + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override factory; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token0; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token1; + /// @inheritdoc IUniswapV3PoolImmutables + uint24 public immutable override fee; + + /// @inheritdoc IUniswapV3PoolImmutables + int24 public immutable override tickSpacing; + + /// @inheritdoc IUniswapV3PoolImmutables + uint128 public immutable override maxLiquidityPerTick; + + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the most-recently updated index of the observations array + uint16 observationIndex; + // the current maximum number of observations that are being stored + uint16 observationCardinality; + // the next maximum number of observations to store, triggered in observations.write + uint16 observationCardinalityNext; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + // whether the pool is locked + bool unlocked; + } + /// @inheritdoc IUniswapV3PoolState + Slot0 public override slot0; + + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal0X128; + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal1X128; + + // accumulated protocol fees in token0/token1 units + struct ProtocolFees { + uint128 token0; + uint128 token1; + } + /// @inheritdoc IUniswapV3PoolState + ProtocolFees public override protocolFees; + + /// @inheritdoc IUniswapV3PoolState + uint128 public override liquidity; + + /// @inheritdoc IUniswapV3PoolState + mapping(int24 => Tick.Info) public override ticks; + /// @inheritdoc IUniswapV3PoolState + mapping(int16 => uint256) public override tickBitmap; + /// @inheritdoc IUniswapV3PoolState + mapping(bytes32 => Position.Info) public override positions; + /// @inheritdoc IUniswapV3PoolState + Oracle.Observation[65535] public override observations; + + /// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance + /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because + /// we use balance checks to determine the payment status of interactions such as mint, swap and flash. + modifier lock() { + require(slot0.unlocked, 'LOK'); + slot0.unlocked = false; + _; + slot0.unlocked = true; + } + + /// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner() + modifier onlyFactoryOwner() { + require(msg.sender == IUniswapV3Factory(factory).owner()); + _; + } + + constructor() { + int24 _tickSpacing; + (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters(); + tickSpacing = _tickSpacing; + + maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing); + } + + /// @dev Common checks for valid tick inputs. + function checkTicks(int24 tickLower, int24 tickUpper) private pure { + require(tickLower < tickUpper, 'TLU'); + require(tickLower >= TickMath.MIN_TICK, 'TLM'); + require(tickUpper <= TickMath.MAX_TICK, 'TUM'); + } + + /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests. + function _blockTimestamp() internal view virtual returns (uint32) { + return uint32(block.timestamp); // truncation is desired + } + + /// @dev Get the pool's balance of token0 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance0() private view returns (uint256) { + (bool success, bytes memory data) = + token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @dev Get the pool's balance of token1 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance1() private view returns (uint256) { + (bool success, bytes memory data) = + token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + override + noDelegateCall + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ) + { + unchecked { + checkTicks(tickLower, tickUpper); + + int56 tickCumulativeLower; + int56 tickCumulativeUpper; + uint160 secondsPerLiquidityOutsideLowerX128; + uint160 secondsPerLiquidityOutsideUpperX128; + uint32 secondsOutsideLower; + uint32 secondsOutsideUpper; + + { + Tick.Info storage lower = ticks[tickLower]; + Tick.Info storage upper = ticks[tickUpper]; + bool initializedLower; + (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = ( + lower.tickCumulativeOutside, + lower.secondsPerLiquidityOutsideX128, + lower.secondsOutside, + lower.initialized + ); + require(initializedLower); + + bool initializedUpper; + (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = ( + upper.tickCumulativeOutside, + upper.secondsPerLiquidityOutsideX128, + upper.secondsOutside, + upper.initialized + ); + require(initializedUpper); + } + + Slot0 memory _slot0 = slot0; + + if (_slot0.tick < tickLower) { + return ( + tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128, + secondsOutsideLower - secondsOutsideUpper + ); + } else if (_slot0.tick < tickUpper) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + _slot0.tick, + _slot0.observationIndex, + liquidity, + _slot0.observationCardinality + ); + return ( + tickCumulative - tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityCumulativeX128 - + secondsPerLiquidityOutsideLowerX128 - + secondsPerLiquidityOutsideUpperX128, + time - secondsOutsideLower - secondsOutsideUpper + ); + } else { + return ( + tickCumulativeUpper - tickCumulativeLower, + secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128, + secondsOutsideUpper - secondsOutsideLower + ); + } + } + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function observe(uint32[] calldata secondsAgos) + external + view + override + noDelegateCall + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return + observations.observe( + _blockTimestamp(), + secondsAgos, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + } + + /// @inheritdoc IUniswapV3PoolActions + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) + external + override + lock + noDelegateCall + { + uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event + uint16 observationCardinalityNextNew = + observations.grow(observationCardinalityNextOld, observationCardinalityNext); + slot0.observationCardinalityNext = observationCardinalityNextNew; + if (observationCardinalityNextOld != observationCardinalityNextNew) + emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew); + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev not locked because it initializes unlocked + function initialize(uint160 sqrtPriceX96) external override { + require(slot0.sqrtPriceX96 == 0, 'AI'); + + int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); + + (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp()); + + slot0 = Slot0({ + sqrtPriceX96: sqrtPriceX96, + tick: tick, + observationIndex: 0, + observationCardinality: cardinality, + observationCardinalityNext: cardinalityNext, + feeProtocol: 0, + unlocked: true + }); + + emit Initialize(sqrtPriceX96, tick); + } + + struct ModifyPositionParams { + // the address that owns the position + address owner; + // the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + // any change in liquidity + int128 liquidityDelta; + } + + /// @dev Effect some changes to a position + /// @param params the position details and the change to the position's liquidity to effect + /// @return position a storage pointer referencing the position with the given owner and tick range + /// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient + /// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient + function _modifyPosition(ModifyPositionParams memory params) + private + noDelegateCall + returns ( + Position.Info storage position, + int256 amount0, + int256 amount1 + ) + { + checkTicks(params.tickLower, params.tickUpper); + + Slot0 memory _slot0 = slot0; // SLOAD for gas optimization + + position = _updatePosition( + params.owner, + params.tickLower, + params.tickUpper, + params.liquidityDelta, + _slot0.tick + ); + + if (params.liquidityDelta != 0) { + if (_slot0.tick < params.tickLower) { + // current tick is below the passed range; liquidity can only become in range by crossing from left to + // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it + amount0 = SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } else if (_slot0.tick < params.tickUpper) { + // current tick is inside the passed range + uint128 liquidityBefore = liquidity; // SLOAD for gas optimization + + // write an oracle entry + (slot0.observationIndex, slot0.observationCardinality) = observations.write( + _slot0.observationIndex, + _blockTimestamp(), + _slot0.tick, + liquidityBefore, + _slot0.observationCardinality, + _slot0.observationCardinalityNext + ); + + amount0 = SqrtPriceMath.getAmount0Delta( + _slot0.sqrtPriceX96, + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + _slot0.sqrtPriceX96, + params.liquidityDelta + ); + + liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta); + } else { + // current tick is above the passed range; liquidity can only become in range by crossing from right to + // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } + } + } + + /// @dev Gets and updates a position with the given liquidity delta + /// @param owner the owner of the position + /// @param tickLower the lower tick of the position's tick range + /// @param tickUpper the upper tick of the position's tick range + /// @param tick the current tick, passed to avoid sloads + function _updatePosition( + address owner, + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta, + int24 tick + ) private returns (Position.Info storage position) { + position = positions.get(owner, tickLower, tickUpper); + + uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization + uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization + + // if we need to update the ticks, do it + bool flippedLower; + bool flippedUpper; + if (liquidityDelta != 0) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + + flippedLower = ticks.update( + tickLower, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + false, + maxLiquidityPerTick + ); + flippedUpper = ticks.update( + tickUpper, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + true, + maxLiquidityPerTick + ); + + if (flippedLower) { + tickBitmap.flipTick(tickLower, tickSpacing); + } + if (flippedUpper) { + tickBitmap.flipTick(tickUpper, tickSpacing); + } + } + + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128); + + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + + // clear any tick data that is no longer needed + if (liquidityDelta < 0) { + if (flippedLower) { + ticks.clear(tickLower); + } + if (flippedUpper) { + ticks.clear(tickUpper); + } + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external override lock returns (uint256 amount0, uint256 amount1) { + require(amount > 0); + (, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: recipient, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(amount0Int); + amount1 = uint256(amount1Int); + + uint256 balance0Before; + uint256 balance1Before; + if (amount0 > 0) balance0Before = balance0(); + if (amount1 > 0) balance1Before = balance1(); + IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data); + if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0'); + if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1'); + + emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1); + } + + /// @inheritdoc IUniswapV3PoolActions + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock returns (uint128 amount0, uint128 amount1) { + unchecked { + // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1} + Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper); + + amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested; + amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested; + + if (amount0 > 0) { + position.tokensOwed0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + position.tokensOwed1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1); + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external override lock returns (uint256 amount0, uint256 amount1) { + (Position.Info storage position, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: msg.sender, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: -int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(-amount0Int); + amount1 = uint256(-amount1Int); + + if (amount0 > 0 || amount1 > 0) { + (position.tokensOwed0, position.tokensOwed1) = ( + position.tokensOwed0 + uint128(amount0), + position.tokensOwed1 + uint128(amount1) + ); + } + + emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1); + } + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + // the timestamp of the current block + uint32 blockTimestamp; + // the current value of the tick accumulator, computed only if we cross an initialized tick + int56 tickCumulative; + // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick + uint160 secondsPerLiquidityCumulativeX128; + // whether we've computed and cached the above two accumulators + bool computedLatestObservation; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the global fee growth of the input token + uint256 feeGrowthGlobalX128; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + /// @inheritdoc IUniswapV3PoolActions + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external override noDelegateCall returns (int256 amount0, int256 amount1) { + unchecked { + require(amountSpecified != 0, 'AS'); + + Slot0 memory slot0Start = slot0; + + require(slot0Start.unlocked, 'LOK'); + require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + 'SPL' + ); + + slot0.unlocked = false; + + SwapCache memory cache = + SwapCache({ + liquidityStart: liquidity, + blockTimestamp: _blockTimestamp(), + feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4), + secondsPerLiquidityCumulativeX128: 0, + tickCumulative: 0, + computedLatestObservation: false + }); + + bool exactInput = amountSpecified > 0; + + SwapState memory state = + SwapState({ + amountSpecifiedRemaining: amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128, + protocolFee: 0, + liquidity: cache.liquidityStart + }); + + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord( + state.tick, + tickSpacing, + zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted + (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( + state.sqrtPriceX96, + (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + fee + ); + + if (exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256()); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256()); + } + + // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee + if (cache.feeProtocol > 0) { + uint256 delta = step.feeAmount / cache.feeProtocol; + step.feeAmount -= delta; + state.protocolFee += uint128(delta); + } + + // update global fee tracker + if (state.liquidity > 0) + state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity); + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + // check for the placeholder value, which we replace with the actual value the first time the swap + // crosses an initialized tick + if (!cache.computedLatestObservation) { + (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle( + cache.blockTimestamp, + 0, + slot0Start.tick, + slot0Start.observationIndex, + cache.liquidityStart, + slot0Start.observationCardinality + ); + cache.computedLatestObservation = true; + } + int128 liquidityNet = + ticks.cross( + step.tickNext, + (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128), + (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128), + cache.secondsPerLiquidityCumulativeX128, + cache.tickCumulative, + cache.blockTimestamp + ); + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + } + + // update tick and write an oracle entry if the tick change + if (state.tick != slot0Start.tick) { + (uint16 observationIndex, uint16 observationCardinality) = + observations.write( + slot0Start.observationIndex, + cache.blockTimestamp, + slot0Start.tick, + cache.liquidityStart, + slot0Start.observationCardinality, + slot0Start.observationCardinalityNext + ); + (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = ( + state.sqrtPriceX96, + state.tick, + observationIndex, + observationCardinality + ); + } else { + // otherwise just update the price + slot0.sqrtPriceX96 = state.sqrtPriceX96; + } + + // update liquidity if it changed + if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity; + + // update fee growth global and, if necessary, protocol fees + // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees + if (zeroForOne) { + feeGrowthGlobal0X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee; + } else { + feeGrowthGlobal1X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee; + } + + (amount0, amount1) = zeroForOne == exactInput + ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated) + : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining); + + // do the transfers and collect payment + if (zeroForOne) { + if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1)); + + uint256 balance0Before = balance0(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA'); + } else { + if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0)); + + uint256 balance1Before = balance1(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA'); + } + + emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick); + slot0.unlocked = true; + } + } + + /// @inheritdoc IUniswapV3PoolActions + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external override lock noDelegateCall { + unchecked { + uint128 _liquidity = liquidity; + require(_liquidity > 0, 'L'); + + uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6); + uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6); + uint256 balance0Before = balance0(); + uint256 balance1Before = balance1(); + + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; + + if (paid0 > 0) { + uint8 feeProtocol0 = slot0.feeProtocol % 16; + uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0; + if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0); + feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity); + } + if (paid1 > 0) { + uint8 feeProtocol1 = slot0.feeProtocol >> 4; + uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1; + if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1); + feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity); + } + + emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); + } + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner { + require( + (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) && + (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)) + ); + uint8 feeProtocolOld = slot0.feeProtocol; + slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4); + emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1); + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) { + amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested; + amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested; + + if (amount0 > 0) { + if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings + protocolFees.token0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings + protocolFees.token1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit CollectProtocol(msg.sender, recipient, amount0, amount1); + } +} + + +// File contracts/test/MockTimeUniswapV3Pool.sol + + +pragma solidity >=0.0; + +// used for testing time dependent behavior +contract MockTimeUniswapV3Pool is UniswapV3Pool { + // Monday, October 5, 2020 9:00:00 AM GMT-05:00 + uint256 public time = 1601906400; + + function setFeeGrowthGlobal0X128(uint256 _feeGrowthGlobal0X128) external { + feeGrowthGlobal0X128 = _feeGrowthGlobal0X128; + } + + function setFeeGrowthGlobal1X128(uint256 _feeGrowthGlobal1X128) external { + feeGrowthGlobal1X128 = _feeGrowthGlobal1X128; + } + + function advanceTime(uint256 by) external { + time += by; + } + + function _blockTimestamp() internal view override returns (uint32) { + return uint32(time); + } +} + + +// File contracts/test/MockTimeUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract MockTimeUniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + Parameters public override parameters; + + event PoolDeployed(address pool); + + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) external returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address( + new MockTimeUniswapV3Pool{salt: keccak256(abi.encodePacked(token0, token1, fee, tickSpacing))}() + ); + emit PoolDeployed(pool); + delete parameters; + } +} + + +// File contracts/test/NoDelegateCallTest.sol + + +pragma solidity >=0.0; + +contract NoDelegateCallTest is NoDelegateCall { + function canBeDelegateCalled() public view returns (uint256) { + return block.timestamp / 5; + } + + function cannotBeDelegateCalled() public view noDelegateCall returns (uint256) { + return block.timestamp / 5; + } + + function getGasCostOfCanBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + canBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function getGasCostOfCannotBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + cannotBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function callsIntoNoDelegateCallFunction() external view { + noDelegateCallPrivate(); + } + + function noDelegateCallPrivate() private view noDelegateCall {} +} + + +// File contracts/test/OracleTest.sol + + +pragma solidity >=0.0; +pragma abicoder v2; + +contract OracleTest { + using Oracle for Oracle.Observation[65535]; + + Oracle.Observation[65535] public observations; + + uint32 public time; + int24 public tick; + uint128 public liquidity; + uint16 public index; + uint16 public cardinality; + uint16 public cardinalityNext; + + struct InitializeParams { + uint32 time; + int24 tick; + uint128 liquidity; + } + + function initialize(InitializeParams calldata params) external { + require(cardinality == 0, 'already initialized'); + time = params.time; + tick = params.tick; + liquidity = params.liquidity; + (cardinality, cardinalityNext) = observations.initialize(params.time); + } + + function advanceTime(uint32 by) public { + unchecked { + time += by; + } + } + + struct UpdateParams { + uint32 advanceTimeBy; + int24 tick; + uint128 liquidity; + } + + // write an observation, then change tick and liquidity + function update(UpdateParams calldata params) external { + advanceTime(params.advanceTimeBy); + (index, cardinality) = observations.write(index, time, tick, liquidity, cardinality, cardinalityNext); + tick = params.tick; + liquidity = params.liquidity; + } + + function batchUpdate(UpdateParams[] calldata params) external { + // sload everything + int24 _tick = tick; + uint128 _liquidity = liquidity; + uint16 _index = index; + uint16 _cardinality = cardinality; + uint16 _cardinalityNext = cardinalityNext; + uint32 _time = time; + + for (uint256 i = 0; i < params.length; i++) { + _time += params[i].advanceTimeBy; + (_index, _cardinality) = observations.write( + _index, + _time, + _tick, + _liquidity, + _cardinality, + _cardinalityNext + ); + _tick = params[i].tick; + _liquidity = params[i].liquidity; + } + + // sstore everything + tick = _tick; + liquidity = _liquidity; + index = _index; + cardinality = _cardinality; + time = _time; + } + + function grow(uint16 _cardinalityNext) external { + cardinalityNext = observations.grow(cardinalityNext, _cardinalityNext); + } + + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return observations.observe(time, secondsAgos, tick, index, liquidity, cardinality); + } + + function getGasCostOfObserve(uint32[] calldata secondsAgos) external view returns (uint256) { + (uint32 _time, int24 _tick, uint128 _liquidity, uint16 _index) = (time, tick, liquidity, index); + uint256 gasBefore = gasleft(); + observations.observe(_time, secondsAgos, _tick, _index, _liquidity, cardinality); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/OracleEchidnaTest.sol + + +pragma solidity >=0.0; + +contract OracleEchidnaTest { + OracleTest private oracle; + + bool private initialized; + uint32 private timePassed; + + constructor() { + oracle = new OracleTest(); + } + + function initialize( + uint32 time, + int24 tick, + uint128 liquidity + ) external { + oracle.initialize(OracleTest.InitializeParams({time: time, tick: tick, liquidity: liquidity})); + initialized = true; + } + + function limitTimePassed(uint32 by) private { + require(timePassed + by >= timePassed); + timePassed += by; + } + + function advanceTime(uint32 by) public { + limitTimePassed(by); + oracle.advanceTime(by); + } + + // write an observation, then change tick and liquidity + function update( + uint32 advanceTimeBy, + int24 tick, + uint128 liquidity + ) external { + limitTimePassed(advanceTimeBy); + oracle.update(OracleTest.UpdateParams({advanceTimeBy: advanceTimeBy, tick: tick, liquidity: liquidity})); + } + + function grow(uint16 cardinality) external { + oracle.grow(cardinality); + } + + function checkTimeWeightedResultAssertions(uint32 secondsAgo0, uint32 secondsAgo1) private view { + require(secondsAgo0 != secondsAgo1); + require(initialized); + // secondsAgo0 should be the larger one + if (secondsAgo0 < secondsAgo1) (secondsAgo0, secondsAgo1) = (secondsAgo1, secondsAgo0); + + uint32 timeElapsed = secondsAgo0 - secondsAgo1; + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo0; + secondsAgos[1] = secondsAgo1; + + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + int56 timeWeightedTick = (tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(timeElapsed)); + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(timeElapsed) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + assert(timeWeightedTick <= type(int24).max); + assert(timeWeightedTick >= type(int24).min); + } + + function echidna_indexAlwaysLtCardinality() external view returns (bool) { + return oracle.index() < oracle.cardinality() || !initialized; + } + + function echidna_AlwaysInitialized() external view returns (bool) { + (, , , bool isInitialized) = oracle.observations(0); + return oracle.cardinality() == 0 || isInitialized; + } + + function echidna_cardinalityAlwaysLteNext() external view returns (bool) { + return oracle.cardinality() <= oracle.cardinalityNext(); + } + + function echidna_canAlwaysObserve0IfInitialized() external view returns (bool) { + if (!initialized) { + return true; + } + uint32[] memory arr = new uint32[](1); + arr[0] = 0; + (bool success, ) = address(oracle).staticcall(abi.encodeWithSelector(OracleTest.observe.selector, arr)); + return success; + } + + function checkTwoAdjacentObservationsTickCumulativeModTimeElapsedAlways0(uint16 index) external view { + uint16 cardinality = oracle.cardinality(); + // check that the observations are initialized, and that the index is not the oldest observation + require(index < cardinality && index != (oracle.index() + 1) % cardinality); + + (uint32 blockTimestamp0, int56 tickCumulative0, , bool initialized0) = + oracle.observations(index == 0 ? cardinality - 1 : index - 1); + (uint32 blockTimestamp1, int56 tickCumulative1, , bool initialized1) = oracle.observations(index); + + require(initialized0); + require(initialized1); + + uint32 timeElapsed = blockTimestamp1 - blockTimestamp0; + assert(timeElapsed > 0); + assert((tickCumulative1 - tickCumulative0) % int56(uint56(timeElapsed)) == 0); + } + + function checkTimeWeightedAveragesAlwaysFitsType(uint32 secondsAgo) external view { + require(initialized); + require(secondsAgo > 0); + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + + // compute the time weighted tick, rounded towards negative infinity + int56 numerator = tickCumulatives[1] - tickCumulatives[0]; + int56 timeWeightedTick = numerator / int56(uint56(secondsAgo)); + if (numerator < 0 && numerator % int56(uint56(secondsAgo)) != 0) { + timeWeightedTick--; + } + + // the time weighted averages fit in their respective accumulated types + assert(timeWeightedTick <= type(int24).max && timeWeightedTick >= type(int24).min); + + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(secondsAgo) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + } +} + + +// File contracts/test/SqrtPriceMathEchidnaTest.sol + + +pragma solidity >=0.0; + + + +contract SqrtPriceMathEchidnaTest { + function mulDivRoundingUpInvariants( + uint256 x, + uint256 y, + uint256 z + ) external pure { + require(z > 0); + uint256 notRoundedUp = FullMath.mulDiv(x, y, z); + uint256 roundedUp = FullMath.mulDivRoundingUp(x, y, z); + assert(roundedUp >= notRoundedUp); + assert(roundedUp - notRoundedUp < 2); + if (roundedUp - notRoundedUp == 1) { + assert(mulmod(x, y, z) > 0); + } else { + assert(mulmod(x, y, z) == 0); + } + } + + function getNextSqrtPriceFromInputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true)); + } else { + assert(sqrtQ >= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true)); + } + } + + function getNextSqrtPriceFromOutputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + } else { + assert(sqrtQ > 0); // this has to be true, otherwise we need another require + assert(sqrtQ >= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + } + } + + function getNextSqrtPriceFromAmount0RoundingUpInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 <= sqrtPX96); + } else { + assert(sqrtQX96 >= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getNextSqrtPriceFromAmount1RoundingDownInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 >= sqrtPX96); + } else { + assert(sqrtQX96 <= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getAmount0DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount0Down = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, false); + assert(amount0Down == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + + uint256 amount0Up = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true); + assert(amount0Up == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, true)); + + assert(amount0Down <= amount0Up); + // diff is 0 or 1 + assert(amount0Up - amount0Down < 2); + } + + // ensure that chained division is always equal to the full-precision case for + // liquidity * (sqrt(P) - sqrt(Q)) / (sqrt(P) * sqrt(Q)) + function getAmount0DeltaEquivalency( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity, + bool roundUp + ) external pure { + require(sqrtP >= sqrtQ); + require(sqrtP > 0 && sqrtQ > 0); + require((sqrtP * sqrtQ) / sqrtP == sqrtQ); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtP - sqrtQ; + uint256 denominator = uint256(sqrtP) * sqrtQ; + + uint256 safeResult = + roundUp + ? FullMath.mulDivRoundingUp(numerator1, numerator2, denominator) + : FullMath.mulDiv(numerator1, numerator2, denominator); + uint256 fullResult = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, roundUp); + + assert(safeResult == fullResult); + } + + function getAmount1DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount1Down = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, false); + assert(amount1Down == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + + uint256 amount1Up = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true); + assert(amount1Up == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, true)); + + assert(amount1Down <= amount1Up); + // diff is 0 or 1 + assert(amount1Up - amount1Down < 2); + } + + function getAmount0DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity); + if (liquidity < 0) assert(amount0 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount0 == 0); + else assert(amount0 > 0); + } + if (liquidity == 0) assert(amount0 == 0); + } + + function getAmount1DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity); + if (liquidity < 0) assert(amount1 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount1 == 0); + else assert(amount1 > 0); + } + if (liquidity == 0) assert(amount1 == 0); + } + + function getOutOfRangeMintInvariants( + uint160 sqrtA, + uint160 sqrtB, + int128 liquidity + ) external pure { + require(sqrtA > 0 && sqrtB > 0); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtA, sqrtB, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtA, sqrtB, liquidity); + + if (sqrtA == sqrtB) { + assert(amount0 == 0); + assert(amount1 == 0); + } else { + assert(amount0 > 0); + assert(amount1 > 0); + } + } + + function getInRangeMintInvariants( + uint160 sqrtLower, + uint160 sqrtCurrent, + uint160 sqrtUpper, + int128 liquidity + ) external pure { + require(sqrtLower > 0); + require(sqrtLower < sqrtUpper); + require(sqrtLower <= sqrtCurrent && sqrtCurrent <= sqrtUpper); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtCurrent, sqrtUpper, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtCurrent, liquidity); + + assert(amount0 > 0 || amount1 > 0); + } +} + + +// File contracts/test/SqrtPriceMathTest.sol + + +pragma solidity >=0.0; + +contract SqrtPriceMathTest { + function getNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + return gasBefore - gasleft(); + } + + function getNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + return gasBefore - gasleft(); + } + + function getAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount0) { + return SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount1) { + return SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getGasCostOfGetAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } + + function getGasCostOfGetAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/SwapMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract SwapMathEchidnaTest { + function checkComputeSwapStepInvariants( + uint160 sqrtPriceRaw, + uint160 sqrtPriceTargetRaw, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external pure { + require(sqrtPriceRaw > 0); + require(sqrtPriceTargetRaw > 0); + require(feePips > 0); + require(feePips < 1e6); + + (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = + SwapMath.computeSwapStep(sqrtPriceRaw, sqrtPriceTargetRaw, liquidity, amountRemaining, feePips); + + assert(amountIn <= type(uint256).max - feeAmount); + + if (amountRemaining < 0) { + assert(amountOut <= uint256(-amountRemaining)); + } else { + assert(amountIn + feeAmount <= uint256(amountRemaining)); + } + + if (sqrtPriceRaw == sqrtPriceTargetRaw) { + assert(amountIn == 0); + assert(amountOut == 0); + assert(feeAmount == 0); + assert(sqrtQ == sqrtPriceTargetRaw); + } + + // didn't reach price target, entire amount must be consumed + if (sqrtQ != sqrtPriceTargetRaw) { + if (amountRemaining < 0) assert(amountOut == uint256(-amountRemaining)); + else assert(amountIn + feeAmount == uint256(amountRemaining)); + } + + // next price is between price and price target + if (sqrtPriceTargetRaw <= sqrtPriceRaw) { + assert(sqrtQ <= sqrtPriceRaw); + assert(sqrtQ >= sqrtPriceTargetRaw); + } else { + assert(sqrtQ >= sqrtPriceRaw); + assert(sqrtQ <= sqrtPriceTargetRaw); + } + } +} + + +// File contracts/test/SwapMathTest.sol + + +pragma solidity >=0.0; + +contract SwapMathTest { + function computeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + external + pure + returns ( + uint160 sqrtQ, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + return SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + } + + function getGasCostOfComputeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/TestERC20.sol + + +pragma solidity >=0.0; + +contract TestERC20 is IERC20Minimal { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + + constructor(uint256 amountToMint) { + mint(msg.sender, amountToMint); + } + + function mint(address to, uint256 amount) public { + uint256 balanceNext = balanceOf[to] + amount; + require(balanceNext >= amount, 'overflow balance'); + balanceOf[to] = balanceNext; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + uint256 balanceBefore = balanceOf[msg.sender]; + require(balanceBefore >= amount, 'insufficient balance'); + balanceOf[msg.sender] = balanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow'); + balanceOf[recipient] = balanceRecipient + amount; + + emit Transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external override returns (bool) { + uint256 allowanceBefore = allowance[sender][msg.sender]; + require(allowanceBefore >= amount, 'allowance insufficient'); + + allowance[sender][msg.sender] = allowanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient'); + balanceOf[recipient] = balanceRecipient + amount; + uint256 balanceSender = balanceOf[sender]; + require(balanceSender >= amount, 'underflow balance sender'); + balanceOf[sender] = balanceSender - amount; + + emit Transfer(sender, recipient, amount); + return true; + } +} + + +// File contracts/test/TestUniswapV3Callee.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Callee is IUniswapV3MintCallback, IUniswapV3SwapCallback, IUniswapV3FlashCallback { + using SafeCast for uint256; + + function swapExact0For1( + address pool, + uint256 amount0In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, amount0In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap0ForExact1( + address pool, + uint256 amount1Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, -amount1Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapExact1For0( + address pool, + uint256 amount1In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, amount1In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap1ForExact0( + address pool, + uint256 amount0Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, -amount0Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapToLowerSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + function swapToHigherSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit SwapCallback(amount0Delta, amount1Delta); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } else { + // if both are not gt 0, both must be 0. + assert(amount0Delta == 0 && amount1Delta == 0); + } + } + + function mint( + address pool, + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external { + IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender)); + } + + event MintCallback(uint256 amount0Owed, uint256 amount1Owed); + + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit MintCallback(amount0Owed, amount1Owed); + if (amount0Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed); + if (amount1Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed); + } + + event FlashCallback(uint256 fee0, uint256 fee1); + + function flash( + address pool, + address recipient, + uint256 amount0, + uint256 amount1, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).flash(recipient, amount0, amount1, abi.encode(msg.sender, pay0, pay1)); + } + + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + emit FlashCallback(fee0, fee1); + + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, pay0); + if (pay1 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, pay1); + } +} + + +// File contracts/test/TestUniswapV3ReentrantCallee.sol + + +pragma solidity >=0.0; + +contract TestUniswapV3ReentrantCallee is IUniswapV3SwapCallback { + string private constant expectedReason = 'LOK'; + + function swapToReenter(address pool) external { + IUniswapV3Pool(pool).swap(address(0), false, 1, TickMath.MAX_SQRT_RATIO - 1, new bytes(0)); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata + ) external override { + // try to reenter swap + try IUniswapV3Pool(msg.sender).swap(address(0), false, 1, 0, new bytes(0)) {} catch Error( + string memory reason + ) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter mint + try IUniswapV3Pool(msg.sender).mint(address(0), 0, 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collect + try IUniswapV3Pool(msg.sender).collect(address(0), 0, 0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter burn + try IUniswapV3Pool(msg.sender).burn(0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter flash + try IUniswapV3Pool(msg.sender).flash(address(0), 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collectProtocol + try IUniswapV3Pool(msg.sender).collectProtocol(address(0), 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + require(false, 'Unable to reenter'); + } +} + + +// File contracts/test/TestUniswapV3Router.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Router is IUniswapV3SwapCallback { + using SafeCast for uint256; + + // flash swaps for an exact amount of token0 in the output pool + function swapForExact0Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount0Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + false, + -amount0Out.toInt256(), + TickMath.MAX_SQRT_RATIO - 1, + abi.encode(pools, msg.sender) + ); + } + + // flash swaps for an exact amount of token1 in the output pool + function swapForExact1Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount1Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + true, + -amount1Out.toInt256(), + TickMath.MIN_SQRT_RATIO + 1, + abi.encode(pools, msg.sender) + ); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) public override { + emit SwapCallback(amount0Delta, amount1Delta); + + (address[] memory pools, address payer) = abi.decode(data, (address[], address)); + + if (pools.length == 1) { + // get the address and amount of the token that we need to pay + address tokenToBePaid = + amount0Delta > 0 ? IUniswapV3Pool(msg.sender).token0() : IUniswapV3Pool(msg.sender).token1(); + int256 amountToBePaid = amount0Delta > 0 ? amount0Delta : amount1Delta; + + bool zeroForOne = tokenToBePaid == IUniswapV3Pool(pools[0]).token1(); + IUniswapV3Pool(pools[0]).swap( + msg.sender, + zeroForOne, + -amountToBePaid, + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + abi.encode(new address[](0), payer) + ); + } else { + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom( + payer, + msg.sender, + uint256(amount0Delta) + ); + } else { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom( + payer, + msg.sender, + uint256(amount1Delta) + ); + } + } + } +} + + +// File contracts/test/TestUniswapV3SwapPay.sol + + +pragma solidity >=0.0; + + +contract TestUniswapV3SwapPay is IUniswapV3SwapCallback { + function swap( + address pool, + address recipient, + bool zeroForOne, + uint160 sqrtPriceX96, + int256 amountSpecified, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).swap( + recipient, + zeroForOne, + amountSpecified, + sqrtPriceX96, + abi.encode(msg.sender, pay0, pay1) + ); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata data + ) external override { + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(pay0)); + } else if (pay1 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(pay1)); + } + } +} + + +// File contracts/test/TickBitmapEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapEchidnaTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) private bitmap; + + // returns whether the given tick is initialized + function isInitialized(int24 tick) private view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } + + function flipTick(int24 tick) external { + bool before = isInitialized(tick); + bitmap.flipTick(tick, 1); + assert(isInitialized(tick) == !before); + } + + function checkNextInitializedTickWithinOneWordInvariants(int24 tick, bool lte) external view { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + if (lte) { + // type(int24).min + 256 + require(tick >= -8388352); + assert(next <= tick); + assert(tick - next < 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick; i > next; i--) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } else { + // type(int24).max - 256 + require(tick < 8388351); + assert(next > tick); + assert(next - tick <= 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick + 1; i < next; i++) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } + } +} + + +// File contracts/test/TickBitmapTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) public bitmap; + + function flipTick(int24 tick) external { + bitmap.flipTick(tick, 1); + } + + function getGasCostOfFlipTick(int24 tick) external returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.flipTick(tick, 1); + return gasBefore - gasleft(); + } + + function nextInitializedTickWithinOneWord(int24 tick, bool lte) + external + view + returns (int24 next, bool initialized) + { + return bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + } + + function getGasCostOfNextInitializedTickWithinOneWord(int24 tick, bool lte) external view returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + return gasBefore - gasleft(); + } + + // returns whether the given tick is initialized + function isInitialized(int24 tick) external view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } +} + + +// File contracts/test/TickEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickEchidnaTest { + function checkTickSpacingToParametersInvariants(int24 tickSpacing) external pure { + require(tickSpacing <= TickMath.MAX_TICK); + require(tickSpacing > 0); + + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + + uint128 maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + + // symmetry around 0 tick + assert(maxTick == -minTick); + // positive max tick + assert(maxTick > 0); + // divisibility + assert((maxTick - minTick) % tickSpacing == 0); + + uint256 numTicks = uint256(int256((maxTick - minTick) / tickSpacing)) + 1; + // max liquidity at every tick is less than the cap + assert(uint256(maxLiquidityPerTick) * numTicks <= type(uint128).max); + } +} + + +// File contracts/test/TickMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickMathEchidnaTest { + // uniqueness and increasing order + function checkGetSqrtRatioAtTickInvariants(int24 tick) external pure { + uint160 ratio = TickMath.getSqrtRatioAtTick(tick); + assert(TickMath.getSqrtRatioAtTick(tick - 1) < ratio && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(ratio >= TickMath.MIN_SQRT_RATIO); + assert(ratio <= TickMath.MAX_SQRT_RATIO); + } + + // the ratio is always between the returned tick and the returned tick+1 + function checkGetTickAtSqrtRatioInvariants(uint160 ratio) external pure { + int24 tick = TickMath.getTickAtSqrtRatio(ratio); + assert(ratio >= TickMath.getSqrtRatioAtTick(tick) && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(tick >= TickMath.MIN_TICK); + assert(tick < TickMath.MAX_TICK); + } +} + + +// File contracts/test/TickMathTest.sol + + +pragma solidity >=0.0; + +contract TickMathTest { + function getSqrtRatioAtTick(int24 tick) external pure returns (uint160) { + return TickMath.getSqrtRatioAtTick(tick); + } + + function getGasCostOfGetSqrtRatioAtTick(int24 tick) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getSqrtRatioAtTick(tick); + return gasBefore - gasleft(); + } + + function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure returns (int24) { + return TickMath.getTickAtSqrtRatio(sqrtPriceX96); + } + + function getGasCostOfGetTickAtSqrtRatio(uint160 sqrtPriceX96) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getTickAtSqrtRatio(sqrtPriceX96); + return gasBefore - gasleft(); + } + + function MIN_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MIN_SQRT_RATIO; + } + + function MAX_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MAX_SQRT_RATIO; + } +} + + +// File contracts/test/TickOverflowSafetyEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickOverflowSafetyEchidnaTest { + using Tick for mapping(int24 => Tick.Info); + + int24 private constant MIN_TICK = -16; + int24 private constant MAX_TICK = 16; + uint128 private constant MAX_LIQUIDITY = type(uint128).max / 32; + + mapping(int24 => Tick.Info) private ticks; + int24 private tick = 0; + + // used to track how much total liquidity has been added. should never be negative + int256 totalLiquidity = 0; + // half the cap of fee growth has happened, this can overflow + uint256 private feeGrowthGlobal0X128 = type(uint256).max / 2; + uint256 private feeGrowthGlobal1X128 = type(uint256).max / 2; + // how much total growth has happened, this cannot overflow + uint256 private totalGrowth0 = 0; + uint256 private totalGrowth1 = 0; + + function increaseFeeGrowthGlobal0X128(uint256 amount) external { + require(totalGrowth0 + amount > totalGrowth0); // overflow check + feeGrowthGlobal0X128 += amount; // overflow desired + totalGrowth0 += amount; + } + + function increaseFeeGrowthGlobal1X128(uint256 amount) external { + require(totalGrowth1 + amount > totalGrowth1); // overflow check + feeGrowthGlobal1X128 += amount; // overflow desired + totalGrowth1 += amount; + } + + function setPosition( + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta + ) external { + require(tickLower > MIN_TICK); + require(tickUpper < MAX_TICK); + require(tickLower < tickUpper); + bool flippedLower = + ticks.update( + tickLower, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + false, + MAX_LIQUIDITY + ); + bool flippedUpper = + ticks.update( + tickUpper, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + true, + MAX_LIQUIDITY + ); + + if (flippedLower) { + if (liquidityDelta < 0) { + assert(ticks[tickLower].liquidityGross == 0); + ticks.clear(tickLower); + } else assert(ticks[tickLower].liquidityGross > 0); + } + + if (flippedUpper) { + if (liquidityDelta < 0) { + assert(ticks[tickUpper].liquidityGross == 0); + ticks.clear(tickUpper); + } else assert(ticks[tickUpper].liquidityGross > 0); + } + + totalLiquidity += liquidityDelta; + // requires should have prevented this + assert(totalLiquidity >= 0); + + if (totalLiquidity == 0) { + totalGrowth0 = 0; + totalGrowth1 = 0; + } + } + + function moveToTick(int24 target) external { + require(target > MIN_TICK); + require(target < MAX_TICK); + while (tick != target) { + if (tick < target) { + if (ticks[tick + 1].liquidityGross > 0) + ticks.cross(tick + 1, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick++; + } else { + if (ticks[tick].liquidityGross > 0) + ticks.cross(tick, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick--; + } + } + } +} + + +// File contracts/test/TickTest.sol + + +pragma solidity >=0.0; + +contract TickTest { + using Tick for mapping(int24 => Tick.Info); + + mapping(int24 => Tick.Info) public ticks; + + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) external pure returns (uint128) { + return Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + } + + function setTick(int24 tick, Tick.Info memory info) external { + ticks[tick] = info; + } + + function getFeeGrowthInside( + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) external view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + return ticks.getFeeGrowthInside(tickLower, tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + } + + function update( + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) external returns (bool flipped) { + return + ticks.update( + tick, + tickCurrent, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + upper, + maxLiquidity + ); + } + + function clear(int24 tick) external { + ticks.clear(tick); + } + + function cross( + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) external returns (int128 liquidityNet) { + return + ticks.cross( + tick, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time + ); + } +} + + +// File contracts/test/UniswapV3PoolSwapTest.sol + + +pragma solidity >=0.0; + + +contract UniswapV3PoolSwapTest is IUniswapV3SwapCallback { + int256 private _amount0Delta; + int256 private _amount1Delta; + + function getSwapResult( + address pool, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96 + ) + external + returns ( + int256 amount0Delta, + int256 amount1Delta, + uint160 nextSqrtRatio + ) + { + (amount0Delta, amount1Delta) = IUniswapV3Pool(pool).swap( + address(0), + zeroForOne, + amountSpecified, + sqrtPriceLimitX96, + abi.encode(msg.sender) + ); + + (nextSqrtRatio, , , , , , ) = IUniswapV3Pool(pool).slot0(); + } + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } + } +} + + +// File contracts/test/UnsafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract UnsafeMathEchidnaTest { + function checkDivRoundingUp(uint256 x, uint256 d) external pure { + require(d > 0); + uint256 z = UnsafeMath.divRoundingUp(x, d); + uint256 diff = z - (x / d); + if (x % d == 0) { + assert(diff == 0); + } else { + assert(diff == 1); + } + } +} + + +// File contracts/UniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + /// @inheritdoc IUniswapV3PoolDeployer + Parameters public override parameters; + + /// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then + /// clearing it after deploying the pool. + /// @param factory The contract address of the Uniswap V3 factory + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The spacing between usable ticks + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) internal returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); + delete parameters; + } +} + + +// File contracts/UniswapV3Factory.sol + + +pragma solidity >=0.0; + + +/// @title Canonical Uniswap V3 factory +/// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees +contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall { + /// @inheritdoc IUniswapV3Factory + address public override owner; + + /// @inheritdoc IUniswapV3Factory + mapping(uint24 => int24) public override feeAmountTickSpacing; + /// @inheritdoc IUniswapV3Factory + mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; + + constructor() { + owner = msg.sender; + emit OwnerChanged(address(0), msg.sender); + + feeAmountTickSpacing[500] = 10; + emit FeeAmountEnabled(500, 10); + feeAmountTickSpacing[3000] = 60; + emit FeeAmountEnabled(3000, 60); + feeAmountTickSpacing[10000] = 200; + emit FeeAmountEnabled(10000, 200); + } + + /// @inheritdoc IUniswapV3Factory + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) public override noDelegateCall returns (address pool) { + require(tokenA != tokenB); + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0)); + int24 tickSpacing = feeAmountTickSpacing[fee]; + require(tickSpacing != 0); + require(getPool[token0][token1][fee] == address(0)); + pool = deploy(address(this), token0, token1, fee, tickSpacing); + getPool[token0][token1][fee] = pool; + // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses + getPool[token1][token0][fee] = pool; + emit PoolCreated(token0, token1, fee, tickSpacing, pool); + } + + /// @inheritdoc IUniswapV3Factory + function setOwner(address _owner) external override { + require(msg.sender == owner); + emit OwnerChanged(owner, _owner); + owner = _owner; + } + + /// @inheritdoc IUniswapV3Factory + function enableFeeAmount(uint24 fee, int24 tickSpacing) public override { + require(msg.sender == owner); + require(fee < 1000000); + // tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that + // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick + // 16384 ticks represents a >5x price change with ticks of 1 bips + require(tickSpacing > 0 && tickSpacing < 16384); + require(feeAmountTickSpacing[fee] == 0); + + feeAmountTickSpacing[fee] = tickSpacing; + emit FeeAmountEnabled(fee, tickSpacing); + } + + function runTest() public { + TestERC20 t1 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000001}(1000000000); + TestERC20 t2 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000002}(1000000000); + IUniswapV3Pool pool = IUniswapV3Pool(createPool(address(t1), address(t2), 500)); + + TestUniswapV3Callee test = new TestUniswapV3Callee(); + t1.approve(address(test), 1000000000); + t2.approve(address(test), 1000000000); + pool.initialize(761446703485210103287273052203988822378723970342); + + test.mint(address(pool), address(this), int24(-1000), int24(1000), uint128(1000)); + + uint256 beforeSwap = t1.balanceOf(address(this)); + test.swapExact0For1(address(pool), 10, address(this), 4295128740); + uint256 afterSwap = t1.balanceOf(address(this)); + + require(beforeSwap != afterSwap, 'WRO'); + } +} + +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// runTest() -> +// ~ emit PoolCreated(address,address,uint24,int24,address): #0xe25a2f4ab57397bff1e25190d8a25c7fdbf97872, #0xe815b5c5c31d4b73175f828b1d19460b97c65b26, #0x01f4, 0x0a, 0x8b800520e09d6d4598288ad0dfd35b6237cdd278 +// ~ emit Approval(address,address,uint256) from 0xe25a2f4ab57397bff1e25190d8a25c7fdbf97872: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, 0x3b9aca00 +// ~ emit Approval(address,address,uint256) from 0xe815b5c5c31d4b73175f828b1d19460b97c65b26: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, 0x3b9aca00 +// ~ emit Initialize(uint160,int24) from 0x8b800520e09d6d4598288ad0dfd35b6237cdd278: 0x85607379ff6f79edb3e272aaeae79d5263988d26, 0x0d56f8 +// ~ emit MintCallback(uint256,uint256) from 0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57: 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe815b5c5c31d4b73175f828b1d19460b97c65b26: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0x8b800520e09d6d4598288ad0dfd35b6237cdd278, 0x65 +// ~ emit Mint(address,address,int24,int24,uint128,uint256,uint256) from 0x8b800520e09d6d4598288ad0dfd35b6237cdd278: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18, #0x03e8, 0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, 0x03e8, 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe815b5c5c31d4b73175f828b1d19460b97c65b26: #0x8b800520e09d6d4598288ad0dfd35b6237cdd278, #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, 0x09 +// ~ emit SwapCallback(int256,int256) from 0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57: 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7 +// ~ emit Transfer(address,address,uint256) from 0xe25a2f4ab57397bff1e25190d8a25c7fdbf97872: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0x8b800520e09d6d4598288ad0dfd35b6237cdd278, 0x0a +// ~ emit Swap(address,address,int256,int256,uint160,uint128,int24) from 0x8b800520e09d6d4598288ad0dfd35b6237cdd278: #0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7, 0x010a9a2fd9e126942e9c978d07, 0x03e8, 0x032b diff --git a/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol index ea6d781898f0..5b7c0ba97474 100644 --- a/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol +++ b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol @@ -10,5 +10,7 @@ contract C { hash3 = keccak256(abi.encodePacked(this.f)); } } +// ==== +// compileToEOF: false // ---- // f() -> 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0x0e9229fb1d2cd02cee4b6c9f25497777014a8766e3479666d1c619066d2887ec diff --git a/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol new file mode 100644 index 000000000000..e87a886582f2 --- /dev/null +++ b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol @@ -0,0 +1,17 @@ +contract C { + uint120[3] x; + function f() public returns (bytes32 hash1, bytes32 hash2, bytes32 hash3) { + uint120[] memory y = new uint120[](3); + x[0] = y[0] = uint120(type(uint).max - 1); + x[1] = y[1] = uint120(type(uint).max - 2); + x[2] = y[2] = uint120(type(uint).max - 3); + hash1 = keccak256(abi.encodePacked(x)); + hash2 = keccak256(abi.encodePacked(y)); + hash3 = keccak256(abi.encodePacked(this.f)); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0xb8fcf925e706038045e8e57a2620706dfa046b8977add2f3c9b2641f2ea6c8b7 diff --git a/test/libsolidity/semanticTests/constructor/callvalue_check.sol b/test/libsolidity/semanticTests/constructor/callvalue_check.sol index f45fbed925ad..faef55828254 100644 --- a/test/libsolidity/semanticTests/constructor/callvalue_check.sol +++ b/test/libsolidity/semanticTests/constructor/callvalue_check.sol @@ -9,26 +9,27 @@ contract B3 {} contract B4 { constructor() {} } contract C { - function createWithValue(bytes memory c, uint256 value) public payable returns (bool) { - uint256 y = 0; - assembly { y := create(value, add(c, 0x20), mload(c)) } - return y != 0; - } - function f(uint256 value) public payable returns (bool) { - return createWithValue(type(B1).creationCode, value); - } - function g(uint256 value) public payable returns (bool) { - return createWithValue(type(B2).creationCode, value); - } - function h(uint256 value) public payable returns (bool) { - return createWithValue(type(B3).creationCode, value); - } - function i(uint256 value) public payable returns (bool) { - return createWithValue(type(B4).creationCode, value); - } + function createWithValue(bytes memory c, uint256 value) public payable returns (bool) { + uint256 y = 0; + assembly { y := create(value, add(c, 0x20), mload(c)) } + return y != 0; + } + function f(uint256 value) public payable returns (bool) { + return createWithValue(type(B1).creationCode, value); + } + function g(uint256 value) public payable returns (bool) { + return createWithValue(type(B2).creationCode, value); + } + function h(uint256 value) public payable returns (bool) { + return createWithValue(type(B3).creationCode, value); + } + function i(uint256 value) public payable returns (bool) { + return createWithValue(type(B4).creationCode, value); + } } // ==== // EVMVersion: >homestead +// compileToEOF: false // ---- // f(uint256), 2000 ether: 0 -> true // f(uint256), 2000 ether: 100 -> false diff --git a/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol b/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol index 583f1ca37b3f..9f979fb74499 100644 --- a/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol +++ b/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol @@ -15,6 +15,8 @@ contract C { return true; } } +// ==== +// compileToEOF: false // ---- // f(), 2000 ether -> true // gas irOptimized: 117623 diff --git a/test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol b/test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol new file mode 100644 index 000000000000..1442ab030630 --- /dev/null +++ b/test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol @@ -0,0 +1,28 @@ +contract A1 {} +contract B1 is A1 { constructor() payable {} } + +contract A2 { constructor() {} } +contract B2 is A2 { constructor() payable {} } + +contract B3 { constructor() payable {} } + +contract C { + function f() public payable returns (bool) { + // Make sure none of these revert. + new B1{value: 10, salt: hex"00"}(); + new B2{value: 10, salt: hex"01"}(); + new B3{value: 10, salt: hex"02"}(); + return true; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f(), 2000 ether -> true +// gas irOptimized: 117623 +// gas irOptimized code: 1800 +// gas legacy: 117821 +// gas legacy code: 4800 +// gas legacyOptimized: 117690 +// gas legacyOptimized code: 4800 diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol index 64b80422c352..d774936444e7 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol @@ -36,5 +36,7 @@ contract C { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol index 931109199649..a40dcee211ca 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol @@ -30,5 +30,7 @@ contract C { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol index 018d410557b0..fbbdae0cb9f8 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol @@ -32,5 +32,7 @@ contract C { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol index 0f9b023b08d9..735b5b68fff2 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol @@ -31,5 +31,7 @@ contract C is S { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol index b6ae85b838d8..3451598ad74a 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol @@ -37,5 +37,7 @@ contract C { x < 2 * type(A).creationCode.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol index 9accc54a2b05..dda110805196 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol @@ -31,5 +31,7 @@ contract C is S { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol index 866fe9dfc452..9e0388a7f800 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol @@ -35,5 +35,7 @@ contract C is X { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol b/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol index bc70b5bef757..9c22d9507fb8 100644 --- a/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol +++ b/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol @@ -36,6 +36,7 @@ contract C { // ==== // compileViaYul: true +// compileToEOF: false // ---- // a() -> FAILURE, hex"92bbf6e8" // b() -> FAILURE, hex"47e26897", hex"0000000000000000000000000000000000000000000000000000000000000001" diff --git a/test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol b/test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol new file mode 100644 index 000000000000..2fd4126a0705 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol @@ -0,0 +1,46 @@ +pragma abicoder v2; + +struct S { + uint256 a; + bool b; + string s; +} + +error E(); +error E1(uint256); +error E2(string); +error E3(S); +error E4(address); +error E5(function() external pure); + +contract C { + function a() external pure { + require(false, E()); + } + function b() external pure { + require(false, E1(1)); + } + function c() external pure { + require(false, E2("string literal")); + } + function d() external pure { + require(false, E3(S(1, true, "string literal"))); + } + function e() external view { + require(false, E4(address(this))); + } + function f() external view { + require(false, E5(this.a)); + } +} + +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// a() -> FAILURE, hex"92bbf6e8" +// b() -> FAILURE, hex"47e26897", hex"0000000000000000000000000000000000000000000000000000000000000001" +// c() -> FAILURE, hex"8f372c34", hex"0000000000000000000000000000000000000000000000000000000000000020", hex"000000000000000000000000000000000000000000000000000000000000000e", hex"737472696e67206c69746572616c000000000000000000000000000000000000" +// d() -> FAILURE, hex"5717173e", hex"0000000000000000000000000000000000000000000000000000000000000020", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000060", hex"000000000000000000000000000000000000000000000000000000000000000e", hex"737472696e67206c69746572616c000000000000000000000000000000000000" +// e() -> FAILURE, hex"7efef9ea", hex"0000000000000000000000008e3f661b8facaa0fa7aa0113847501029db6517e" +// f() -> FAILURE, hex"0c3f12eb", hex"8e3f661b8facaa0fa7aa0113847501029db6517e0dbe671f0000000000000000" diff --git a/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol index 3af19ca64fbf..59a39d9152f7 100644 --- a/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol +++ b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol @@ -14,5 +14,7 @@ contract C } } +// ==== +// compileToEOF: false // ---- // f() -> FAILURE, hex"271b1dfa", hex"c06afe3a8444fc0004668591e8306bfb9968e79ef37cdc8e0000000000000000" diff --git a/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol new file mode 100644 index 000000000000..0857fabd4a13 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol @@ -0,0 +1,21 @@ +error CustomError(function(uint256) external pure returns (uint256)); + +contract C +{ + function e(uint256 x) external pure returns (uint256) + { + return x; + } + + function f() external view + { + // more than one stack slot + require(false, CustomError(this.e)); + } +} + +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> FAILURE, hex"271b1dfa", hex"dfc163ea0fefc2097b7425134f69fcafa3742b0af37cdc8e0000000000000000" diff --git a/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol b/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol index 61e1bbdd6c0e..0add96c4421c 100644 --- a/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol +++ b/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol @@ -13,6 +13,8 @@ contract C { d.deposit(_id); } } +// ==== +// compileToEOF: false // ---- // constructor() -> // gas irOptimized: 113970 diff --git a/test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol b/test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol new file mode 100644 index 000000000000..81d52a075171 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol @@ -0,0 +1,28 @@ +contract D { + event Deposit(address indexed _from, bytes32 indexed _id, uint _value); + function deposit(bytes32 _id) public payable { + emit Deposit(msg.sender, _id, msg.value); + } +} +contract C { + D d; + constructor() { + d = new D(); + } + function deposit(bytes32 _id) public payable { + d.deposit(_id); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor() -> +// gas irOptimized: 113970 +// gas irOptimized code: 51400 +// gas legacy: 119776 +// gas legacy code: 125000 +// gas legacyOptimized: 114187 +// gas legacyOptimized code: 57400 +// deposit(bytes32), 18 wei: 0x1234 -> +// ~ emit Deposit(address,bytes32,uint256) from 0x32b73100436177e8f2d2aa1214bb4c1230143ec2: #0x3cb69f8aa1103d7ce41821a1b2e1c85c2b63dfa4, #0x1234, 0x00 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function.sol b/test/libsolidity/semanticTests/events/event_indexed_function.sol index ea7574159500..a4006b196b83 100644 --- a/test/libsolidity/semanticTests/events/event_indexed_function.sol +++ b/test/libsolidity/semanticTests/events/event_indexed_function.sol @@ -4,6 +4,8 @@ contract C { emit Test(this.f); } } +// ==== +// compileToEOF: false // ---- // f() -> // ~ emit Test(function): #0xc06afe3a8444fc0004668591e8306bfb9968e79e26121ff00000000000000000 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function2.sol b/test/libsolidity/semanticTests/events/event_indexed_function2.sol index d4c47e868a1f..b0f2ee7a4d2b 100644 --- a/test/libsolidity/semanticTests/events/event_indexed_function2.sol +++ b/test/libsolidity/semanticTests/events/event_indexed_function2.sol @@ -8,6 +8,8 @@ contract C { emit TestB(this.f2); } } +// ==== +// compileToEOF: false // ---- // f1() -> // ~ emit TestA(function): #0xc06afe3a8444fc0004668591e8306bfb9968e79ec27fc3050000000000000000 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol b/test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol new file mode 100644 index 000000000000..202489dc5519 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol @@ -0,0 +1,18 @@ +contract C { + event TestA(function() external indexed); + event TestB(function(uint256) external indexed); + function f1() public { + emit TestA(this.f1); + } + function f2(uint256 a) public { + emit TestB(this.f2); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f1() -> +// ~ emit TestA(function): #0xa80a5d214a51e09e24fa3e854004da1ac3f5beffc27fc3050000000000000000 +// f2(uint256): 1 -> +// ~ emit TestB(function): #0xa80a5d214a51e09e24fa3e854004da1ac3f5beffbf3724af0000000000000000 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function_eof.sol b/test/libsolidity/semanticTests/events/event_indexed_function_eof.sol new file mode 100644 index 000000000000..87f960c76910 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_indexed_function_eof.sol @@ -0,0 +1,12 @@ +contract C { + event Test(function() external indexed); + function f() public { + emit Test(this.f); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> +// ~ emit Test(function): #0x1141c91a4a817b60b5339bc09ac809cedc7649ab26121ff00000000000000000 diff --git a/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol b/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol index 4dff7f9162cc..981efc4c95d1 100644 --- a/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol +++ b/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol @@ -220,7 +220,7 @@ library strings { // Mask out irrelevant bytes and check again uint256 mask = type(uint256).max; // 0xffff... if(shortest < 32) { - mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); + mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); } uint256 diff; // This depends on potential underflow. diff --git a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol index c53e49f1f032..2e26df4b8fe2 100644 --- a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol +++ b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol @@ -174,6 +174,8 @@ contract DepositContract is IDepositContract, ERC165 { ret[7] = bytesValue[0]; } } +// ==== +// compileToEOF: false // ---- // constructor() // gas irOptimized: 809570 diff --git a/test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol b/test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol new file mode 100644 index 000000000000..5546f48603b9 --- /dev/null +++ b/test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol @@ -0,0 +1,216 @@ +// ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━ +// ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓ +// ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛ +// ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━ +// ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓ +// ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛ +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +// SPDX-License-Identifier: CC0-1.0 + +// This interface is designed to be compatible with the Vyper version. +/// @notice This is the Ethereum 2.0 deposit contract interface. +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs +interface IDepositContract { + /// @notice A processed deposit event. + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); + + /// @notice Submit a Phase 0 DepositData object. + /// @param pubkey A BLS12-381 public key. + /// @param withdrawal_credentials Commitment to a public key for withdrawals. + /// @param signature A BLS12-381 signature. + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. + /// Used as a protection against malformed input. + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable; + + /// @notice Query the current deposit root hash. + /// @return The deposit root hash. + function get_deposit_root() external view returns (bytes32); + + /// @notice Query the current deposit count. + /// @return The deposit count encoded as a little endian 64-bit number. + function get_deposit_count() external view returns (bytes memory); +} + +// Based on official specification in https://eips.ethereum.org/EIPS/eip-165 +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceId The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceId` and + /// `interfaceId` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceId) external pure returns (bool); +} + +// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity. +// It tries to stay as close as possible to the original source code. +/// @notice This is the Ethereum 2.0 deposit contract interface. +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs +contract DepositContract is IDepositContract, ERC165 { + uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; + // NOTE: this also ensures `deposit_count` will fit into 64-bits + uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; + uint256 deposit_count; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; + + constructor() public { + // Compute hashes in empty sparse Merkle tree + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) + zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); + } + + function get_deposit_root() override external view returns (bytes32) { + bytes32 node; + uint size = deposit_count; + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { + if ((size & 1) == 1) + node = sha256(abi.encodePacked(branch[height], node)); + else + node = sha256(abi.encodePacked(node, zero_hashes[height])); + size /= 2; + } + return sha256(abi.encodePacked( + node, + to_little_endian_64(uint64(deposit_count)), + bytes24(0) + )); + } + + function get_deposit_count() override external view returns (bytes memory) { + return to_little_endian_64(uint64(deposit_count)); + } + + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) override external payable { + // Extended ABI length checks since dynamic types are used. + require(pubkey.length == 48, "DepositContract: invalid pubkey length"); + require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); + require(signature.length == 96, "DepositContract: invalid signature length"); + + // Check deposit amount + require(msg.value >= 1 ether, "DepositContract: deposit value too low"); + require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); + uint deposit_amount = msg.value / 1 gwei; + require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); + + // Emit `DepositEvent` log + bytes memory amount = to_little_endian_64(uint64(deposit_amount)); + emit DepositEvent( + pubkey, + withdrawal_credentials, + amount, + signature, + to_little_endian_64(uint64(deposit_count)) + ); + + // Compute deposit data root (`DepositData` hash tree root) + bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); + bytes32 signature_root = sha256(abi.encodePacked( + sha256(abi.encodePacked(signature[:64])), + sha256(abi.encodePacked(signature[64:], bytes32(0))) + )); + bytes32 node = sha256(abi.encodePacked( + sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), + sha256(abi.encodePacked(amount, bytes24(0), signature_root)) + )); + + // Verify computed and expected deposit data roots match + require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); + + // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) + require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); + + // Add deposit data root to Merkle tree (update a single `branch` node) + deposit_count += 1; + uint size = deposit_count; + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { + if ((size & 1) == 1) { + branch[height] = node; + return; + } + node = sha256(abi.encodePacked(branch[height], node)); + size /= 2; + } + // As the loop should always end prematurely with the `return` statement, + // this code should be unreachable. We assert `false` just to be safe. + assert(false); + } + + function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { + return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId; + } + + function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { + ret = new bytes(8); + bytes8 bytesValue = bytes8(value); + // Byteswapping during copying to bytes. + ret[0] = bytesValue[7]; + ret[1] = bytesValue[6]; + ret[2] = bytesValue[5]; + ret[3] = bytesValue[4]; + ret[4] = bytesValue[3]; + ret[5] = bytesValue[2]; + ret[6] = bytesValue[1]; + ret[7] = bytesValue[0]; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor() +// gas irOptimized: 809602 +// gas irOptimized code: 558000 +// gas legacy: 919945 +// gas legacy code: 1437600 +// gas legacyOptimized: 848699 +// gas legacyOptimized code: 878200 +// supportsInterface(bytes4): 0x0 -> 0 +// supportsInterface(bytes4): 0xffffffff00000000000000000000000000000000000000000000000000000000 -> false # defined to be false by ERC-165 # +// supportsInterface(bytes4): 0x01ffc9a700000000000000000000000000000000000000000000000000000000 -> true # ERC-165 id # +// supportsInterface(bytes4): 0x8564090700000000000000000000000000000000000000000000000000000000 -> true # the deposit interface id # +// get_deposit_root() -> 0x691a2cb303bfa42437412cd455155952c395370b31a4be3adfb0a373e7ee7c5c +// gas irOptimized: 109178 +// gas legacy: 142735 +// gas legacyOptimized: 117558 +// get_deposit_count() -> 0x20, 8, 0 # TODO: check balance and logs after each deposit # +// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0 -> FAILURE # Empty input # +// get_deposit_root() -> 0x691a2cb303bfa42437412cd455155952c395370b31a4be3adfb0a373e7ee7c5c +// gas irOptimized: 109178 +// gas legacy: 142735 +// gas legacyOptimized: 117558 +// get_deposit_count() -> 0x20, 8, 0 +// deposit(bytes,bytes,bytes,bytes32), 1 ether: 0x80, 0xe0, 0x120, 0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0x00f50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8 -> # txhash: 0x7085c586686d666e8bb6e9477a0f0b09565b2060a11f1c4209d3a52295033832 # +// ~ emit DepositEvent(bytes,bytes,bytes,bytes,bytes): 0xa0, 0x0100, 0x0140, 0x0180, 0x0200, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0xf50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x08, 0xca9a3b00000000000000000000000000000000000000000000000000000000, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8, 0x08, 0x00 +// get_deposit_root() -> 0x9c655a38a141cd7aecbff6b73d31a1c3d8bbcc1de86b627ac57e7e8342ab2839 +// gas irOptimized: 109174 +// gas legacy: 142744 +// gas legacyOptimized: 117570 +// get_deposit_count() -> 0x20, 8, 0x0100000000000000000000000000000000000000000000000000000000000000 +// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0x80, 0xe0, 0x120, 0xdbd986dc85ceb382708cf90a3500f500f0a393c5ece76963ac3ed72eccd2c301, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x00344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d -> # txhash: 0x404d8e109822ce448e68f45216c12cb051b784d068fbe98317ab8e50c58304ac # +// ~ emit DepositEvent(bytes,bytes,bytes,bytes,bytes): 0xa0, 0x0100, 0x0140, 0x0180, 0x0200, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x08, 0x40597307000000000000000000000000000000000000000000000000000000, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d, 0x08, 0x0100000000000000000000000000000000000000000000000000000000000000 +// get_deposit_root() -> 0xd6ccaa7e8ae43c12e2b885375964e2e5e6cb1708d13ff9fd63c8b3325b423f73 +// gas irOptimized: 109174 +// gas legacy: 142744 +// gas legacyOptimized: 117570 +// get_deposit_count() -> 0x20, 8, 0x0200000000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/externalContracts/snark.sol b/test/libsolidity/semanticTests/externalContracts/snark.sol index 906279d17935..0c8c3bf5243b 100644 --- a/test/libsolidity/semanticTests/externalContracts/snark.sol +++ b/test/libsolidity/semanticTests/externalContracts/snark.sol @@ -18,9 +18,9 @@ library Pairing { function P2() internal returns (G2Point memory) { return G2Point( [11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781], + 10857046999023057135944570762232829481370756359578518086990519993285655852781], [4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930] + 8495653923123431417604973247489272438418190587263600148770280649306958101930] ); } @@ -43,7 +43,7 @@ library Pairing { bool success; assembly { success := call(sub(gas(), 2000), 6, 0, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work + // Use "invalid" to make gas estimation work switch success case 0 { invalid() } } require(success); @@ -59,7 +59,7 @@ library Pairing { bool success; assembly { success := call(sub(gas(), 2000), 7, 0, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work + // Use "invalid" to make gas estimation work switch success case 0 { invalid() } } require(success); @@ -87,7 +87,7 @@ library Pairing { bool success; assembly { success := call(sub(gas(), 2000), 8, 0, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work + // Use "invalid" to make gas estimation work switch success case 0 { invalid() } } require(success); @@ -121,7 +121,7 @@ library Pairing { G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, G1Point memory c1, G2Point memory c2, - G1Point memory d1, G2Point memory d2 + G1Point memory d1, G2Point memory d2 ) internal returns (bool) { G1Point[] memory p1 = new G1Point[](4); G2Point[] memory p2 = new G2Point[](4); @@ -289,6 +289,7 @@ contract Test { // // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // library: Pairing // f() -> true diff --git a/test/libsolidity/semanticTests/externalContracts/snark_eof.sol b/test/libsolidity/semanticTests/externalContracts/snark_eof.sol new file mode 100644 index 000000000000..ff1db7419b51 --- /dev/null +++ b/test/libsolidity/semanticTests/externalContracts/snark_eof.sol @@ -0,0 +1,312 @@ +library Pairing { + struct G1Point { + uint X; + uint Y; + } + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint[2] X; + uint[2] Y; + } + + /// @return the generator of G1 + function P1() internal returns (G1Point memory) { + return G1Point(1, 2); + } + + /// @return the generator of G2 + function P2() internal returns (G2Point memory) { + return G2Point( + [11559732032986387107991004021392285783925812861821192530917403151452391805634, + 10857046999023057135944570762232829481370756359578518086990519993285655852781], + [4082367875863433681332203403145435568316851327593401208105741076214120093531, + 8495653923123431417604973247489272438418190587263600148770280649306958101930] + ); + } + + /// @return the negation of p, i.e. p.add(p.negate()) should be zero. + function negate(G1Point memory p) internal returns (G1Point memory) { + // The prime q in the base field F_q for G1 + uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) + return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function add(G1Point memory p1, G1Point memory p2) internal returns (G1Point memory r) { + uint[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + uint return_flag; + assembly { + return_flag := extcall(6, input, 0xc0, 0) + // Use "invalid" to make gas estimation work + switch return_flag case 1 { invalid() } case 2 { invalid() } + + returndatacopy(r, 0, 64) + } + require(return_flag == 0); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.mul(1) and p.add(p) == p.mul(2) for all points p. + function mul(G1Point memory p, uint s) internal returns (G1Point memory r) { + uint[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + uint return_flag; + assembly { + return_flag := extcall(7, input, 0x80, 0) + // Use "invalid" to make gas estimation work + switch return_flag case 1 { invalid() } case 2 { invalid() } + + returndatacopy(r, 0, 64) + } + require(return_flag == 0); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) internal returns (bool) { + require(p1.length == p2.length); + uint elements = p1.length; + uint inputSize = p1.length * 6; + uint[] memory input = new uint[](inputSize); + for (uint i = 0; i < elements; i++) + { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint[1] memory out; + uint return_flag; + assembly { + return_flag := extcall(8, add(input, 0x20), mul(inputSize, 0x20), 0) + // Use "invalid" to make gas estimation work + switch return_flag case 1 { invalid() } case 2 { invalid() } + + returndatacopy(out, 0, 32) + } + require(return_flag == 0); + return out[0] != 0; + } + function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + function pairingProd3( + G1Point memory a1, G2Point memory a2, + G1Point memory b1, G2Point memory b2, + G1Point memory c1, G2Point memory c2 + ) internal returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + function pairingProd4( + G1Point memory a1, G2Point memory a2, + G1Point memory b1, G2Point memory b2, + G1Point memory c1, G2Point memory c2, + G1Point memory d1, G2Point memory d2 + ) internal returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} + +contract Test { + using Pairing for *; + struct VerifyingKey { + Pairing.G2Point A; + Pairing.G1Point B; + Pairing.G2Point C; + Pairing.G2Point gamma; + Pairing.G1Point gammaBeta1; + Pairing.G2Point gammaBeta2; + Pairing.G2Point Z; + Pairing.G1Point[] IC; + } + struct Proof { + Pairing.G1Point A; + Pairing.G1Point A_p; + Pairing.G2Point B; + Pairing.G1Point B_p; + Pairing.G1Point C; + Pairing.G1Point C_p; + Pairing.G1Point K; + Pairing.G1Point H; + } + function f() public returns (bool) { + Pairing.G1Point memory p1; + Pairing.G1Point memory p2; + p1.X = 1; p1.Y = 2; + p2.X = 1; p2.Y = 2; + Pairing.G1Point memory explicit_sum = Pairing.add(p1, p2); + Pairing.G1Point memory scalar_prod = Pairing.mul(p1, 2); + return (explicit_sum.X == scalar_prod.X && + explicit_sum.Y == scalar_prod.Y); + } + function g() public returns (bool) { + Pairing.G1Point memory x = Pairing.add(Pairing.P1(), Pairing.negate(Pairing.P1())); + // should be zero + return (x.X == 0 && x.Y == 0); + } + function testMul() public returns (bool) { + Pairing.G1Point memory p; + // @TODO The points here are reported to be not well-formed + p.X = 14125296762497065001182820090155008161146766663259912659363835465243039841726; + p.Y = 16229134936871442251132173501211935676986397196799085184804749187146857848057; + p = Pairing.mul(p, 13986731495506593864492662381614386532349950841221768152838255933892789078521); + return + p.X == 18256332256630856740336504687838346961237861778318632856900758565550522381207 && + p.Y == 6976682127058094634733239494758371323697222088503263230319702770853579280803; + } + function pair() public returns (bool) { + Pairing.G2Point memory fiveTimesP2 = Pairing.G2Point( + [4540444681147253467785307942530223364530218361853237193970751657229138047649, 20954117799226682825035885491234530437475518021362091509513177301640194298072], + [11631839690097995216017572651900167465857396346217730511548857041925508482915, 21508930868448350162258892668132814424284302804699005394342512102884055673846] + ); + // The prime p in the base field F_p for G1 + uint p = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + Pairing.G1Point[] memory g1points = new Pairing.G1Point[](2); + Pairing.G2Point[] memory g2points = new Pairing.G2Point[](2); + // check e(5 P1, P2)e(-P1, 5 P2) == 1 + g1points[0] = Pairing.P1().mul(5); + g1points[1] = Pairing.P1().negate(); + g2points[0] = Pairing.P2(); + g2points[1] = fiveTimesP2; + if (!Pairing.pairing(g1points, g2points)) + return false; + // check e(P1, P2)e(-P1, P2) == 1 + g1points[0] = Pairing.P1(); + g1points[1] = Pairing.P1(); + g1points[1].Y = p - g1points[1].Y; + g2points[0] = Pairing.P2(); + g2points[1] = Pairing.P2(); + if (!Pairing.pairing(g1points, g2points)) + return false; + return true; + } + function verifyingKey() internal returns (VerifyingKey memory vk) { + vk.A = Pairing.G2Point([0x209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7, 0x04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678], [0x2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d, 0x120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550]); + vk.B = Pairing.G1Point(0x2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc02, 0x03d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db84); + vk.C = Pairing.G2Point([0x2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb, 0x01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb3], [0x14a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713, 0x178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee24590]); + vk.gamma = Pairing.G2Point([0x25f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb1, 0x22acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d], [0x065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf68, 0x06d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb]); + vk.gammaBeta1 = Pairing.G1Point(0x15794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f21, 0x14db745c6780e9df549864cec19c2daf4531f6ec0c89cc1c7436cc4d8d300c6d); + vk.gammaBeta2 = Pairing.G2Point([0x1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e, 0x283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39], [0x140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e, 0x0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd4]); + vk.Z = Pairing.G2Point([0x217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac29, 0x0a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c], [0x26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a9855, 0x2fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d7]); + vk.IC = new Pairing.G1Point[](10); + vk.IC[0] = Pairing.G1Point(0x0aee46a7ea6e80a3675026dfa84019deee2a2dedb1bbe11d7fe124cb3efb4b5a, 0x044747b6e9176e13ede3a4dfd0d33ccca6321b9acd23bf3683a60adc0366ebaf); + vk.IC[1] = Pairing.G1Point(0x1e39e9f0f91fa7ff8047ffd90de08785777fe61c0e3434e728fce4cf35047ddc, 0x2e0b64d75ebfa86d7f8f8e08abbe2e7ae6e0a1c0b34d028f19fa56e9450527cb); + vk.IC[2] = Pairing.G1Point(0x1c36e713d4d54e3a9644dffca1fc524be4868f66572516025a61ca542539d43f, 0x042dcc4525b82dfb242b09cb21909d5c22643dcdbe98c4d082cc2877e96b24db); + vk.IC[3] = Pairing.G1Point(0x17d5d09b4146424bff7e6fb01487c477bbfcd0cdbbc92d5d6457aae0b6717cc5, 0x02b5636903efbf46db9235bbe74045d21c138897fda32e079040db1a16c1a7a1); + vk.IC[4] = Pairing.G1Point(0x0f103f14a584d4203c27c26155b2c955f8dfa816980b24ba824e1972d6486a5d, 0x0c4165133b9f5be17c804203af781bcf168da7386620479f9b885ecbcd27b17b); + vk.IC[5] = Pairing.G1Point(0x232063b584fb76c8d07995bee3a38fa7565405f3549c6a918ddaa90ab971e7f8, 0x2ac9b135a81d96425c92d02296322ad56ffb16299633233e4880f95aafa7fda7); + vk.IC[6] = Pairing.G1Point(0x09b54f111d3b2d1b2fe1ae9669b3db3d7bf93b70f00647e65c849275de6dc7fe, 0x18b2e77c63a3e400d6d1f1fbc6e1a1167bbca603d34d03edea231eb0ab7b14b4); + vk.IC[7] = Pairing.G1Point(0x0c54b42137b67cc268cbb53ac62b00ecead23984092b494a88befe58445a244a, 0x18e3723d37fae9262d58b548a0575f59d9c3266db7afb4d5739555837f6b8b3e); + vk.IC[8] = Pairing.G1Point(0x0a6de0e2240aa253f46ce0da883b61976e3588146e01c9d8976548c145fe6e4a, 0x04fbaa3a4aed4bb77f30ebb07a3ec1c7d77a7f2edd75636babfeff97b1ea686e); + vk.IC[9] = Pairing.G1Point(0x111e2e2a5f8828f80ddad08f9f74db56dac1cc16c1cb278036f79a84cf7a116f, 0x1d7d62e192b219b9808faa906c5ced871788f6339e8d91b83ac1343e20a16b30); + } + function verify(uint[] memory input, Proof memory proof) internal returns (uint) { + VerifyingKey memory vk = verifyingKey(); + require(input.length + 1 == vk.IC.length); + // Compute the linear combination vk_x + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint i = 0; i < input.length; i++) + vk_x = Pairing.add(vk_x, Pairing.mul(vk.IC[i + 1], input[i])); + vk_x = Pairing.add(vk_x, vk.IC[0]); + if (!Pairing.pairingProd2(proof.A, vk.A, Pairing.negate(proof.A_p), Pairing.P2())) return 1; + if (!Pairing.pairingProd2(vk.B, proof.B, Pairing.negate(proof.B_p), Pairing.P2())) return 2; + if (!Pairing.pairingProd2(proof.C, vk.C, Pairing.negate(proof.C_p), Pairing.P2())) return 3; + if (!Pairing.pairingProd3( + proof.K, vk.gamma, + Pairing.negate(Pairing.add(vk_x, Pairing.add(proof.A, proof.C))), vk.gammaBeta2, + Pairing.negate(vk.gammaBeta1), proof.B + )) return 4; + if (!Pairing.pairingProd3( + Pairing.add(vk_x, proof.A), proof.B, + Pairing.negate(proof.H), vk.Z, + Pairing.negate(proof.C), Pairing.P2() + )) return 5; + return 0; + } + event Verified(string); + function verifyTx() public returns (bool) { + uint[] memory input = new uint[](9); + Proof memory proof; + proof.A = Pairing.G1Point(12873740738727497448187997291915224677121726020054032516825496230827252793177, 21804419174137094775122804775419507726154084057848719988004616848382402162497); + proof.A_p = Pairing.G1Point(7742452358972543465462254569134860944739929848367563713587808717088650354556, 7324522103398787664095385319014038380128814213034709026832529060148225837366); + proof.B = Pairing.G2Point( + [8176651290984905087450403379100573157708110416512446269839297438960217797614, 15588556568726919713003060429893850972163943674590384915350025440408631945055], + [15347511022514187557142999444367533883366476794364262773195059233657571533367, 4265071979090628150845437155927259896060451682253086069461962693761322642015]); + proof.B_p = Pairing.G1Point(2979746655438963305714517285593753729335852012083057917022078236006592638393, 6470627481646078059765266161088786576504622012540639992486470834383274712950); + proof.C = Pairing.G1Point(6851077925310461602867742977619883934042581405263014789956638244065803308498, 10336382210592135525880811046708757754106524561907815205241508542912494488506); + proof.C_p = Pairing.G1Point(12491625890066296859584468664467427202390981822868257437245835716136010795448, 13818492518017455361318553880921248537817650587494176379915981090396574171686); + proof.H = Pairing.G1Point(12091046215835229523641173286701717671667447745509192321596954139357866668225, 14446807589950902476683545679847436767890904443411534435294953056557941441758); + proof.K = Pairing.G1Point(21341087976609916409401737322664290631992568431163400450267978471171152600502, 2942165230690572858696920423896381470344658299915828986338281196715687693170); + input[0] = 13986731495506593864492662381614386532349950841221768152838255933892789078521; + input[1] = 622860516154313070522697309645122400675542217310916019527100517240519630053; + input[2] = 11094488463398718754251685950409355128550342438297986977413505294941943071569; + input[3] = 6627643779954497813586310325594578844876646808666478625705401786271515864467; + input[4] = 2957286918163151606545409668133310005545945782087581890025685458369200827463; + input[5] = 1384290496819542862903939282897996566903332587607290986044945365745128311081; + input[6] = 5613571677741714971687805233468747950848449704454346829971683826953541367271; + input[7] = 9643208548031422463313148630985736896287522941726746581856185889848792022807; + input[8] = 18066496933330839731877828156604; + if (verify(input, proof) == 0) { + emit Verified("Successfully verified."); + return true; + } else { + return false; + } + } +} +/// Disabled because the point seems to be not well-formed, we need to find another example. +/// testMul() -> true +// +// ==== +// EVMVersion: >=constantinople +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// library: Pairing +// f() -> true +// g() -> true +// pair() -> true +// gas irOptimized: 270424 +// gas legacy: 275206 +// gas legacyOptimized: 266925 +// verifyTx() -> true +// ~ emit Verified(string): 0x20, 0x16, "Successfully verified." +// gas irOptimized: 785783 +// gas legacy: 801868 +// gas legacyOptimized: 770942 diff --git a/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol b/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol index 7a05ff5d0270..3df7b02d346c 100644 --- a/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol +++ b/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol @@ -11,5 +11,7 @@ contract D { return test(); } } +// ==== +// compileToEOF: false // ---- // f() -> true diff --git a/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol b/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol index 229618384d1e..f34b2b1a30be 100644 --- a/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol +++ b/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol @@ -21,6 +21,8 @@ contract C { return 7; } } +// ==== +// compileToEOF: false // ---- // f() -> FAILURE // g() -> FAILURE diff --git a/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol b/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol index 6f7f020fe678..88dc405f2425 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol @@ -18,6 +18,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // f(uint256): 0 -> FAILURE // f(uint256): 1 -> FAILURE diff --git a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol index 472921c1d25c..e25ae41d150e 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol @@ -20,6 +20,8 @@ contract C { return 1 + c; } } +// ==== +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 88853 diff --git a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol index d1b6e8e866ae..9e8590e510a3 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol @@ -23,6 +23,7 @@ contract C { // ==== // EVMVersion: >=byzantium // revertStrings: debug +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 98698 diff --git a/test/libsolidity/semanticTests/functionCall/failed_create.sol b/test/libsolidity/semanticTests/functionCall/failed_create.sol index 657b4b5cfff6..0f583a2831db 100644 --- a/test/libsolidity/semanticTests/functionCall/failed_create.sol +++ b/test/libsolidity/semanticTests/functionCall/failed_create.sol @@ -15,6 +15,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // constructor(), 20 wei // gas irOptimized: 61548 diff --git a/test/libsolidity/semanticTests/functionCall/failed_create_eof.sol b/test/libsolidity/semanticTests/functionCall/failed_create_eof.sol new file mode 100644 index 000000000000..9457cdecbd2f --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/failed_create_eof.sol @@ -0,0 +1,38 @@ +contract D { constructor() payable {} } +contract C { + uint public x; + constructor() payable {} + function f(uint amount) public returns (D) { + x++; + return (new D){value: amount, salt: bytes32(x)}(); + } + function stack(uint depth) public payable returns (address) { + if (depth > 0) + return this.stack(depth - 1); + else + return address(f(0)); + } +} +// ==== +// EVMVersion: >=byzantium +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 20 wei +// gas irOptimized: 61548 +// gas irOptimized code: 104600 +// gas legacy: 70147 +// gas legacy code: 215400 +// gas legacyOptimized: 61715 +// gas legacyOptimized code: 106800 +// f(uint256): 20 -> 0x760ad2428f897a994a0377ef5a3b626dfe295672 +// x() -> 1 +// f(uint256): 20 -> FAILURE +// x() -> 1 +// stack(uint256): 1023 -> FAILURE +// gas irOptimized: 252410 +// gas legacy: 477722 +// gas legacyOptimized: 299567 +// x() -> 1 +// stack(uint256): 10 -> 0xd64ba06e96a2fa752f7c8e10c7b6912e518e40c2 +// x() -> 2 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol index e6187d06e2df..a8e1454cb615 100644 --- a/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol @@ -36,6 +36,8 @@ contract test { myBal = address(this).balance; } } +// ==== +// compileToEOF: false // ---- // constructor(), 20 wei -> // gas irOptimized: 120218 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol new file mode 100644 index 000000000000..bb52384134d4 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol @@ -0,0 +1,52 @@ +contract helper { + bool flag; + + function getBalance() public payable returns (uint256 myBalance) { + return address(this).balance; + } + + function setFlag() public { + flag = true; + } + + function getFlag() public returns (bool fl) { + return flag; + } +} + + +contract test { + helper h; + + constructor() payable { + h = new helper(); + } + + function sendAmount(uint256 amount) public payable returns (uint256 bal) { + return h.getBalance{value: amount}(); + } + + function outOfGas() public returns (bool ret) { + h.setFlag(); + return true; + } + + function checkState() public returns (bool flagAfter, uint256 myBal) { + flagAfter = h.getFlag(); + myBal = address(this).balance; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 20 wei -> +// gas irOptimized: 120218 +// gas irOptimized code: 132000 +// gas legacy: 130568 +// gas legacy code: 261000 +// gas legacyOptimized: 121069 +// gas legacyOptimized code: 147000 +// sendAmount(uint256): 5 -> 5 +// outOfGas() -> true +// checkState() -> true, 15 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol index 41079af03955..a8a70bbe38d1 100644 --- a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol @@ -35,6 +35,8 @@ contract test { myBal = address(this).balance; } } +// ==== +// compileToEOF: false // ---- // constructor(), 20 wei -> // gas irOptimized: 120218 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol new file mode 100644 index 000000000000..a382519cc1c5 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol @@ -0,0 +1,51 @@ +contract helper { + bool flag; + + function getBalance() payable public returns(uint256 myBalance) { + return address(this).balance; + } + + function setFlag() public { + flag = true; + } + + function getFlag() public returns(bool fl) { + return flag; + } +} +contract test { + helper h; + constructor() payable { + h = new helper(); + } + + function sendAmount(uint amount) public payable returns(uint256 bal) { + return h.getBalance{value: amount}(); + } + + function outOfGas() public returns(bool ret) { + h.setFlag { + gas: 2 + }(); + return true; + } + + function checkState() public returns(bool flagAfter, uint myBal) { + flagAfter = h.getFlag(); + myBal = address(this).balance; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 20 wei -> +// gas irOptimized: 120218 +// gas irOptimized code: 132000 +// gas legacy: 130568 +// gas legacy code: 261000 +// gas legacyOptimized: 121069 +// gas legacyOptimized code: 147000 +// sendAmount(uint256): 5 -> 5 +// outOfGas() -> true +// checkState() -> true, 15 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/functionTypes/address_member.sol b/test/libsolidity/semanticTests/functionTypes/address_member.sol index a5f56f3dc688..bf3ea77b52d1 100644 --- a/test/libsolidity/semanticTests/functionTypes/address_member.sol +++ b/test/libsolidity/semanticTests/functionTypes/address_member.sol @@ -6,5 +6,7 @@ contract C { a2 = [this.f.address][0]; } } +// ==== +// compileToEOF: false // ---- // f() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e, 0xc06afe3a8444fc0004668591e8306bfb9968e79e diff --git a/test/libsolidity/semanticTests/functionTypes/address_member_eof.sol b/test/libsolidity/semanticTests/functionTypes/address_member_eof.sol new file mode 100644 index 000000000000..3d657a23222b --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/address_member_eof.sol @@ -0,0 +1,13 @@ +contract C { + function f() public view returns (address a1, address a2) { + a1 = this.f.address; + this.f.address; + [this.f.address][0]; + a2 = [this.f.address][0]; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0xab639b56c881a5b607f4a706bcf1d7d383b83703, 0xab639b56c881a5b607f4a706bcf1d7d383b83703 diff --git a/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol index d5e105e5f536..26f9206aca0c 100644 --- a/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol +++ b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol @@ -19,6 +19,8 @@ contract C { delete x; } } +// ==== +// compileToEOF: false // ---- // x() -> 0 // y() -> 0 diff --git a/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol new file mode 100644 index 000000000000..2ad999449a59 --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol @@ -0,0 +1,40 @@ +contract C { + function() external public x; + uint public y = 0; + + function increment() public { + ++y; + } + + function set() external { + x = this.increment; + } + + function incrementIndirectly() public { + x(); + } + + function deleteFunction() public { + // used to lead to an ICE during IR + delete x; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// x() -> 0 +// y() -> 0 +// increment() -> +// y() -> 1 +// set() -> +// x() -> 0xb7ac6c1bf268409abee9be742a25c518cfd13729d09de08a0000000000000000 +// increment() -> +// y() -> 2 +// incrementIndirectly() -> +// y() -> 3 +// deleteFunction() -> +// increment() -> +// y() -> 4 +// incrementIndirectly() -> +// y() -> 4 diff --git a/test/libsolidity/semanticTests/immutable/multi_creation.sol b/test/libsolidity/semanticTests/immutable/multi_creation.sol index aa8a1ad8a860..68b5a6211f8e 100644 --- a/test/libsolidity/semanticTests/immutable/multi_creation.sol +++ b/test/libsolidity/semanticTests/immutable/multi_creation.sol @@ -25,6 +25,8 @@ contract C { return (a, (new A()).f(), (new B()).f()); } } +// ==== +// compileToEOF: false // ---- // f() -> 3, 7, 5 // gas irOptimized: 86796 diff --git a/test/libsolidity/semanticTests/immutable/multi_creation_eof.sol b/test/libsolidity/semanticTests/immutable/multi_creation_eof.sol new file mode 100644 index 000000000000..9c376fc70ff8 --- /dev/null +++ b/test/libsolidity/semanticTests/immutable/multi_creation_eof.sol @@ -0,0 +1,40 @@ +contract A { + uint immutable a; + constructor() { + a = 7; + } + function f() public view returns (uint) { return a; } +} +contract B { + uint immutable a; + constructor() { + a = 5; + } + function f() public view returns (uint) { return a; } +} +contract C { + uint immutable a; + uint public x; + uint public y; + constructor() { + a = 3; + x = (new A{salt: hex"00"}()).f(); + y = (new B{salt: hex"00"}()).f(); + } + function f() public returns (uint256, uint, uint) { + return (a, (new A{salt: hex"01"}()).f(), (new B{salt: hex"01"}()).f()); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 3, 7, 5 +// gas irOptimized: 86796 +// gas irOptimized code: 37200 +// gas legacy: 87728 +// gas legacy code: 60800 +// gas legacyOptimized: 86771 +// gas legacyOptimized code: 37200 +// x() -> 7 +// y() -> 5 diff --git a/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol b/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol index 0865ed2876ad..25c31c95f1bb 100644 --- a/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol +++ b/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol @@ -18,6 +18,8 @@ contract D { return (new C()).transfer(5); } } +// ==== +// compileToEOF: false // ---- // f() -> 1 // gas irOptimized: 77051 diff --git a/test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol b/test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol new file mode 100644 index 000000000000..726aba9911a9 --- /dev/null +++ b/test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol @@ -0,0 +1,32 @@ +contract C { + function balance() public returns (uint256) { + return 1; + } + + function transfer(uint256 amount) public returns (uint256) { + return amount; + } +} + + +contract D { + function f() public returns (uint256) { + return (new C{salt: hex"00"}()).balance(); + } + + function g() public returns (uint256) { + return (new C{salt: hex"01"}()).transfer(5); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 1 +// gas irOptimized: 77051 +// gas legacy: 54480 +// gas legacy code: 57800 +// g() -> 5 +// gas irOptimized: 77106 +// gas legacy: 55016 +// gas legacy code: 57800 diff --git a/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol b/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol index b9697c5ae1d7..26aaed73d511 100644 --- a/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol +++ b/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol @@ -17,6 +17,8 @@ contract A { return d.getX(); } } +// ==== +// compileToEOF: false // ---- // g(int256): -1 -> -1 // gas legacy: 77876 diff --git a/test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol b/test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol new file mode 100644 index 000000000000..6937a6743db6 --- /dev/null +++ b/test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol @@ -0,0 +1,29 @@ +==== Source: A ==== +contract C { + int private x; + constructor (int p) public { x = p; } + function getX() public returns (int) { return x; } +} +==== Source: B ==== +import "A" as M; + +contract D is M.C { + constructor (int p) M.C(p) public {} +} + +contract A { + function g(int p) public returns (int) { + D d = new D{salt: bytes32(uint256(p))}(p); + return d.getX(); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// g(int256): -1 -> -1 +// gas legacy: 77878 +// gas legacy code: 24200 +// g(int256): 10 -> 10 +// gas legacy: 77506 +// gas legacy code: 24200 diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol index ae891df5dc4f..4b46ac74b02b 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol @@ -12,6 +12,8 @@ contract C { return this.testFunction.address; } } +// ==== +// compileToEOF: false // ---- // testYul() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e // testSol() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol new file mode 100644 index 000000000000..c1823c6f4c30 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol @@ -0,0 +1,20 @@ +contract C { + function testFunction() external {} + + function testYul() public returns (address adr) { + function() external fp = this.testFunction; + + assembly { + adr := fp.address + } + } + function testSol() public returns (address) { + return this.testFunction.address; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testYul() -> 0xb546c6ff998ecadc80f48650a3d77fd361aebb4e +// testSol() -> 0xb546c6ff998ecadc80f48650a3d77fd361aebb4e diff --git a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol index e43fc475740a..540dbeb163d6 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol @@ -63,6 +63,7 @@ contract C { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // testDelegateCall() -> true // testCall() -> true diff --git a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol new file mode 100644 index 000000000000..dcd7ab5ca68b --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol @@ -0,0 +1,78 @@ +contract D { + function addOne() external { + assembly { + let x := tload(0) + tstore(0, add(x, 1)) + } + } + function get() external returns (uint x) { + assembly { + x := tload(0) + } + } +} + +contract C { + function set(uint x) external { + assembly { + tstore(0, x) + } + } + + function get() external view returns (uint x) { + assembly { + x := tload(0) + } + } + + function testDelegateCall() external returns (bool) { + this.set(5); + D d = new D{salt: hex"00"}(); + // Caller contract is the owner of the transient storage + (bool success, ) = address(d).delegatecall(abi.encodeCall(d.addOne, ())); + require(success); + require(this.get() == 6); + return true; + } + + function testCall() external returns (bool) { + this.set(5); + D d = new D{salt: hex"01"}(); + // Callee/Target contract is the owner of the transient storage + (bool success, ) = address(d).call(abi.encodeCall(d.addOne, ())); + require(success); + require(d.get() == 1); + return true; + } + + function tloadAllowedStaticCall() external returns (bool) { + this.set(5); + D d = new D{salt: hex"02"}(); + (bool success, bytes memory result) = address(d).staticcall(abi.encodeCall(d.get, ())); + require(success); + require(abi.decode(result, (uint)) == 0); + return true; + } + + function tstoreNotAllowedStaticCall() external returns (bool) { + D d = new D{salt: hex"03"}(); + (bool success, ) = address(d).staticcall(abi.encodeCall(d.addOne, ())); + require(!success); + return true; + } +} +// ==== +// EVMVersion: >=cancun +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testDelegateCall() -> true +// testCall() -> true +// tloadAllowedStaticCall() -> true +// tstoreNotAllowedStaticCall() -> true +// gas irOptimized: 98419720 +// gas irOptimized code: 19000 +// gas legacy: 98409086 +// gas legacy code: 30000 +// gas legacyOptimized: 98420962 +// gas legacyOptimized code: 17800 diff --git a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol index c2066ef8be1e..928a2a07bb69 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol @@ -38,6 +38,7 @@ contract D { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor() -> // gas irOptimized: 127596 diff --git a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol index 62e1e4fba6dc..9a7ac401cbd6 100644 --- a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol +++ b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol @@ -32,6 +32,8 @@ contract C { return (sb.parentFun(), sb.subBFun()); } } +// ==== +// compileToEOF: false // ---- // convertParent() -> 1 // gas irOptimized: 85524 diff --git a/test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol b/test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol new file mode 100644 index 000000000000..8a4c7d47e3f9 --- /dev/null +++ b/test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol @@ -0,0 +1,47 @@ +interface Parent { + function parentFun() external returns (uint256); +} + +interface SubA is Parent { + function subAFun() external returns (uint256); +} + +interface SubB is Parent { + function subBFun() external returns (uint256); +} + +contract Impl is SubA, SubB { + function parentFun() override external returns (uint256) { return 1; } + function subAFun() override external returns (uint256) { return 2; } + function subBFun() override external returns (uint256) { return 3; } +} + +contract C { + function convertParent() public returns (uint256) { + Parent p = new Impl(); + return p.parentFun(); + } + + function convertSubA() public returns (uint256, uint256) { + bytes32 s = 0x0000000000000000000000000000000000000000000000000000000000000001; + SubA sa = new Impl{salt: s}(); + return (sa.parentFun(), sa.subAFun()); + } + + function convertSubB() public returns (uint256, uint256) { + bytes32 s = 0x0000000000000000000000000000000000000000000000000000000000000002; + SubB sb = new Impl{salt: s}(); + return (sb.parentFun(), sb.subBFun()); + } +} +// ==== +// compileToEOF: false +// ---- +// convertParent() -> 1 +// gas irOptimized: 85524 +// convertSubA() -> 1, 2 +// gas irOptimized: 86155 +// gas legacy: 99047 +// convertSubB() -> 1, 3 +// gas irOptimized: 86098 +// gas legacy: 98981 diff --git a/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol index 1240a8251427..b99b63d14320 100644 --- a/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol +++ b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol @@ -14,6 +14,8 @@ contract ClientReceipt { return other.getAddress(); } } +// ==== +// compileToEOF: false // ---- // constructor(), 2000 wei -> // gas irOptimized: 114353 diff --git a/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol new file mode 100644 index 000000000000..39b48fa66347 --- /dev/null +++ b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol @@ -0,0 +1,33 @@ +contract Other { + constructor() payable { + } + function getAddress() public returns (address) { + return address(this); + } +} +contract ClientReceipt { + Other other; + constructor() payable { + other = new Other{value:500}(); + } + function getAddress() public returns (address) { + return other.getAddress(); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 2000 wei -> +// gas irOptimized: 114353 +// gas irOptimized code: 58800 +// gas legacy: 118618 +// gas legacy code: 111400 +// gas legacyOptimized: 114067 +// gas legacyOptimized code: 59800 +// balance -> 1500 +// gas irOptimized: 191881 +// gas legacy: 235167 +// gas legacyOptimized: 180756 +// getAddress() -> 0xbeeb053868faf7a26a382b708a61fad0cfab5a48 +// balance: 0xbeeb053868faf7a26a382b708a61fad0cfab5a48 -> 500 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol index ec41099eecbb..231c0c531536 100644 --- a/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol @@ -50,6 +50,8 @@ contract C { return -x; } } +// ==== +// compileToEOF: false // ---- // testMul(int32,int32): 42, 10 -> 420 // gas irOptimized: 102563 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol new file mode 100644 index 000000000000..bf27cebb3b73 --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol @@ -0,0 +1,68 @@ +type Int32 is int32; +using {add as +, unsub as -} for Int32 global; + +function add(Int32 x, Int32 y) pure returns (Int32) { + return loadAdder().mul(x, y); +} + +function unsub(Int32 x) pure returns (Int32) { + return loadAdder().inc(x); +} + +interface IAdder { + function mul(Int32, Int32) external pure returns (Int32); + function inc(Int32) external pure returns (Int32); +} + +contract Adder is IAdder { + function mul(Int32 x, Int32 y) external pure override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) * Int32.unwrap(y)); + } + + function inc(Int32 x) external pure override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) + 1); + } +} + +function storeAdder(IAdder adder) pure { + assembly { + // This test would also work without assembly if we could hard-code an address here. + mstore(0, adder) + } +} + +function loadAdder() pure returns (IAdder adder) { + assembly { + adder := mload(0) + } +} + +contract C { + function testMul(Int32 x, Int32 y) public returns (Int32) { + storeAdder(new Adder{salt: hex"00"}()); + + return x + y; + } + + function testInc(Int32 x) public returns (Int32) { + storeAdder(new Adder{salt: hex"01"}()); + + return -x; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testMul(int32,int32): 42, 10 -> 420 +// gas irOptimized: 102563 +// gas legacy: 56981 +// gas legacy code: 127000 +// gas legacyOptimized: 55163 +// gas legacyOptimized code: 68400 +// testInc(int32): 42 -> 43 +// gas irOptimized: 102386 +// gas legacy: 56239 +// gas legacy code: 127000 +// gas legacyOptimized: 54851 +// gas legacyOptimized code: 68400 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol index 7e70eb091bf6..ebd20f32b2ae 100644 --- a/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol @@ -56,6 +56,8 @@ contract C { return -x; } } +// ==== +// compileToEOF: false // ---- // testMul(int32,int32): 42, 10 -> 420 // gas irOptimized: 102563 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol new file mode 100644 index 000000000000..bc78fb261a7e --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol @@ -0,0 +1,74 @@ +type Int32 is int32; +using {add as +, unsub as -} for Int32 global; + +function add(Int32 x, Int32 y) pure returns (Int32) { + return loadAdder().mul(x, y); +} + +function unsub(Int32 x) pure returns (Int32) { + return loadAdder().inc(x); +} + +interface IAdderPure { + function mul(Int32, Int32) external pure returns (Int32); + function inc(Int32) external pure returns (Int32); +} + +interface IAdderView { + function mul(Int32, Int32) external view returns (Int32); + function inc(Int32) external view returns (Int32); +} + +contract Adder is IAdderView { + function mul(Int32 x, Int32 y) external view override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) * Int32.unwrap(y)); + } + + function inc(Int32 x) external view override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) + 1); + } +} + +function storeAdder(IAdderView adder) pure { + assembly { + // This test would also work without assembly if we could hard-code an address here. + mstore(0, adder) + } +} + +function loadAdder() pure returns (IAdderPure adder) { + assembly { + // The adder we stored is view but we cheat by using a modified version with pure functions + adder := mload(0) + } +} + +contract C { + function testMul(Int32 x, Int32 y) public returns (Int32) { + storeAdder(new Adder{salt: hex"00"}()); + + return x + y; + } + + function testInc(Int32 x) public returns (Int32) { + storeAdder(new Adder{salt: hex"01"}()); + + return -x; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testMul(int32,int32): 42, 10 -> 420 +// gas irOptimized: 102563 +// gas legacy: 56981 +// gas legacy code: 127000 +// gas legacyOptimized: 55163 +// gas legacyOptimized code: 68400 +// testInc(int32): 42 -> 43 +// gas irOptimized: 102386 +// gas legacy: 56239 +// gas legacy code: 127000 +// gas legacyOptimized: 54851 +// gas legacyOptimized code: 68400 diff --git a/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol b/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol index 110b1e50c724..66e5553fede4 100644 --- a/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol +++ b/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol @@ -8,5 +8,6 @@ contract C { // ==== // EVMVersion: >=byzantium // revertStrings: debug +// compileToEOF: false // ---- // g() -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code" diff --git a/test/libsolidity/semanticTests/reverts/revert_return_area.sol b/test/libsolidity/semanticTests/reverts/revert_return_area.sol index 8ab4ca22722c..9aaa4f1b4a4a 100644 --- a/test/libsolidity/semanticTests/reverts/revert_return_area.sol +++ b/test/libsolidity/semanticTests/reverts/revert_return_area.sol @@ -14,5 +14,6 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // f() -> 0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol b/test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol new file mode 100644 index 000000000000..8d8e2b3279dd --- /dev/null +++ b/test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol @@ -0,0 +1,21 @@ +contract C { + fallback() external { + revert("abc"); + } + + function f() public returns (uint s, uint r) { + address x = address(this); + assembly { + mstore(0, 7) + s := extcall(x, 0, 0, 0) + returndatacopy(0, 0, 32) + r := mload(0) + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0x01, 0x08c379a000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/salted_create/prediction_example.sol b/test/libsolidity/semanticTests/salted_create/prediction_example.sol index 7d0e3c8b0dbc..89bac53e9d41 100644 --- a/test/libsolidity/semanticTests/salted_create/prediction_example.sol +++ b/test/libsolidity/semanticTests/salted_create/prediction_example.sol @@ -5,14 +5,33 @@ contract D { } } +// TODO: this is horrible and hopefully avoided at the spec level +function adjustContractCodeForArgSize(bytes memory x, uint16 argSize) +{ + assembly { + let memPos := add(x, 32) + if eq(shr(232, mload(memPos)), 0xef0001) { + let numCodeSections := shr(240, mload(add(memPos, 7))) + let dataSectionSizeOffset := add(memPos, add(10, mul(numCodeSections, 2))) + let tmp := mload(dataSectionSizeOffset) + let dataSectionSize := shr(240, tmp) + dataSectionSize := add(dataSectionSize, argSize) + if gt(dataSectionSize, 0xFFFF) { revert(0,0) } + mstore(dataSectionSizeOffset, or(shr(16, shl(16, tmp)), shl(240, dataSectionSize))) + } + } +} + contract C { function createDSalted(bytes32 salt, uint arg) public { + bytes memory creationCode = type(D).creationCode; + adjustContractCodeForArgSize(creationCode, 32); address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(abi.encodePacked( - type(D).creationCode, + creationCode, arg )) ))))); @@ -24,6 +43,7 @@ contract C { // ==== // EVMVersion: >=constantinople // compileViaYul: also +// compileToEOF: false // ---- // createDSalted(bytes32,uint256): 42, 64 -> // gas legacy: 78573 diff --git a/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol b/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol index 25d1935770d1..f92c5c3e2094 100644 --- a/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol +++ b/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol @@ -19,6 +19,7 @@ contract A { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // f(), 10 ether -> 3007, 3008, 3009 // gas irOptimized: 187022 diff --git a/test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol b/test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol new file mode 100644 index 000000000000..c7e28e6884d5 --- /dev/null +++ b/test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol @@ -0,0 +1,31 @@ +contract B +{ + uint x; + function getBalance() public view returns (uint) { + return address(this).balance * 1000 + x; + } + constructor(uint _x) payable { + x = _x; + } +} + +contract A { + function f() public payable returns (uint, uint, uint) { + B x = new B{salt: "abc0", value: 3}(7); + B y = new B{value: 3, salt: "abc1"}(8); + B z = new B{salt: "abc2", value: 3}(9); + return (x.getBalance(), y.getBalance(), z.getBalance()); + } +} +// ==== +// EVMVersion: >=constantinople +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f(), 10 ether -> 3007, 3008, 3009 +// gas irOptimized: 187022 +// gas irOptimized code: 67200 +// gas legacy: 190863 +// gas legacy code: 190200 +// gas legacyOptimized: 187258 +// gas legacyOptimized code: 92400 diff --git a/test/libsolidity/semanticTests/shanghai/evmone_support.sol b/test/libsolidity/semanticTests/shanghai/evmone_support.sol index 359dc516ffcd..b2fa6a69eaa0 100644 --- a/test/libsolidity/semanticTests/shanghai/evmone_support.sol +++ b/test/libsolidity/semanticTests/shanghai/evmone_support.sol @@ -26,6 +26,7 @@ contract Test { // ==== // compileViaYul: also // EVMVersion: >=shanghai +// compileToEOF: false // ---- // bytecode() -> 0x20, 4, 0x60205ff300000000000000000000000000000000000000000000000000000000 // isPush0Supported() -> true diff --git a/test/libsolidity/semanticTests/state/gasleft.sol b/test/libsolidity/semanticTests/state/gasleft.sol index 6ce623ce81ea..c6e29504fbeb 100644 --- a/test/libsolidity/semanticTests/state/gasleft.sol +++ b/test/libsolidity/semanticTests/state/gasleft.sol @@ -3,6 +3,8 @@ contract C { return gasleft() > 0; } } +// ==== +// compileToEOF: false // ---- // f() -> true // f() -> true diff --git a/test/libsolidity/semanticTests/tryCatch/create.sol b/test/libsolidity/semanticTests/tryCatch/create.sol index 43d0f22f6506..399bdc645cc4 100644 --- a/test/libsolidity/semanticTests/tryCatch/create.sol +++ b/test/libsolidity/semanticTests/tryCatch/create.sol @@ -27,6 +27,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // f() -> 0, 0, 96, 13, "test message." // g() -> 0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0, 96, 7, "success" diff --git a/test/libsolidity/semanticTests/tryCatch/create_eof.sol b/test/libsolidity/semanticTests/tryCatch/create_eof.sol new file mode 100644 index 000000000000..9735fbcfe101 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/create_eof.sol @@ -0,0 +1,34 @@ +contract Reverts { + constructor(uint) { revert("test message."); } +} +contract Succeeds { + constructor(uint) { } +} + +contract C { + function f() public returns (Reverts x, uint, string memory txt) { + uint i = 3; + try new Reverts(i) returns (Reverts r) { + x = r; + txt = "success"; + } catch Error(string memory s) { + txt = s; + } + } + function g() public returns (Succeeds x, uint, string memory txt) { + uint i = 8; + try new Succeeds(i) returns (Succeeds r) { + x = r; + txt = "success"; + } catch Error(string memory s) { + txt = s; + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0, 0, 96, 13, "test message." +// g() -> 0xD1416F618F37EB929020DF731D1A78F392766843, 0, 96, 7, "success" diff --git a/test/libsolidity/semanticTests/tryCatch/return_function.sol b/test/libsolidity/semanticTests/tryCatch/return_function.sol index 6aac30fe4617..deb45122ae22 100644 --- a/test/libsolidity/semanticTests/tryCatch/return_function.sol +++ b/test/libsolidity/semanticTests/tryCatch/return_function.sol @@ -13,5 +13,7 @@ contract C { } function fun() public pure {} } +// ==== +// compileToEOF: false // ---- // f() -> 0x1, 0xc06afe3a8444fc0004668591e8306bfb9968e79e946644cd0000000000000000, 9 diff --git a/test/libsolidity/semanticTests/tryCatch/return_function_eof.sol b/test/libsolidity/semanticTests/tryCatch/return_function_eof.sol new file mode 100644 index 000000000000..397942e499a6 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/return_function_eof.sol @@ -0,0 +1,20 @@ +contract C { + function g() public returns (uint a, function() external h, uint b) { + a = 1; + h = this.fun; + b = 9; + } + function f() public returns (uint, function() external, uint) { + // Note that the function type uses two stack slots. + try this.g() returns (uint a, function() external h, uint b) { + return (a, h, b); + } catch { + } + } + function fun() public pure {} +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0x1, 0x67831474d284bf49471795e23b524e80a6b386a0946644cd0000000000000000, 9 diff --git a/test/libsolidity/semanticTests/various/address_code.sol b/test/libsolidity/semanticTests/various/address_code.sol index 549512eb3e44..fa8eff9c3819 100644 --- a/test/libsolidity/semanticTests/various/address_code.sol +++ b/test/libsolidity/semanticTests/various/address_code.sol @@ -12,6 +12,8 @@ contract C { function g() public view returns (uint) { return address(0).code.length; } function h() public view returns (uint) { return address(1).code.length; } } +// ==== +// compileToEOF: false // ---- // constructor() -> // gas irOptimized: 70760 diff --git a/test/libsolidity/semanticTests/various/address_code_complex.sol b/test/libsolidity/semanticTests/various/address_code_complex.sol index f2d21c9069b3..c9e185890c93 100644 --- a/test/libsolidity/semanticTests/various/address_code_complex.sol +++ b/test/libsolidity/semanticTests/various/address_code_complex.sol @@ -12,6 +12,8 @@ contract C { function f() public returns (bytes memory) { return address(new A()).code; } function g() public returns (uint) { return address(new A()).code.length; } } +// ==== +// compileToEOF: false // ---- // f() -> 0x20, 0x20, 0x48aa5566000000 // g() -> 0x20 diff --git a/test/libsolidity/semanticTests/various/code_access_content.sol b/test/libsolidity/semanticTests/various/code_access_content.sol index 22ceff337f89..b11807661479 100644 --- a/test/libsolidity/semanticTests/various/code_access_content.sol +++ b/test/libsolidity/semanticTests/various/code_access_content.sol @@ -36,6 +36,8 @@ contract C { return true; } } +// ==== +// compileToEOF: false // ---- // testRuntime() -> true // gas legacy: 76575 diff --git a/test/libsolidity/semanticTests/various/code_access_create.sol b/test/libsolidity/semanticTests/various/code_access_create.sol index f5f0b8644423..e17446a10dbf 100644 --- a/test/libsolidity/semanticTests/various/code_access_create.sol +++ b/test/libsolidity/semanticTests/various/code_access_create.sol @@ -21,6 +21,8 @@ contract C { return d.f(); } } +// ==== +// compileToEOF: false // ---- // test() -> 7 // gas legacy: 76647 diff --git a/test/libsolidity/semanticTests/various/code_access_padding.sol b/test/libsolidity/semanticTests/various/code_access_padding.sol index 831d4bde4ab4..ebb6b3a06528 100644 --- a/test/libsolidity/semanticTests/various/code_access_padding.sol +++ b/test/libsolidity/semanticTests/various/code_access_padding.sol @@ -14,5 +14,7 @@ contract C { } } } +// ==== +// compileToEOF: false // ---- // diff() -> 0 # This checks that the allocation function pads to multiples of 32 bytes # diff --git a/test/libsolidity/semanticTests/various/code_access_runtime.sol b/test/libsolidity/semanticTests/various/code_access_runtime.sol index 10d7c1852e95..6406acd4c41c 100644 --- a/test/libsolidity/semanticTests/various/code_access_runtime.sol +++ b/test/libsolidity/semanticTests/various/code_access_runtime.sol @@ -21,6 +21,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // test() -> 42 // gas legacy: 76034 diff --git a/test/libsolidity/semanticTests/various/code_length.sol b/test/libsolidity/semanticTests/various/code_length.sol index 844a0f65706f..5f331724be44 100644 --- a/test/libsolidity/semanticTests/various/code_length.sol +++ b/test/libsolidity/semanticTests/various/code_length.sol @@ -57,6 +57,8 @@ contract C { } } +// ==== +// compileToEOF: false // ---- // constructor() // gas legacy: 66989 diff --git a/test/libsolidity/semanticTests/various/code_length_contract_member.sol b/test/libsolidity/semanticTests/various/code_length_contract_member.sol index ff883139a46e..385e4b500171 100644 --- a/test/libsolidity/semanticTests/various/code_length_contract_member.sol +++ b/test/libsolidity/semanticTests/various/code_length_contract_member.sol @@ -11,5 +11,7 @@ contract C { return (s.code.length, s.another.length, address(this).code.length > 50); } } +// ==== +// compileToEOF: false // ---- // f() -> 0x20, 0x20, true diff --git a/test/libsolidity/semanticTests/various/codehash.sol b/test/libsolidity/semanticTests/various/codehash.sol index fa7dab9dab9f..bf373362ba72 100644 --- a/test/libsolidity/semanticTests/various/codehash.sol +++ b/test/libsolidity/semanticTests/various/codehash.sol @@ -13,6 +13,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // f() -> 0x0 // g() -> 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 diff --git a/test/libsolidity/semanticTests/various/codehash_assembly.sol b/test/libsolidity/semanticTests/various/codehash_assembly.sol index fe2210fa107a..f0a73142fe93 100644 --- a/test/libsolidity/semanticTests/various/codehash_assembly.sol +++ b/test/libsolidity/semanticTests/various/codehash_assembly.sol @@ -17,6 +17,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // f() -> 0 // g() -> 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 diff --git a/test/libsolidity/semanticTests/various/create_calldata.sol b/test/libsolidity/semanticTests/various/create_calldata.sol index a5f61d2ddd67..a9d2f640e3fd 100644 --- a/test/libsolidity/semanticTests/various/create_calldata.sol +++ b/test/libsolidity/semanticTests/various/create_calldata.sol @@ -6,6 +6,8 @@ contract C { assert(msg.data.length == 0); } } +// ==== +// compileToEOF: false // ---- // constructor(): 42 -> // gas irOptimized: 68239 diff --git a/test/libsolidity/semanticTests/various/create_random.sol b/test/libsolidity/semanticTests/various/create_random.sol index 676ec32a89ae..601cea718fc4 100644 --- a/test/libsolidity/semanticTests/various/create_random.sol +++ b/test/libsolidity/semanticTests/various/create_random.sol @@ -5,8 +5,8 @@ contract C { function testRunner() external returns (address a1, address a2) { assembly { - // This is `return(0, 1)`. We are using a simplified/fixed initcode to avoid - // instability due to metadata changes. + // This is `return(0, 1)`. We are using a simplified/fixed initcode to avoid + // instability due to metadata changes. let initcode := hex"60016000f3" mstore(0, initcode) @@ -23,7 +23,7 @@ contract C { function calculateCreate(address from, uint256 nonce) private pure returns (address) { assert(nonce <= 127); bytes memory data = - bytes.concat(hex"d694", bytes20(uint160(from)), nonce == 0 ? bytes1(hex"80") : bytes1(uint8(nonce))); + bytes.concat(hex"d694", bytes20(uint160(from)), nonce == 0 ? bytes1(hex"80") : bytes1(uint8(nonce))); return address(uint160(uint256(keccak256(data)))); // Take the lower 160-bits } @@ -33,6 +33,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // addr() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e // testRunner() -> 0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x2c1c30623ddd93e0b765a6caaca0c859eeb0644d diff --git a/test/libsolidity/semanticTests/various/gasleft_decrease.sol b/test/libsolidity/semanticTests/various/gasleft_decrease.sol index ab7302743f64..bffca20b3dd4 100644 --- a/test/libsolidity/semanticTests/various/gasleft_decrease.sol +++ b/test/libsolidity/semanticTests/various/gasleft_decrease.sol @@ -14,6 +14,8 @@ contract C { return true; } } +// ==== +// compileToEOF: false // ---- // f() -> true // g() -> true diff --git a/test/libsolidity/semanticTests/various/many_subassemblies.sol b/test/libsolidity/semanticTests/various/many_subassemblies.sol index b270c7006694..a4e9d96bdd9e 100644 --- a/test/libsolidity/semanticTests/various/many_subassemblies.sol +++ b/test/libsolidity/semanticTests/various/many_subassemblies.sol @@ -28,6 +28,8 @@ contract D { new C10(); } } +// ==== +// compileToEOF: false // ---- // run() -> // gas irOptimized: 374934 diff --git a/test/libsolidity/semanticTests/various/many_subassemblies_eof.sol b/test/libsolidity/semanticTests/various/many_subassemblies_eof.sol new file mode 100644 index 000000000000..95c797d0230f --- /dev/null +++ b/test/libsolidity/semanticTests/various/many_subassemblies_eof.sol @@ -0,0 +1,41 @@ +contract C0 {} +contract C1 {} +contract C2 {} +contract C3 {} +contract C4 {} +contract C5 {} +contract C6 {} +contract C7 {} +contract C8 {} +contract C9 {} +contract C10 {} + +contract D { + function run() public { + // This is primarily meant to test assembly import via --import-asm-json. + // The exported JSON will fail the reimport unless the subassembly indices are parsed + // correctly - as hex numbers. + new C0{salt: hex"00"}(); + new C1{salt: hex"01"}(); + new C2{salt: hex"02"}(); + new C3{salt: hex"03"}(); + new C4{salt: hex"04"}(); + new C5{salt: hex"05"}(); + new C6{salt: hex"06"}(); + new C7{salt: hex"07"}(); + new C8{salt: hex"08"}(); + new C9{salt: hex"09"}(); + new C10{salt: hex"0a"}(); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// run() -> +// gas irOptimized: 374934 +// gas irOptimized code: 6600 +// gas legacy: 375119 +// gas legacy code: 17600 +// gas legacyOptimized: 375119 +// gas legacyOptimized code: 17600 diff --git a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol index 4cb5c8c713bf..da413b0becd3 100644 --- a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol +++ b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol @@ -62,6 +62,7 @@ contract D { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 67028 diff --git a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol index a582649196c3..407cf54150f9 100644 --- a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol +++ b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol @@ -33,6 +33,7 @@ contract D { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor(), 2 ether -> // gas irOptimized: 108104 diff --git a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol index dd550d31a0cf..1e5483deb814 100644 --- a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol +++ b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol @@ -80,6 +80,7 @@ contract D { // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 132974 diff --git a/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol b/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol index 843376baa553..03793fcd23d1 100644 --- a/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol +++ b/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol @@ -15,7 +15,10 @@ contract C { b = this.f; } } +// ==== +// compileToEOF: false // ---- // f(uint256): 2 -> 4 // h(uint256): 2 -> 5 // t() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79eb3de648b0000000000000000, 0xc06afe3a8444fc0004668591e8306bfb9968e79eb3de648b0000000000000000 + diff --git a/test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol b/test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol new file mode 100644 index 000000000000..391334123782 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol @@ -0,0 +1,25 @@ +contract C { + function f(uint x) public pure returns (uint) { + return 2 * x; + } + function g() public view returns (function (uint) external returns (uint)) { + return this.f; + } + function h(uint x) public returns (uint) { + return this.g()(x) + 1; + } + function t() external view returns ( + function(uint) external returns (uint) a, + function(uint) external view returns (uint) b) { + a = this.f; + b = this.f; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f(uint256): 2 -> 4 +// h(uint256): 2 -> 5 +// t() -> 0x86e73fe93c23a38c4bdba9e4b9cc0ddcc0fe293eb3de648b0000000000000000, 0x86e73fe93c23a38c4bdba9e4b9cc0ddcc0fe293eb3de648b0000000000000000 + diff --git a/test/libsolidity/semanticTests/viaYul/function_address.sol b/test/libsolidity/semanticTests/viaYul/function_address.sol index 0c1c58dd821d..cffb34dd9136 100644 --- a/test/libsolidity/semanticTests/viaYul/function_address.sol +++ b/test/libsolidity/semanticTests/viaYul/function_address.sol @@ -9,6 +9,8 @@ contract C { return a.address; } } +// ==== +// compileToEOF: false // ---- // f() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e // g() -> true diff --git a/test/libsolidity/semanticTests/viaYul/function_address_eof.sol b/test/libsolidity/semanticTests/viaYul/function_address_eof.sol new file mode 100644 index 000000000000..c3ea05b7febc --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/function_address_eof.sol @@ -0,0 +1,18 @@ +contract C { + function f() external returns (address) { + return this.f.address; + } + function g() external returns (bool) { + return this.f.address == address(this); + } + function h(function() external a) public returns (address) { + return a.address; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0x1a7b7ed5ae36cd8c4f6da702d8409d6cf9bd1f6d +// g() -> true +// h(function): left(0x1122334400112233445566778899AABBCCDDEEFF42424242) -> 0x1122334400112233445566778899AABBCCDDEEFF