diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 34a072bb2..20e1c5f43 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61756 \ No newline at end of file +61332 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index e30ad0bd7..770caaf99 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -133519 \ No newline at end of file +132997 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index 7d2a0de80..3541eaf39 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -135555 \ No newline at end of file +134973 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index 86aaa87ae..e05cffe7d 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -128029 \ No newline at end of file +127102 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index 3cee40984..9166a87da 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -128167 \ No newline at end of file +127212 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 4a8a8af95..88081af81 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47176 \ No newline at end of file +51576 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 5f5388bff..88081af81 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46993 \ No newline at end of file +51576 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 0deea6f3a..b8ad8f5e7 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -122988 \ No newline at end of file +128316 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index f0ffb8d7f..5f7fe3b44 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122698 \ No newline at end of file +127696 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 03487c9ad..f821b49cb 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130067 \ No newline at end of file +135236 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 6f25fa6cb..46630b5d9 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129776 \ No newline at end of file +134615 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 4e0b3e678..86f965291 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141379 \ No newline at end of file +148571 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 0ac9adb43..e2b8005b9 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150227 \ No newline at end of file +157220 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 0ac9adb43..e2b8005b9 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150227 \ No newline at end of file +157220 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index dc5b485f8..ccb34392b 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149852 \ No newline at end of file +156456 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 19a44cb47..c2b5120a1 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108578 \ No newline at end of file +114165 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index 54ee23172..bdea68d3c 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115770 \ No newline at end of file +122555 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 241fc1a58..8c20e971e 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115395 \ No newline at end of file +121791 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 39f7f6418..dbd7a9ac1 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134146 \ No newline at end of file +138177 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index d76b23660..4622f7b48 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126885 \ No newline at end of file +131258 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index d09340678..105b30011 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128486 \ No newline at end of file +135218 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index b7a40909c..a34b498cd 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116625 \ No newline at end of file +123169 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 56522a67e..0c144b4d2 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152333 \ No newline at end of file +162419 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 3a72622d1..5b88c6213 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151588 \ No newline at end of file +161283 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index ed79ff90b..dd90e5a55 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134133 \ No newline at end of file +145296 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 89b7d1b0f..6312e8ddf 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130372 \ No newline at end of file +138010 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index d08ced170..cf908d5cf 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -170992 \ No newline at end of file +180194 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 06871ca37..c9779d027 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140972 \ No newline at end of file +150782 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 26a0e4338..ad9b48fff 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336811 \ No newline at end of file +369623 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 2225fef68..6d4460fb9 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345280 \ No newline at end of file +378280 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 4658e17db..351997f8d 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344835 \ No newline at end of file +377364 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index fa5c26f06..dcb8d5ae5 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314793 \ No newline at end of file +321205 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 856ad2de7..3a7c7c34a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315435 \ No newline at end of file +321875 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index dbdfcf612..7d8a26862 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241017 \ No newline at end of file +247444 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 2262310e9..4d62f1da1 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370973 \ No newline at end of file +423280 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 4428bae13..fcd47aaf3 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320811 \ No newline at end of file +327236 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 430eef12d..59610f85c 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372111 \ No newline at end of file +423986 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index c3044f8c8..b3a6480c2 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371504 \ No newline at end of file +422936 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 93b17f36d..19380f4f4 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416486 \ No newline at end of file +460558 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index ee34c59c9..53dd01e77 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79445 \ No newline at end of file +79259 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index 0b63319b8..ac17c65c1 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62333 \ No newline at end of file +62159 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index 7772c13d9..532d2de91 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45221 \ No newline at end of file +45035 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_subscribe.snap b/.forge-snapshots/PositionManager_subscribe.snap new file mode 100644 index 000000000..f6081d8d7 --- /dev/null +++ b/.forge-snapshots/PositionManager_subscribe.snap @@ -0,0 +1 @@ +88475 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_unsubscribe.snap b/.forge-snapshots/PositionManager_unsubscribe.snap new file mode 100644 index 000000000..3651ba8a5 --- /dev/null +++ b/.forge-snapshots/PositionManager_unsubscribe.snap @@ -0,0 +1 @@ +63253 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap new file mode 100644 index 000000000..cb86556e8 --- /dev/null +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -0,0 +1 @@ +146317 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap new file mode 100644 index 000000000..8cf53dc80 --- /dev/null +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -0,0 +1 @@ +151973 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap new file mode 100644 index 000000000..2f2aa6ed8 --- /dev/null +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -0,0 +1 @@ +80048 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap new file mode 100644 index 000000000..9606490a7 --- /dev/null +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -0,0 +1 @@ +84626 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap new file mode 100644 index 000000000..9aebeeca5 --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -0,0 +1 @@ +122994 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap new file mode 100644 index 000000000..1a3ae7138 --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -0,0 +1 @@ +147949 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap new file mode 100644 index 000000000..1ce389ff1 --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -0,0 +1 @@ +81420 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap new file mode 100644 index 000000000..7fe1af46a --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -0,0 +1 @@ +205421 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap new file mode 100644 index 000000000..6233611de --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -0,0 +1 @@ +122296 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap new file mode 100644 index 000000000..ee524ce0d --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -0,0 +1 @@ +152648 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap new file mode 100644 index 000000000..f965907ac --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -0,0 +1 @@ +122364 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap new file mode 100644 index 000000000..f813bc4bc --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -0,0 +1 @@ +98875 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap new file mode 100644 index 000000000..584ea1427 --- /dev/null +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -0,0 +1 @@ +204897 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap b/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap index 0cd7e117a..a9e72c33a 100644 --- a/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap +++ b/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap @@ -1 +1 @@ -2398 \ No newline at end of file +2367 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap b/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap index d10ca6688..b1b49287a 100644 --- a/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap +++ b/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap @@ -1 +1 @@ -8543 \ No newline at end of file +8444 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getLiquidity.snap b/.forge-snapshots/StateView_extsload_getLiquidity.snap index 5303ac12b..ab57eb1e7 100644 --- a/.forge-snapshots/StateView_extsload_getLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getLiquidity.snap @@ -1 +1 @@ -1509 \ No newline at end of file +1480 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getPositionInfo.snap b/.forge-snapshots/StateView_extsload_getPositionInfo.snap index 9d57e9ada..0a1ca398f 100644 --- a/.forge-snapshots/StateView_extsload_getPositionInfo.snap +++ b/.forge-snapshots/StateView_extsload_getPositionInfo.snap @@ -1 +1 @@ -2927 \ No newline at end of file +2973 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap b/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap index 280f1a098..8d6430831 100644 --- a/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap @@ -1 +1 @@ -1746 \ No newline at end of file +1750 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getSlot0.snap b/.forge-snapshots/StateView_extsload_getSlot0.snap index 38ca8416e..466fe4026 100644 --- a/.forge-snapshots/StateView_extsload_getSlot0.snap +++ b/.forge-snapshots/StateView_extsload_getSlot0.snap @@ -1 +1 @@ -1606 \ No newline at end of file +1548 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickBitmap.snap b/.forge-snapshots/StateView_extsload_getTickBitmap.snap index dfd4d9fcd..0e8ebf59f 100644 --- a/.forge-snapshots/StateView_extsload_getTickBitmap.snap +++ b/.forge-snapshots/StateView_extsload_getTickBitmap.snap @@ -1 +1 @@ -1704 \ No newline at end of file +1476 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap b/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap index f26febc18..7c43ea017 100644 --- a/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap +++ b/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap @@ -1 +1 @@ -2756 \ No newline at end of file +2672 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickInfo.snap b/.forge-snapshots/StateView_extsload_getTickInfo.snap index 90a812894..3582118cc 100644 --- a/.forge-snapshots/StateView_extsload_getTickInfo.snap +++ b/.forge-snapshots/StateView_extsload_getTickInfo.snap @@ -1 +1 @@ -3090 \ No newline at end of file +2896 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickLiquidity.snap b/.forge-snapshots/StateView_extsload_getTickLiquidity.snap index ff3534610..0504b068d 100644 --- a/.forge-snapshots/StateView_extsload_getTickLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getTickLiquidity.snap @@ -1 +1 @@ -1901 \ No newline at end of file +1748 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index cef380791..ec718f71f 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8438 \ No newline at end of file +5137 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index 32d9c91a2..56a7e65e8 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -119534 \ No newline at end of file +121819 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index 516f2efeb..cbfeb1a9c 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -118729 \ No newline at end of file +120826 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index b98ed60b4..6bf2eef7d 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -127601 \ No newline at end of file +129715 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 1b8b982f1..891422a70 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -134431 \ No newline at end of file +135623 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index d96af129a..a7bc469ae 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -185972 \ No newline at end of file +191979 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index 66eb073fb..ef0bcd5fe 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -177907 \ No newline at end of file +178175 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 17723637e..89acd064f 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -237494 \ No newline at end of file +248385 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index 53ae189d3..a93abe955 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -229453 \ No newline at end of file +234581 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index e30ad0bd7..24fe6dec9 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -133519 \ No newline at end of file +134546 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 954557e3f..7f06d7f09 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -118622 \ No newline at end of file +120742 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index faf9096ad..4e26e8532 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -117800 \ No newline at end of file +119714 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index 44f276e89..1fdaa9cd4 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -125192 \ No newline at end of file +128078 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index fba2e6e31..5680b0271 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -119470 \ No newline at end of file +121895 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index 3e0079ae8..85542b79f 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -128342 \ No newline at end of file +130784 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index b144de141..a883e8809 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -133143 \ No newline at end of file +134905 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index ce8f44670..eb9099cb8 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -185287 \ No newline at end of file +190319 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index 4539caa73..c2de3e9b9 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -182137 \ No newline at end of file +183492 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index de954cf9b..ddf200aa9 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -237427 \ No newline at end of file +245811 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index 0012a0905..583269aea 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -234301 \ No newline at end of file +238984 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 2028f2194..0ff3d5c82 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -228579 \ No newline at end of file +224567 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index 107eb18c3..d44e2734a 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -131940 \ No newline at end of file +133809 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index 2d74c0647..fc1ac5689 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -123989 \ No newline at end of file +126982 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index e3baa57f7..dacad49f6 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -118280 \ No newline at end of file +120876 \ No newline at end of file diff --git a/.forge-snapshots/positionDescriptor bytecode size.snap b/.forge-snapshots/positionDescriptor bytecode size.snap new file mode 100644 index 000000000..60d759c40 --- /dev/null +++ b/.forge-snapshots/positionDescriptor bytecode size.snap @@ -0,0 +1 @@ +24110 \ No newline at end of file diff --git a/.forge-snapshots/positionManager bytecode size.snap b/.forge-snapshots/positionManager bytecode size.snap new file mode 100644 index 000000000..404029b94 --- /dev/null +++ b/.forge-snapshots/positionManager bytecode size.snap @@ -0,0 +1 @@ +19060 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb5820e5f..73de4cdbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv + run: forge test --isolate -vvv env: FOUNDRY_PROFILE: ci + FORGE_SNAPSHOT_CHECK: true diff --git a/.gitignore b/.gitignore index 785fb3933..f4ebe6d33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ cache/ foundry-out/ -.vscode/ \ No newline at end of file +.vscode/ +broadcast/*/*/dry-run/*.json +broadcast/*/*/run-[0-9]*.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 9d6618d5b..fb9fdc7d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 +[submodule "lib/forge-gas-snapshot"] + path = lib/forge-gas-snapshot + url = https://github.com/marktoda/forge-gas-snapshot diff --git a/LICENSE b/LICENSE index ecbc05937..45956f482 100644 --- a/LICENSE +++ b/LICENSE @@ -1,339 +1,7 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 +Copyright 2023 Universal Navigation Inc. - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - Preamble +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. \ No newline at end of file +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/audits/DRAFT_ABDK_audit_periphery_universal_router.pdf b/audits/DRAFT_ABDK_audit_periphery_universal_router.pdf new file mode 100644 index 000000000..d6fbb06a2 Binary files /dev/null and b/audits/DRAFT_ABDK_audit_periphery_universal_router.pdf differ diff --git a/audits/DRAFT_Spearbit_audit_periphery.pdf b/audits/DRAFT_Spearbit_audit_periphery.pdf new file mode 100644 index 000000000..be2bf236c Binary files /dev/null and b/audits/DRAFT_Spearbit_audit_periphery.pdf differ diff --git a/audits/OpenZeppelin_audit_periphery_universal_router.pdf b/audits/OpenZeppelin_audit_periphery_universal_router.pdf new file mode 100644 index 000000000..794db691c Binary files /dev/null and b/audits/OpenZeppelin_audit_periphery_universal_router.pdf differ diff --git a/broadcast/01_PoolManager.s.sol/11155111/run-latest.json b/broadcast/01_PoolManager.s.sol/11155111/run-latest.json new file mode 100644 index 000000000..732bc8a75 --- /dev/null +++ b/broadcast/01_PoolManager.s.sol/11155111/run-latest.json @@ -0,0 +1,69 @@ +{ + "transactions": [ + { + "hash": "0xae8b5df5aeaac2faf35a24f27312c4defd3eefbca9243f504e68621e5fae551c", + "transactionType": "CREATE", + "contractName": "PoolManager", + "contractAddress": "0xf242ce588b030d0895c51c0730f2368680f80644", + "function": null, + "arguments": [ + "200000" + ], + "transaction": { + "from": "0xb7a249bdeff39727b5eb4c7ad458f682bae6adad", + "gas": "0x69aa97", + "value": "0x0", + "input": "0x60c034609d57601f615fff38819003918201601f19168301916001600160401b0383118484101760a157808492602094604052833981010312609d57515f80546001600160a01b03191633908117825560405192917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a36080523060a052615f4990816100b6823960805181611317015260a051816135f40152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60a0806040526004361015610012575f80fd5b5f3560e01c908162fdd58e14612d7f5750806301ffc9a714612cc0578063095bcdb614612c165780630b0d9c0914612b8a57806311da60b414612b2f578063156e29f614612a7f5780631e2eaeaf14612a45578063234266d7146127ce5780632d7713891461270c57806335fd631a146126985780633dd45adb14612634578063426a8493146125b057806348c8949114612325578063527596511461220d578063558a729714612136578063598af9e71461209e5780635a6bcfda146115e2578063695c5bf514610f3f5780637e87ce7d14610e0257806380f0b44c14610ce15780638161b87414610be95780638da5cb5b14610b9957806397e8cd4e14610b365780639bf6645f14610ae9578063a584119414610aa8578063b6363cf214610a17578063dbd035ff146109c1578063f02de3b214610970578063f135baaa14610936578063f2fde38b1461088a578063f3cd914c146104ff578063f5298aca146103345763fe99049a14610186575f80fd5b346103305760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576101bd612de9565b6101c5612e0c565b90604435917f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885961027973ffffffffffffffffffffffffffffffffffffffff80606435951693843314158061030d575b610287575b845f52600460205260405f20875f5260205260405f2061023a878254613097565b90551693845f52600460205260405f20865f5260205260405f2061025f8282546130a4565b905560408051338152602081019290925290918291820190565b0390a4602060405160018152f35b845f52600560205260405f208233165f5260205260405f20875f5260205260405f2054867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036102da575b5050610219565b6102e391613097565b855f52600560205260405f208333165f5260205260405f20885f5260205260405f20555f866102d3565b50845f52600360205260405f208233165f5260205260ff60405f20541615610214565b5f80fd5b346103305761034236612e2f565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7577f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288596103ed73ffffffffffffffffffffffffffffffffffffffff805f9516956103bb6103b38661322a565b339089613270565b169233841415806104a0575b6103f2575b8385526004602052604085208686526020526040852061025f828254613097565b0390a4005b83855260056020526040852073ffffffffffffffffffffffffffffffffffffffff33168652602052604085208686526020526040852054817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610459575b50506103cc565b61046291613097565b84865260056020526040862073ffffffffffffffffffffffffffffffffffffffff331687526020526040862087875260205260408620558681610452565b5083855260036020526040852073ffffffffffffffffffffffffffffffffffffffff3316865260205260ff604086205416156103c7565b7f54e3ca0d000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610330576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761053836612f2b565b60607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5c36011261033057604051906060820182811067ffffffffffffffff82111761085d5760405260a4358015158103610330578252602082019060c435825260e4359073ffffffffffffffffffffffffffffffffffffffff8216820361033057604084019182526101043567ffffffffffffffff8111610330576105e1903690600401612ff7565b9290937fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576106136135dd565b51156108355760a0822092835f52600660205260405f209061063482613644565b60808401958482828a8a5173ffffffffffffffffffffffffffffffffffffffff169361065f94613c01565b90949195606088015160020b968b511515915173ffffffffffffffffffffffffffffffffffffffff1691604051986106968a612ebc565b895260208901526040880152606087015262ffffff166080860152885115155f149862ffffff6107b7986106ec6107a49860209d6108155773ffffffffffffffffffffffffffffffffffffffff8b511695614d47565b9492968291926107e8575b505073ffffffffffffffffffffffffffffffffffffffff6040850151169360606fffffffffffffffffffffffffffffffff60a08301511691015160020b90604051958860801d600f0b875288600f0b60208801526040870152606086015260808501521660a08301527f40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112f60c03393a38673ffffffffffffffffffffffffffffffffffffffff8a5116613e3e565b809491946107bf575b5050823391613720565b604051908152f35b73ffffffffffffffffffffffffffffffffffffffff6107e19251169083613720565b84806107ad565b73ffffffffffffffffffffffffffffffffffffffff165f5260018f5260405f209081540190558e806106f7565b73ffffffffffffffffffffffffffffffffffffffff8e8c01511695614d47565b7fbe8b8507000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330577fffffffffffffffffffffffff00000000000000000000000000000000000000006108e2612de9565b73ffffffffffffffffffffffffffffffffffffffff5f549161090782841633146130b1565b1691829116175f55337f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3005b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576004355c5f5260205ff35b34610330575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b34610330576109cf36613025565b6040519160408360208152836020820152019160051b8301916020806040850193925b8335548152019101908483821015610a0e5750602080916109f2565b60408186030190f35b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610a4e612de9565b73ffffffffffffffffffffffffffffffffffffffff610a6b612e0c565b91165f52600360205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060ff60405f2054166040519015158152f35b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610ae7610ae2612de9565b613154565b005b3461033057610af736613025565b6040519160408360208152836020820152019160051b8301916020806040850193925b83355c8152019101908483821015610a0e575060208091610b1a565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305773ffffffffffffffffffffffffffffffffffffffff610b82612de9565b165f526001602052602060405f2054604051908152f35b34610330575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057602073ffffffffffffffffffffffffffffffffffffffff5f5416604051908152f35b346103305760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610c20612de9565b610c28612e0c565b9060443573ffffffffffffffffffffffffffffffffffffffff600254163303610cb9576020926107b79180610cb1575073ffffffffffffffffffffffffffffffffffffffff81165f526001845260405f20549283915b73ffffffffffffffffffffffffffffffffffffffff81165f526001865260405f20610caa848254613097565b9055613378565b928391610c7e565b7f48f5c3ed000000000000000000000000000000000000000000000000000000005f5260045ffd5b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610d18612de9565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d757335f90815273ffffffffffffffffffffffffffffffffffffffff8216602052604090205c610d6e60243561322a565b600f0b908103610dda577fffffffffffffffffffffffffffffffff800000000000000000000000000000008114610dad57610ae79133915f0390613270565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fbda73abf000000000000000000000000000000000000000000000000000000005f5260045ffd5b346103305760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610e3a36612f2b565b610e42612f19565b9073ffffffffffffffffffffffffffffffffffffffff600254163303610cb9576103e9610fff831610623e900062fff0008416101615610f0e57602060a07fe9c42593e71f84403b84352cd168d693e2c9fcd1fdbcc3feb21d92b43e6696f9922092835f526006825260405f20610eb881613644565b805479ffffff00000000000000000000000000000000000000000000008360b81b16907fffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffff1617905562ffffff60405191168152a2005b62ffffff827fa7abe2f7000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b346103305760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610f7736612f2b565b60a4359073ffffffffffffffffffffffffffffffffffffffff82168083036103305760c43567ffffffffffffffff811161033057610fb9903690600401612ff7565b90610fc26135dd565b6060840193845160020b617fff81136115b75750845160020b6001811261158c575073ffffffffffffffffffffffffffffffffffffffff81511692602082019373ffffffffffffffffffffffffffffffffffffffff855116908181101561155e5750506080820173ffffffffffffffffffffffffffffffffffffffff81511695604084019661105762ffffff89511682613a74565b15611533575061106c62ffffff885116613b32565b91805173ffffffffffffffffffffffffffffffffffffffff811690813303611470575b505060a08520955f73ffffffffffffffffffffffffffffffffffffffff6002541680611312575b50875f52600660205260405f2090815473ffffffffffffffffffffffffffffffffffffffff166112ea5773ffffffffffffffffffffffffffffffffffffffff7fdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d64389860a09860209f98839662ffffff96859661114e7cffffff00000000000000000000000000000000000000000000000000009d614989565b9c8d9460d01b169079ffffff00000000000000000000000000000000000000000000008f76ffffff00000000000000000000000000000000000000008f9188901b16179160b81b16171790558851908a868316928333036111e8575b5050505050505116995116995116995160020b91511690604051998a528a8a01526040890152606088015260020b95866080820152a4604051908152f35b611000166111f7575b816111aa565b6112ae6112da926112df96604051966020947fa910f80f00000000000000000000000000000000000000000000000000000000899687015233602487015261128c604487018c73ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b60e486015260020b610104850152610120610124850152610144840191613116565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101845283612ed8565b613fe2565b508f8881808a6111f1565b7f7983c051000000000000000000000000000000000000000000000000000000005f5260045ffd5b90505a7f0000000000000000000000000000000000000000000000000000000000000000809110611448575f8091604051938260208601917f553bfc370000000000000000000000000000000000000000000000000000000083526113c58d602489019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b60a487526113d460c488612ed8565b81965193f160203d141680611435575b80611427575b8061140c575b156114045762ffffff600191165b906110b6565b505f806113fe565b506103e9610fff821610623e900062fff000831610166113f0565b5062ffffff811681146113ea565b60405191503d5f833e5f825192526113e4565b7f1ee49702000000000000000000000000000000000000000000000000000000005f5260045ffd5b6120001661147f575b8061108f565b61152c90604051907f3440d82000000000000000000000000000000000000000000000000000000000602083015233602483015261150a604483018973ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8460e48301526101006101048301526112da826112ae61012482018a8d613116565b508a611479565b7fe65af6a0000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f6e6c9830000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7fe9e90588000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fb70024f8000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b34610330576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761161b36612f2b565b60807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5c360112610330576040519061165282612e84565b60a4358060020b810361033057825260c4358060020b810361033057602083015260e43560408301526101043560608301526101243567ffffffffffffffff8111610330576116a5903690600401612ff7565b92907fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576116d66135dd565b60a0832093845f52600660205260405f20906116f182613644565b608085015173ffffffffffffffffffffffffffffffffffffffff811690813303611fc5575b5050835160020b92602085015160020b966117346040870151613853565b95606088015160020b9860608201516040519a6117508c612ea0565b338c528860208d01528260408d015289600f0b60608d015260808c015260a08b01525f9080881215611f8e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276188812611f6257620d89e88113611f3757604051986117ba8a612e84565b5f8a525f60208b01525f60408b01525f60608b015280600f0b611c87575b60048801988060020b5f528960205260405f20998360020b5f5260205260405f20895460a01c60020b8281125f14611c35575060028060018d0154600184015490039c015491015490039c5b60a073ffffffffffffffffffffffffffffffffffffffff8251169101519060405191602683015284600683015282600383015281525f603a600c83012091816040820152816020820152525f526006890160205260405f20986fffffffffffffffffffffffffffffffff8a54169a83600f0b155f14611bd8578b15611bb0576118e760409f6119c59c6118e1916119d79f5b60018301956118d960026118cd848a5485036141c1565b950192835485036141c1565b96555561322a565b9161322a565b6fffffffffffffffffffffffffffffffff169060801b179b5f84600f0b12611b46575b5082600f0b611a0f575b5050505061193b61192c8960801d8360801d01613853565b9189600f0b90600f0b01613853565b6fffffffffffffffffffffffffffffffff169060801b1791815160020b90602083015160020b8b8401516060850151918d5194855260208501528c84015260608301527ff208f4912782fd25c7f114ca3723a2d5dd6f3bcc3ac8db5af63baa85f711d5ec60803393a38773ffffffffffffffffffffffffffffffffffffffff608082015116613922565b809491946119e3575b50833391613720565b82519182526020820152f35b611a099073ffffffffffffffffffffffffffffffffffffffff6080840151169083613720565b856119ce565b809192939450548060a01c60020b9073ffffffffffffffffffffffffffffffffffffffff16908381125f14611a775750505091611a6591611a5f611a55611a6a956144e5565b91600f0b926144e5565b906148b4565b613853565b60801b5b8a808080611914565b948091939295125f14611b12576fffffffffffffffffffffffffffffffff92611acf611a65611ae594611abc611a65600396611ab68b600f0b916144e5565b876148b4565b93611aca89600f0b926144e5565b614857565b84169060801b17940192600f0b828454166148fb565b167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055611a6e565b9150611b2f935091611b29611a55611a65946144e5565b90614857565b6fffffffffffffffffffffffffffffffff16611a6e565b808f9151611b86575b0151611b5c575b8e61190a565b611b81848260049160020b5f52016020525f6002604082208281558260018201550155565b611b56565b611bab848460049160020b5f52016020525f6002604082208281558260018201550155565b611b4f565b7faefeb924000000000000000000000000000000000000000000000000000000005f5260045ffd5b6118e760409f6119c59c6118e1916119d79f6fffffffffffffffffffffffffffffffff611c088a600f0b836148fb565b167fffffffffffffffffffffffffffffffff000000000000000000000000000000008454161783556118b6565b909a908413611c5b5760028060018d0154600184015490039c015491015490039c611824565b99600260018b015460018d01549003600183015490039b81808d0154910154900391015490039c611824565b8860020b5f5260048801602052886fffffffffffffffffffffffffffffffff60405f20805490611ce1838316928d611cc288600f0b866148fb565b9415968787871615141597611f08575b505086600f0b9060801d613df7565b60801b8383161790551660208c01528a528160020b5f526004880160205260405f208054906fffffffffffffffffffffffffffffffff8216611d2684600f0b826148fb565b901592836fffffffffffffffffffffffffffffffff831615141593611ee1575b84600f0b9060801d600f0b03916f7fffffffffffffffffffffffffffffff83137fffffffffffffffffffffffffffffffff80000000000000000000000000000000841217610dad57826fffffffffffffffffffffffffffffffff935060801b8383161790551660608c015260408b0152898c5f83600f0b1215611e09575b5051611def575b60408a0151156117d857611dea60808d015160020b8360058b01614499565b6117d8565b611e0460808d015160020b8a60058b01614499565b611dcb565b60016fffffffffffffffffffffffffffffffff602060808294015160020b940151169280807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2761805028180620d89e80502030501810416809111611eb5576fffffffffffffffffffffffffffffffff60608c01511611611e8957898c611dc4565b507fb8e3c385000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b897fb8e3c385000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b8b5460a01c60020b8613611d465760018c0154600184015560028c01546002840155611d46565b815460a01c60020b1215611f1d575b8f611cd2565b80600160029201546001860155015460028401558d611f17565b7f1ad777f8000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b877fd5e2f7ab000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b87604491604051917fc4433ed500000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b5f604087015113808091612091575b156120265750506040517f259982e500000000000000000000000000000000000000000000000000000000602082015261201d916112da826112ae86898b8d3360248701613799565b505b8680611716565b159081612083575b5061203a575b5061201f565b6040517f21d0ee7000000000000000000000000000000000000000000000000000000000602082015261207c916112da826112ae86898b8d3360248701613799565b5086612034565b61020091501615158861202e565b5061080082161515611fd4565b346103305760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576120d5612de9565b73ffffffffffffffffffffffffffffffffffffffff6120f2612e0c565b91165f52600560205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f5260205260405f206044355f52602052602060405f2054604051908152f35b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761216d612de9565b602435908115158092036103305773ffffffffffffffffffffffffffffffffffffffff90335f52600360205260405f208282165f5260205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff851617905560405192835216907fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa26760203392a3602060405160018152f35b346103305760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761224536612f2b565b61224d612f19565b906280000062ffffff60408301511614801590612301575b6122d95760a0906122758361375c565b205f52600660205260405f209061228b82613644565b81547fffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffff1660d09190911b7cffffff000000000000000000000000000000000000000000000000000016179055005b7f30d21641000000000000000000000000000000000000000000000000000000005f5260045ffd5b5073ffffffffffffffffffffffffffffffffffffffff608082015116331415612265565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305760043567ffffffffffffffff811161033057612374903690600401612ff7565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c61258857612400915f9160017fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235d60405193849283927f91dd7346000000000000000000000000000000000000000000000000000000008452602060048501526024840191613116565b038183335af190811561257d575f916124d5575b507f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5c6124ad5760406020915f7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f835194859381855280519182918282880152018686015e5f85828601015201168101030190f35b7f5212cba1000000000000000000000000000000000000000000000000000000005f5260045ffd5b90503d805f833e6124e68183612ed8565b8101906020818303126103305780519067ffffffffffffffff8211610330570181601f820112156103305780519067ffffffffffffffff821161085d576040519261255960207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160185612ed8565b8284526020838301011161033057815f9260208093018386015e8301015281612414565b6040513d5f823e3d90fd5b7f5090d6c6000000000000000000000000000000000000000000000000000000005f5260045ffd5b346103305773ffffffffffffffffffffffffffffffffffffffff6125d336612e2f565b91929092335f52600560205260405f208282165f5260205260405f20845f526020528260405f205560405192835216907fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a760203392a4602060405160018152f35b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057612666612de9565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576107b76020916134fb565b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576024356004356040519160408360208152826020820152019060051b8301916001602060408501935b83548152019101908483821015610a0e575060206001916126f0565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305760043573ffffffffffffffffffffffffffffffffffffffff81168091036103305761277e73ffffffffffffffffffffffffffffffffffffffff5f541633146130b1565b807fffffffffffffffffffffffff000000000000000000000000000000000000000060025416176002557fb4bd8ef53df690b9943d3318996006dbb82a25f54719d8c8035b516a2a5b8acc5f80a2005b34610330576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761280736612f2b565b60c4359060a43560e43567ffffffffffffffff81116103305761282e903690600401612ff7565b9190937fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576128606135dd565b60a084205f52600660205260405f209461287986613644565b6080850194855173ffffffffffffffffffffffffffffffffffffffff8116908133036129ed575b50506fffffffffffffffffffffffffffffffff6003880154169586156129c5576020976128cc8661322a565b5f036128d78661322a565b5f036fffffffffffffffffffffffffffffffff169060801b1797866129ac575b85612991575b505061290a338884613720565b519273ffffffffffffffffffffffffffffffffffffffff841693843303612936575b8888604051908152f35b601016612944575b8061292c565b612985956112da936112ae926040519788957fe1b4af69000000000000000000000000000000000000000000000000000000008d880152336024880161368a565b5082808080808061293e565b61299d60029187614100565b910190815401905588806128fd565b6129b68188614100565b600183019081540190556128f7565b7fa74f97ab000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020166129fb575b806128a0565b6040517fb6a8b0fa000000000000000000000000000000000000000000000000000000006020820152612a3e916112da826112ae8a888a8c8a336024880161368a565b50876129f5565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057600435545f5260205ff35b3461033057612a8d36612e2f565b907fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7577f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288596103ed73ffffffffffffffffffffffffffffffffffffffff805f941695612b0c612aff8761322a565b8603600f0b339089613270565b16938484526004602052604084208685526020526040842061025f8282546130a4565b5f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330577fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d75760206107b7336134fb565b346103305760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057612bc1612de9565b612bc9612e0c565b604435907fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d757610ae792612c11612c048461322a565b5f03600f0b339083613270565b613378565b346103305773ffffffffffffffffffffffffffffffffffffffff612c3936612e2f565b91929092335f52600460205260405f20845f5260205260405f20612c5e848254613097565b90551690815f52600460205260405f20835f5260205260405f20612c838282546130a4565b9055604080513380825260208201939093527f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288599181908101610279565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576004357fffffffff00000000000000000000000000000000000000000000000000000000811680910361033057807f01ffc9a70000000000000000000000000000000000000000000000000000000060209214908115612d55575b506040519015158152f35b7f0f632fb30000000000000000000000000000000000000000000000000000000091501482612d4a565b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305760209073ffffffffffffffffffffffffffffffffffffffff612dce612de9565b165f526004825260405f206024355f52825260405f20548152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361033057565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361033057565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126103305760043573ffffffffffffffffffffffffffffffffffffffff8116810361033057906024359060443590565b6080810190811067ffffffffffffffff82111761085d57604052565b60c0810190811067ffffffffffffffff82111761085d57604052565b60a0810190811067ffffffffffffffff82111761085d57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761085d57604052565b60a4359062ffffff8216820361033057565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60a09101126103305760405190612f6282612ebc565b8160043573ffffffffffffffffffffffffffffffffffffffff8116810361033057815260243573ffffffffffffffffffffffffffffffffffffffff8116810361033057602082015260443562ffffff811681036103305760408201526064358060020b81036103305760608201526084359073ffffffffffffffffffffffffffffffffffffffff821682036103305760800152565b9181601f840112156103305782359167ffffffffffffffff8311610330576020838186019501011161033057565b9060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126103305760043567ffffffffffffffff811161033057826023820112156103305780600401359267ffffffffffffffff84116103305760248460051b83010111610330576024019190565b91908203918211610dad57565b91908201809211610dad57565b156130b857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f554e415554484f52495a454400000000000000000000000000000000000000006044820152fd5b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b73ffffffffffffffffffffffffffffffffffffffff7f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95c166132025773ffffffffffffffffffffffffffffffffffffffff81169081156131fe576131b790613b4f565b907f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95d7f1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd955d565b5050565b7f996caf65000000000000000000000000000000000000000000000000000000005f5260045ffd5b6f8000000000000000000000000000000081101561324857600f0b90565b7f93dafdf1000000000000000000000000000000000000000000000000000000005f5260045ffd5b9190600f0b918215613373576132a6919073ffffffffffffffffffffffffffffffffffffffff8092165f521660205260405f2090565b6132b2815c9283613be6565b80915d61332357507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5c017f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5d5b565b1561332a57565b60017f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5c017f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5d565b505050565b90919073ffffffffffffffffffffffffffffffffffffffff811690816134165750505f80808093855af1156133aa5750565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f3d604051937f8549db59000000000000000000000000000000000000000000000000000000008552600485015260406024850152806044850152805f606486013e011660640190fd5b60205f60448194968260409573ffffffffffffffffffffffffffffffffffffffff988751998a947fa9059cbb00000000000000000000000000000000000000000000000000000000865216600485015260248401525af13d15601f3d1160018551141617169282815282602082015201521561348f5750565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f3d604051937fb12c5f9c000000000000000000000000000000000000000000000000000000008552600485015260406024850152806044850152805f606486013e011660640190fd5b7f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95c919073ffffffffffffffffffffffffffffffffffffffff8316613550576133219034935b61354a8561322a565b90613270565b346135b5576133219061358c7f1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd955c61358786613b4f565b613097565b935f7f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95d613541565b7f19d245cf000000000000000000000000000000000000000000000000000000005f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016300361361c57565b7f0d89438e000000000000000000000000000000000000000000000000000000005f5260045ffd5b5473ffffffffffffffffffffffffffffffffffffffff161561366257565b7f486aa307000000000000000000000000000000000000000000000000000000005f5260045ffd5b91926137056101209473ffffffffffffffffffffffffffffffffffffffff61371d999794168552602085019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b60c083015260e0820152816101008201520191613116565b90565b9073ffffffffffffffffffffffffffffffffffffffff6020613321949361374f85848351168660801d90613270565b01511690600f0b90613270565b62ffffff16620f4240811161376e5750565b7f14002113000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9061371d95936138166101609473ffffffffffffffffffffffffffffffffffffffff61384594168552602085019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051600290810b60c08501526020820151900b60e0840152604081015161010084015260600151610120830152565b816101408201520191613116565b9081600f0b91820361324857565b91610180936138df61390e9273ffffffffffffffffffffffffffffffffffffffff61371d9a9895168652602086019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051600290810b60c08601526020820151900b60e0850152604081015161010085015260600151610120840152565b610140820152816101608201520191613116565b929593945f9573ffffffffffffffffffffffffffffffffffffffff85163314613a6a57875f6040860151135f146139fc576104008616613965575b505050505050565b6139f0969897506139dd926139e9969594926139b1926040519687957f5a2a81000000000000000000000000000000000000000000000000000000000060208801523360248801613861565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282612ed8565b6002821615159161492c565b809261494c565b915f808080808061395d565b94939291906101008516613a1257505050505050565b6139f0969850859750613a5e92916139b1916139e9976040519687957f8db2b6520000000000000000000000000000000000000000000000000000000060208801523360248801613861565b6001821615159161492c565b505f955050505050565b608081161580613b26575b613afc57604081161580613b1a575b613afc5761040081161580613b0e575b613afc5761010081161580613b02575b613afc5773ffffffffffffffffffffffffffffffffffffffff8116613adc575062ffffff1662800000141590565b613fff161590811591613aed575090565b62800000915062ffffff161490565b50505f90565b50600181161515613aae565b50600281161515613a9e565b50600481161515613a8e565b50600881161515613a7f565b6280000062ffffff821614613b4a5761371d8161375c565b505f90565b73ffffffffffffffffffffffffffffffffffffffff1680613b6f57504790565b6020602491604051928380927f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa90811561257d575f91613bb7575090565b90506020813d602011613bde575b81613bd260209383612ed8565b81010312610330575190565b3d9150613bc5565b9190915f8382019384129112908015821691151617610dad57565b6020830151955f9586959194913373ffffffffffffffffffffffffffffffffffffffff851614613dea5760808416613c3b575b5050505050565b613d23926139b1613d1d92613d09946040519586947f575e24b4000000000000000000000000000000000000000000000000000000006020870152336024870152613cd3604487018c73ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051151560e487015260208101516101048701526040015173ffffffffffffffffffffffffffffffffffffffff16610124860152565b610140610144850152610164840191613116565b82613fe2565b916060835103613dc2576040015162ffffff166280000014613db6575b600816613d51575b80808080613c34565b604001519250608083901d600f0b8015613d4857613d72905f861295613be6565b9315613dae575f84135b613d86575f613d48565b7ffa0b71d6000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f8412613d7c565b60608201519350613d40565b7f1e048e1d000000000000000000000000000000000000000000000000000000005f5260045ffd5b505f965086955050505050565b90600f0b90600f0b01907fffffffffffffffffffffffffffffffff8000000000000000000000000000000082126f7fffffffffffffffffffffffffffffff831317610dad57565b9196959394929473ffffffffffffffffffffffffffffffffffffffff83163314613fd5578460801d94600f0b938860408516613efd575b50505050505f9481600f0b15801590613ef1575b613e95575b5050509190565b613ecc9395505f60208201511290511515145f14613ed4576fffffffffffffffffffffffffffffffff169060801b175b809361494c565b5f8080613e8e565b906fffffffffffffffffffffffffffffffff169060801b17613ec5565b5082600f0b1515613e89565b613fb9613fc5946139b1611a6595613fcb999895613f9e613cd3966040519788967fb47b2fb1000000000000000000000000000000000000000000000000000000006020890152336024890152604488019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8c610144850152610160610164850152610184840191613116565b6004821615159161492c565b90613df7565b5f80808088613e75565b5050505050909150905f90565b9190915f80602085519501948582855af11561407e5750604051917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f3d011683016040523d83523d9060208401915f833e602084511091821561404a575b5050613dc257565b5190517fffffffff000000000000000000000000000000000000000000000000000000009182169116141590505f80614042565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f3d73ffffffffffffffffffffffffffffffffffffffff604051947f319d54c300000000000000000000000000000000000000000000000000000000865216600485015260406024850152806044850152805f606486013e011660640190fd5b908160801b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff70010000000000000000000000000000000084099282808510940393808503948584111561033057146141ba5770010000000000000000000000000000000082910981805f03168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b5091500490565b90808202917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff828209918380841093039280840393847001000000000000000000000000000000001115610330571461423a57700100000000000000000000000000000000910990828211900360801b910360801c1790565b50505060801c90565b818102907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83820990828083109203918083039283620f4240111561033057146142c2577fde8f6cefed634549b62c77574f722e1ac57e23f24d8fd5cb790fb65668c2613993620f4240910990828211900360fa1b910360061c170290565b5050620f424091500490565b90808202917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff828209918380841093039280840393846c010000000000000000000000001115610330571461433f576c01000000000000000000000000910990828211900360a01b910360601c1790565b50505060601c90565b908160601b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6c0100000000000000000000000084099282808510940393808503948584111561033057146141ba576c0100000000000000000000000082910981805f03168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b91818302917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff818509938380861095039480860395868511156103305714614491579082910981805f03168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b505091500490565b919060020b9060020b908181076144c75705908160081d5f52602052600160ff60405f2092161b8154189055565b601c906044926040519163d4d8f3e683526020830152604082015201fd5b60020b908160ff1d82810118620d89e8811161482b5763ffffffff9192600182167001fffcb933bd6fad37aa2d162d1a5940010270010000000000000000000000000000000018916002811661480f575b600481166147f3575b600881166147d7575b601081166147bb575b6020811661479f575b60408116614783575b60808116614767575b610100811661474b575b610200811661472f575b6104008116614713575b61080081166146f7575b61100081166146db575b61200081166146bf575b61400081166146a3575b6180008116614687575b62010000811661466b575b620200008116614650575b620400008116614635575b620800001661461c575b5f126145f5575b0160201c90565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff046145ee565b6b048a170391f7dc42444e8fa290910260801c906145e7565b6d2216e584f5fa1ea926041bedfe9890920260801c916145dd565b916e5d6af8dedb81196699c329225ee6040260801c916145d2565b916f09aa508b5b7a84e1c677de54f3e99bc90260801c916145c7565b916f31be135f97d08fd981231505542fcfa60260801c916145bc565b916f70d869a156d2a1b890bb3df62baf32f70260801c916145b2565b916fa9f746462d870fdf8a65dc1f90e061e50260801c916145a8565b916fd097f3bdfd2022b8845ad8f792aa58250260801c9161459e565b916fe7159475a2c29b7443b29c7fa6e889d90260801c91614594565b916ff3392b0822b70005940c7a398e4b70f30260801c9161458a565b916ff987a7253ac413176f2b074cf7815e540260801c91614580565b916ffcbe86c7900a88aedcffc83b479aa3a40260801c91614576565b916ffe5dee046a99a2a811c461f1969c30530260801c9161456c565b916fff2ea16466c96a3843ec78b326b528610260801c91614563565b916fff973b41fa98c081472e6896dfb254c00260801c9161455a565b916fffcb9843d60f6159c9db58835c9266440260801c91614551565b916fffe5caca7e10e4e61c3624eaa0941cd00260801c91614548565b916ffff2e50f5f656932ef12357cf3c7fdcc0260801c9161453f565b916ffff97272373d413259a46990580e213a0260801c91614536565b827f8b86327a000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b905f83600f0b125f1461488c57614882925f036fffffffffffffffffffffffffffffffff1691615c21565b5f81126132485790565b6148a8926fffffffffffffffffffffffffffffffff1691615bc6565b5f8112613248575f0390565b905f83600f0b125f146148df57614882925f036fffffffffffffffffffffffffffffffff1691615d18565b6148a8926fffffffffffffffffffffffffffffffff1691615c61565b906fffffffffffffffffffffffffffffffff90600f0b911601908160801c61491f57565b6393dafdf15f526004601cfd5b9061493691613fe2565b9015613b4a576040815103613dc2576040015190565b61496f906149618360801d8260801d03613853565b92600f0b90600f0b03613853565b6fffffffffffffffffffffffffffffffff169060801b1790565b73fffd8963efd1fc6a506488495d951d516396168273ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffd895d83011611614cce5777ffffffffffffffffffffffffffffffffffffffff000000008160201b16806fffffffffffffffffffffffffffffffff811160071b90811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c9060ff821160031b91821c92600f841160021b93841c94600160038711811b96871c1196171717171717179060808210155f14614cc4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8182011c5b800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c80029081607f1c8260ff1c1c80029283607f1c8460ff1c1c80029485607f1c8660ff1c1c80029687607f1c8860ff1c1c80029889607f1c8a60ff1c1c80029a8b607f1c8c60ff1c1c80029c8d80607f1c9060ff1c1c800260cd1c6604000000000000169d60cc1c6608000000000000169c60cb1c6610000000000000169b60ca1c6620000000000000169a60c91c6640000000000000169960c81c6680000000000000169860c71c670100000000000000169760c61c670200000000000000169660c51c670400000000000000169560c41c670800000000000000169460c31c671000000000000000169360c21c672000000000000000169260c11c674000000000000000169160c01c67800000000000000016907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800160401b1717171717171717171717171717693627a301d71055774c85027ffffffffffffffffffffffffffffffffffd709b7e5480fba5a50fed5e62ffc556810160801d60020b906fdb2df09e81959a81455e260799a0632f0160801d60020b918282145f14614c815750905090565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff614cb5846144e5565b1611614cbf575090565b905090565b81607f031b614a99565b73ffffffffffffffffffffffffffffffffffffffff907f61487524000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b8115614d1a570490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b9190915f604051614d5781612ea0565b5f815260208101905f8252604081015f815260608201965f885260808301965f885260a08401975f895284998854906020850151159a6fffffffffffffffffffffffffffffffff60038c0154169384928d155f14615bb7575f610fff8660b81c169b5b60408a01518c52528473ffffffffffffffffffffffffffffffffffffffff168099528460a01c60020b90528c155f14615bac5760018c0154905b5252608084015162400000811615615b9d5762bfffff16614e148161375c565b61ffff8816615b7757945b855f60408701511297620f424062ffffff83161480615b6f575b615b4757604087015115615b315750508a615ad157606085019073ffffffffffffffffffffffffffffffffffffffff82511681811015615a9a5750505173ffffffffffffffffffffffffffffffffffffffff166401000276a38110615a6f57505b60405160805260e060805101608051811067ffffffffffffffff82111761085d576040525f608051525f602060805101525f604060805101525f606060805101525f6080805101525f60a060805101525f60c060805101525b8a51158015615a32575b6158e55773ffffffffffffffffffffffffffffffffffffffff60408c0151166080515260608b015160020b845160020b90815f818307129105038b155f1461578c5760ff8116918160020b60081d60010b5f5260058c0160205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60018086011b0190541691821592831593845f1461575757610330577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2761894817f07060605060205040602030205040301060502050303040105050304000000006f8421084210842108cc6318c6db6d54be60ff946fffffffffffffffffffffffffffffffff1060071b83811c67ffffffffffffffff1060061b1783811c63ffffffff1060051b1783811c61ffff1060041b1783811c861060031b1792831c1c601f161a1790031660020b900360020b0260020b5b905b15156040608051015260020b8060206080510152131561572a575b620d89e86020608051015160020b121561571a575b8973ffffffffffffffffffffffffffffffffffffffff6150aa6020608051015160020b6144e5565b1680606060805101528c73ffffffffffffffffffffffffffffffffffffffff60408201511673ffffffffffffffffffffffffffffffffffffffff606089015116906fffffffffffffffffffffffffffffffff60a08401511692519262ffffff8b169473ffffffffffffffffffffffffffffffffffffffff60ff8815168583101885831802851816831015935f86125f1461554a5786620f4240039161515183885f03614243565b861561552b57615170858760ff8d151686861018868618028618615c61565b975b8882106154525750508160ff73ffffffffffffffffffffffffffffffffffffffff9a15168183101891180218958691620f424082145f14615443575050845b935b1561543557916151c292615c21565b905b60c0608051015260a060805101526080805101521660408c015285155f146153f75760a060805101515f8112613248578b51038b5261520f60808051015160c06080510151906130a4565b5f81126132485760208c018051918203918213600116610dad57525b61ffff87166153c4575b6fffffffffffffffffffffffffffffffff60a08c015116806153a4575b5073ffffffffffffffffffffffffffffffffffffffff60408c01511673ffffffffffffffffffffffffffffffffffffffff606060805101511681145f146153665750604060805101516152bd575b60ff8a15166020608051015160020b0360020b60608c0152614ef3565b89615342576fffffffffffffffffffffffffffffffff61532960808d015160028c01545b6020608051015160020b60020b5f5260048d0160205260405f2091600183019081549003905560028201908154900390555460801d8d8d15615334575b60a0015183166148fb565b1660a08c01526152a0565b5f91909103600f0b9061531e565b6fffffffffffffffffffffffffffffffff6153298c608060018d01549101516152e1565b73ffffffffffffffffffffffffffffffffffffffff6080515116810361538d575b50614ef3565b61539690614989565b60020b60608c01525f615387565b6153b49060c06080510151614100565b60808c019081510190525f615252565b96620f424060808051015161ffff89169060c06080510151010204908160c060805101510360c060805101520196615235565b60808051015160c06080510151015f8112613248578b51018b5260a060805101515f81126132485761542e60208d01918251613be6565b905261522b565b61543e92615d18565b6151c2565b61544d9187615dbf565b6151b1565b975098505050508094501581151761551e5773ffffffffffffffffffffffffffffffffffffffff9482156154ba5761548b908583615e5e565b935b8480828486156154ab576154a092615c61565b8095015f03936151b3565b916154b592615bc6565b6154a0565b6154f6906154f1908781116154fc576154e9906fffffffffffffffffffffffffffffffff88169060601b614d10565b8784166130a4565b615ef2565b9361548d565b615519906fffffffffffffffffffffffffffffffff881690614348565b6154e9565b634f2461b85f526004601cfd5b6155448560ff8c15168585101885851802851888615bc6565b97615172565b91969395949386156156fb5761556f888560ff84151686861018868618028618615c21565b945b8581106155d657509173ffffffffffffffffffffffffffffffffffffffff97918160ff6155c396951516818310189118021880965b156155c857916155b592615c61565b809380620f42400391615dbf565b6151c4565b6155d192615bc6565b6155b5565b945050505081851582151761551e5784156156d15773ffffffffffffffffffffffffffffffffffffffff81116156785760601b6fffffffffffffffffffffffffffffffff86168082061515910401905b73ffffffffffffffffffffffffffffffffffffffff8116958287111561566b5773ffffffffffffffffffffffffffffffffffffffff6155c393819803165b80966155a6565b634323a5555f526004601cfd5b6fffffffffffffffffffffffffffffffff86166156a3816c01000000000000000000000000846143fa565b918115614d1a576c0100000000000000000000000090096156c5575b90615626565b600101806156bf575f80fd5b90856156f66155c39373ffffffffffffffffffffffffffffffffffffffff9884615de8565b615664565b6157148860ff8315168585101885851802851886615d18565b94615571565b620d89e860206080510152615082565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276186020608051015261506d565b50507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276189360020b900360020b0260020b615050565b600101908160020b908260ff16928260081d60010b5f5260058d016020527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600160ff60405f2093161b011990541691821592831593845f146158ac57610330577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276189481600160ff931901167e1f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405601f6101e07f804040554300526644320000502061067405302602000010750620017611707760fc7fb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff860260f81c161b60f71c1692831c63d76453e004161a17031660020b0160020b0260020b5b90615052565b505060ff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276189481031660020b0160020b0260020b6158a6565b90919996989397929550606087015160a01b76ffffff0000000000000000000000000000000000000000167fffffffffffffffffff000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff60408a0151169216171782556fffffffffffffffffffffffffffffffff60a0870151168091036159ed575b5081156159de57600260808601519101555b15146159c1576159a7604061599b6020850151613853565b92015183519003613853565b6fffffffffffffffffffffffffffffffff169060801b1793565b6040015181516159d19103613853565b6159a76020830151613853565b60016080860151910155615983565b6fffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffff000000000000000000000000000000006003830154161760038201555f615971565b5073ffffffffffffffffffffffffffffffffffffffff60408c01511673ffffffffffffffffffffffffffffffffffffffff60608601511614614efd565b7f9e4d7cc7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60449250604051917f7c9c6e8f00000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b606085019073ffffffffffffffffffffffffffffffffffffffff82511681811115615a9a5750505173ffffffffffffffffffffffffffffffffffffffff1673fffd8963efd1fc6a506488495d951d5263988d26811015615a6f5750614e9a565b9a509a50505050975050505050505f925f929190565b7f96206246000000000000000000000000000000000000000000000000000000005f5260045ffd5b508815614e39565b62ffffff168061ffff891602620f424080820615159104019061ffff8916010394614e1f565b508060d01c62ffffff16614e14565b60028c015490614df4565b5f610fff8660c41c169b614dba565b6fffffffffffffffffffffffffffffffff6c010000000000000000000000009173ffffffffffffffffffffffffffffffffffffffff80600195169116038060ff1d90810118931692615c1881856142ce565b93091515160190565b6fffffffffffffffffffffffffffffffff9073ffffffffffffffffffffffffffffffffffffffff8061371d9594169116038060ff1d9081011891166142ce565b9073ffffffffffffffffffffffffffffffffffffffff811673ffffffffffffffffffffffffffffffffffffffff831611615d12575b73ffffffffffffffffffffffffffffffffffffffff8216928315615d06577bffffffffffffffffffffffffffffffff00000000000000000000000073ffffffffffffffffffffffffffffffffffffffff615cfa948185169403169160601b16615dbf565b90808206151591040190565b62bfc9215f526004601cfd5b90615c96565b73ffffffffffffffffffffffffffffffffffffffff821673ffffffffffffffffffffffffffffffffffffffff821611615db9575b73ffffffffffffffffffffffffffffffffffffffff8116918215615d065761371d937bffffffffffffffffffffffffffffffff00000000000000000000000073ffffffffffffffffffffffffffffffffffffffff615db4948185169403169160601b166143fa565b614d10565b90615d4c565b929190615dcd8282866143fa565b938215614d1a5709615ddb57565b9060010190811561033057565b91908115615e59577bffffffffffffffffffffffffffffffff00000000000000000000000073ffffffffffffffffffffffffffffffffffffffff9160601b169216918282029183838311918404141615615e4c5761371d926154f192820391615dbf565b63f5c787f15f526004601cfd5b505090565b90918015615eec5773ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffff000000000000000000000000819460601b16921680820281615eae8483614d10565b14615ed4575b5090615ec3615ec89284614d10565b6130a4565b80820615159104011690565b8301838110615eb4579150615ee892615dbf565b1690565b50905090565b9073ffffffffffffffffffffffffffffffffffffffff82169182036132485756fea2646970667358221220869bd0bb084561b6548d25f7df7705a61a4ab603a2e434d0334613bc910ea31364736f6c634300081a00330000000000000000000000000000000000000000000000000000000000030d40", + "nonce": "0x3", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xd982eb", + "logs": [ + { + "address": "0xf242ce588b030d0895c51c0730f2368680f80644", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000b7a249bdeff39727b5eb4c7ad458f682bae6adad" + ], + "data": "0x", + "blockHash": "0xbe2e0a1495d4ce221acefeb888c03d41bddf4f2e34daf178b2918ffa4921dce9", + "blockNumber": "0x629a6e", + "transactionHash": "0xae8b5df5aeaac2faf35a24f27312c4defd3eefbca9243f504e68621e5fae551c", + "transactionIndex": "0x36", + "logIndex": "0x83", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000800000000000000000000040000000000000200000020000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xae8b5df5aeaac2faf35a24f27312c4defd3eefbca9243f504e68621e5fae551c", + "transactionIndex": "0x36", + "blockHash": "0xbe2e0a1495d4ce221acefeb888c03d41bddf4f2e34daf178b2918ffa4921dce9", + "blockNumber": "0x629a6e", + "gasUsed": "0x514826", + "effectiveGasPrice": "0x180be5bdd", + "from": "0xb7a249bdeff39727b5eb4c7ad458f682bae6adad", + "to": null, + "contractAddress": "0xf242ce588b030d0895c51c0730f2368680f80644" + } + ], + "libraries": [], + "pending": [], + "returns": { + "manager": { + "internal_type": "contract IPoolManager", + "value": "0xf242cE588b030d0895C51C0730F2368680f80644" + } + }, + "timestamp": 1723140369, + "chain": 11155111, + "commit": "8f392139" +} \ No newline at end of file diff --git a/broadcast/01_PoolManager.s.sol/84532/run-latest.json b/broadcast/01_PoolManager.s.sol/84532/run-latest.json new file mode 100644 index 000000000..8cf013d8a --- /dev/null +++ b/broadcast/01_PoolManager.s.sol/84532/run-latest.json @@ -0,0 +1,75 @@ +{ + "transactions": [ + { + "hash": "0xd029dbd1f19f32a5512ebf04817925f32ef1480312e50d36570c2cadf990b812", + "transactionType": "CREATE", + "contractName": "PoolManager", + "contractAddress": "0x39bf2eff94201cfaa471932655404f63315147a4", + "function": null, + "arguments": [ + "500000" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x69a309", + "value": "0x0", + "input": "0x60c034609d57601f615ff838819003918201601f19168301916001600160401b0383118484101760a157808492602094604052833981010312609d57515f80546001600160a01b03191633908117825560405192917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a36080523060a052615f4290816100b6823960805181611317015260a051816135ed0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60a0806040526004361015610012575f80fd5b5f3560e01c908162fdd58e14612d785750806301ffc9a714612cb9578063095bcdb614612c0f5780630b0d9c0914612b8357806311da60b414612b28578063156e29f614612a785780631e2eaeaf14612a3e578063234266d7146127c75780632d7713891461270557806335fd631a146126915780633dd45adb1461262d578063426a8493146125a957806348c894911461231e5780635275965114612206578063558a72971461212f578063598af9e7146120975780635a6bcfda146115e2578063695c5bf514610f3f5780637e87ce7d14610e0257806380f0b44c14610ce15780638161b87414610be95780638da5cb5b14610b9957806397e8cd4e14610b365780639bf6645f14610ae9578063a584119414610aa8578063b6363cf214610a17578063dbd035ff146109c1578063f02de3b214610970578063f135baaa14610936578063f2fde38b1461088a578063f3cd914c146104ff578063f5298aca146103345763fe99049a14610186575f80fd5b346103305760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576101bd612de2565b6101c5612e05565b90604435917f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885961027973ffffffffffffffffffffffffffffffffffffffff80606435951693843314158061030d575b610287575b845f52600460205260405f20875f5260205260405f2061023a878254613090565b90551693845f52600460205260405f20865f5260205260405f2061025f82825461309d565b905560408051338152602081019290925290918291820190565b0390a4602060405160018152f35b845f52600560205260405f208233165f5260205260405f20875f5260205260405f2054867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036102da575b5050610219565b6102e391613090565b855f52600560205260405f208333165f5260205260405f20885f5260205260405f20555f866102d3565b50845f52600360205260405f208233165f5260205260ff60405f20541615610214565b5f80fd5b346103305761034236612e28565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7577f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288596103ed73ffffffffffffffffffffffffffffffffffffffff805f9516956103bb6103b386613223565b339089613269565b169233841415806104a0575b6103f2575b8385526004602052604085208686526020526040852061025f828254613090565b0390a4005b83855260056020526040852073ffffffffffffffffffffffffffffffffffffffff33168652602052604085208686526020526040852054817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610459575b50506103cc565b61046291613090565b84865260056020526040862073ffffffffffffffffffffffffffffffffffffffff331687526020526040862087875260205260408620558681610452565b5083855260036020526040852073ffffffffffffffffffffffffffffffffffffffff3316865260205260ff604086205416156103c7565b7f54e3ca0d000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610330576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761053836612f24565b60607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5c36011261033057604051906060820182811067ffffffffffffffff82111761085d5760405260a4358015158103610330578252602082019060c435825260e4359073ffffffffffffffffffffffffffffffffffffffff8216820361033057604084019182526101043567ffffffffffffffff8111610330576105e1903690600401612ff0565b9290937fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576106136135d6565b51156108355760a0822092835f52600660205260405f20906106348261363d565b60808401958482828a8a5173ffffffffffffffffffffffffffffffffffffffff169361065f94613bfa565b90949195606088015160020b968b511515915173ffffffffffffffffffffffffffffffffffffffff1691604051986106968a612eb5565b895260208901526040880152606087015262ffffff166080860152885115155f149862ffffff6107b7986106ec6107a49860209d6108155773ffffffffffffffffffffffffffffffffffffffff8b511695614d40565b9492968291926107e8575b505073ffffffffffffffffffffffffffffffffffffffff6040850151169360606fffffffffffffffffffffffffffffffff60a08301511691015160020b90604051958860801d600f0b875288600f0b60208801526040870152606086015260808501521660a08301527f40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112f60c03393a38673ffffffffffffffffffffffffffffffffffffffff8a5116613e37565b809491946107bf575b5050823391613719565b604051908152f35b73ffffffffffffffffffffffffffffffffffffffff6107e19251169083613719565b84806107ad565b73ffffffffffffffffffffffffffffffffffffffff165f5260018f5260405f209081540190558e806106f7565b73ffffffffffffffffffffffffffffffffffffffff8e8c01511695614d40565b7fbe8b8507000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330577fffffffffffffffffffffffff00000000000000000000000000000000000000006108e2612de2565b73ffffffffffffffffffffffffffffffffffffffff5f549161090782841633146130aa565b1691829116175f55337f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3005b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576004355c5f5260205ff35b34610330575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b34610330576109cf3661301e565b6040519160408360208152836020820152019160051b8301916020806040850193925b8335548152019101908483821015610a0e5750602080916109f2565b60408186030190f35b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610a4e612de2565b73ffffffffffffffffffffffffffffffffffffffff610a6b612e05565b91165f52600360205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060ff60405f2054166040519015158152f35b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610ae7610ae2612de2565b61314d565b005b3461033057610af73661301e565b6040519160408360208152836020820152019160051b8301916020806040850193925b83355c8152019101908483821015610a0e575060208091610b1a565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305773ffffffffffffffffffffffffffffffffffffffff610b82612de2565b165f526001602052602060405f2054604051908152f35b34610330575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057602073ffffffffffffffffffffffffffffffffffffffff5f5416604051908152f35b346103305760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610c20612de2565b610c28612e05565b9060443573ffffffffffffffffffffffffffffffffffffffff600254163303610cb9576020926107b79180610cb1575073ffffffffffffffffffffffffffffffffffffffff81165f526001845260405f20549283915b73ffffffffffffffffffffffffffffffffffffffff81165f526001865260405f20610caa848254613090565b9055613371565b928391610c7e565b7f48f5c3ed000000000000000000000000000000000000000000000000000000005f5260045ffd5b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610d18612de2565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d757335f90815273ffffffffffffffffffffffffffffffffffffffff8216602052604090205c610d6e602435613223565b600f0b908103610dda577fffffffffffffffffffffffffffffffff800000000000000000000000000000008114610dad57610ae79133915f0390613269565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fbda73abf000000000000000000000000000000000000000000000000000000005f5260045ffd5b346103305760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610e3a36612f24565b610e42612f12565b9073ffffffffffffffffffffffffffffffffffffffff600254163303610cb9576103e9610fff831610623e900062fff0008416101615610f0e57602060a07fe9c42593e71f84403b84352cd168d693e2c9fcd1fdbcc3feb21d92b43e6696f9922092835f526006825260405f20610eb88161363d565b805479ffffff00000000000000000000000000000000000000000000008360b81b16907fffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffff1617905562ffffff60405191168152a2005b62ffffff827fa7abe2f7000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b346103305760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057610f7736612f24565b60a4359073ffffffffffffffffffffffffffffffffffffffff82168083036103305760c43567ffffffffffffffff811161033057610fb9903690600401612ff0565b90610fc26135d6565b6060840193845160020b617fff81136115b75750845160020b6001811261158c575073ffffffffffffffffffffffffffffffffffffffff81511692602082019373ffffffffffffffffffffffffffffffffffffffff855116908181101561155e5750506080820173ffffffffffffffffffffffffffffffffffffffff81511695604084019661105762ffffff89511682613a6d565b15611533575061106c62ffffff885116613b2b565b91805173ffffffffffffffffffffffffffffffffffffffff811690813303611470575b505060a08520955f73ffffffffffffffffffffffffffffffffffffffff6002541680611312575b50875f52600660205260405f2090815473ffffffffffffffffffffffffffffffffffffffff166112ea5773ffffffffffffffffffffffffffffffffffffffff7fdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d64389860a09860209f98839662ffffff96859661114e7cffffff00000000000000000000000000000000000000000000000000009d614982565b9c8d9460d01b169079ffffff00000000000000000000000000000000000000000000008f76ffffff00000000000000000000000000000000000000008f9188901b16179160b81b16171790558851908a868316928333036111e8575b5050505050505116995116995116995160020b91511690604051998a528a8a01526040890152606088015260020b95866080820152a4604051908152f35b611000166111f7575b816111aa565b6112ae6112da926112df96604051966020947fa910f80f00000000000000000000000000000000000000000000000000000000899687015233602487015261128c604487018c73ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b60e486015260020b61010485015261012061012485015261014484019161310f565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101845283612ed1565b613fdb565b508f8881808a6111f1565b7f7983c051000000000000000000000000000000000000000000000000000000005f5260045ffd5b90505a7f0000000000000000000000000000000000000000000000000000000000000000809110611448575f8091604051938260208601917f553bfc370000000000000000000000000000000000000000000000000000000083526113c58d602489019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b60a487526113d460c488612ed1565b81965193f160203d141680611435575b80611427575b8061140c575b156114045762ffffff600191165b906110b6565b505f806113fe565b506103e9610fff821610623e900062fff000831610166113f0565b5062ffffff811681146113ea565b60405191503d5f833e5f825192526113e4565b7f1ee49702000000000000000000000000000000000000000000000000000000005f5260045ffd5b6120001661147f575b8061108f565b61152c90604051907f3440d82000000000000000000000000000000000000000000000000000000000602083015233602483015261150a604483018973ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8460e48301526101006101048301526112da826112ae61012482018a8d61310f565b508a611479565b7fe65af6a0000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f6e6c9830000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7fe9e90588000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fb70024f8000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b34610330576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761161b36612f24565b60807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5c360112610330576040519061165282612e7d565b60a4358060020b810361033057825260c4358060020b810361033057602083015260e435604083015260608201916101043583526101243567ffffffffffffffff8111610330576116a7903690600401612ff0565b907fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576116d76135d6565b60a0842091825f52600660205260405f20916116f28361363d565b60808601518573ffffffffffffffffffffffffffffffffffffffff821691823303611fb9575b5090505160020b93602086015160020b95611736604082015161384c565b96606089015160020b99516040519a61174e8c612e99565b338c528860208d01528260408d015289600f0b60608d015260808c015260a08b01525f9080881215611f82577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276188812611f5657620d89e88113611f2b57604051986117b88a612e7d565b5f8a525f60208b01525f60408b01525f60608b015280600f0b611c7b575b60048801988060020b5f528960205260405f20998360020b5f5260205260405f20895460a01c60020b8281125f14611c29575060028060018d0154600184015490039c015491015490039c5b60a073ffffffffffffffffffffffffffffffffffffffff8251169101519060405191602683015284600683015282600383015281525f603a600c83012091816040820152816020820152525f526006890160205260405f20986fffffffffffffffffffffffffffffffff8a54169a83600f0b155f14611bcc578b15611ba4576118e560409f6119b99c6118df916119cb9f5b60018301956118d760026118cb848a5485036141ba565b950192835485036141ba565b965555613223565b91613223565b6fffffffffffffffffffffffffffffffff169060801b179b5f84600f0b12611b3a575b5082600f0b611a03575b5050505061193961192a8960801d8360801d0161384c565b9189600f0b90600f0b0161384c565b6fffffffffffffffffffffffffffffffff169060801b1791815160020b90602083015160020b8b840151908c5193845260208401528b8301527f541c041c2cce48e614b3de043c9280f06b6164c0a1741649e2de3c3d375f797460603393a38773ffffffffffffffffffffffffffffffffffffffff60808201511661391b565b809491946119d7575b50833391613719565b82519182526020820152f35b6119fd9073ffffffffffffffffffffffffffffffffffffffff6080840151169083613719565b856119c2565b809192939450548060a01c60020b9073ffffffffffffffffffffffffffffffffffffffff16908381125f14611a6b5750505091611a5991611a53611a49611a5e956144de565b91600f0b926144de565b906148ad565b61384c565b60801b5b8a808080611912565b948091939295125f14611b06576fffffffffffffffffffffffffffffffff92611ac3611a59611ad994611ab0611a59600396611aaa8b600f0b916144de565b876148ad565b93611abe89600f0b926144de565b614850565b84169060801b17940192600f0b828454166148f4565b167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055611a62565b9150611b23935091611b1d611a49611a59946144de565b90614850565b6fffffffffffffffffffffffffffffffff16611a62565b808f9151611b7a575b0151611b50575b8e611908565b611b75848260049160020b5f52016020525f6002604082208281558260018201550155565b611b4a565b611b9f848460049160020b5f52016020525f6002604082208281558260018201550155565b611b43565b7faefeb924000000000000000000000000000000000000000000000000000000005f5260045ffd5b6118e560409f6119b99c6118df916119cb9f6fffffffffffffffffffffffffffffffff611bfc8a600f0b836148f4565b167fffffffffffffffffffffffffffffffff000000000000000000000000000000008454161783556118b4565b909a908413611c4f5760028060018d0154600184015490039c015491015490039c611822565b99600260018b015460018d01549003600183015490039b81808d0154910154900391015490039c611822565b8860020b5f5260048801602052886fffffffffffffffffffffffffffffffff60405f20805490611cd5838316928d611cb688600f0b866148f4565b9415968787871615141597611efc575b505086600f0b9060801d613df0565b60801b8383161790551660208c01528a528160020b5f526004880160205260405f208054906fffffffffffffffffffffffffffffffff8216611d1a84600f0b826148f4565b901592836fffffffffffffffffffffffffffffffff831615141593611ed5575b84600f0b9060801d600f0b03916f7fffffffffffffffffffffffffffffff83137fffffffffffffffffffffffffffffffff80000000000000000000000000000000841217610dad57826fffffffffffffffffffffffffffffffff935060801b8383161790551660608c015260408b0152898c5f83600f0b1215611dfd575b5051611de3575b60408a0151156117d657611dde60808d015160020b8360058b01614492565b6117d6565b611df860808d015160020b8a60058b01614492565b611dbf565b60016fffffffffffffffffffffffffffffffff602060808294015160020b940151169280807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2761805028180620d89e80502030501810416809111611ea9576fffffffffffffffffffffffffffffffff60608c01511611611e7d57898c611db8565b507fb8e3c385000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b897fb8e3c385000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b8b5460a01c60020b8613611d3a5760018c0154600184015560028c01546002840155611d3a565b815460a01c60020b1215611f11575b8f611cc6565b80600160029201546001860155015460028401558d611f0b565b7f1ad777f8000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b877fd5e2f7ab000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b87604491604051917fc4433ed500000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b5f60408301511380809161208a575b1561201c575050612012916112da896112ae87876040519687947f259982e50000000000000000000000000000000000000000000000000000000060208701523360248701613792565b505b878581611718565b15908161207c575b50612031575b5050612014565b612074916112da896112ae87876040519687947f21d0ee700000000000000000000000000000000000000000000000000000000060208701523360248701613792565b50878561202a565b61020091501615158a612024565b5061080082161515611fc8565b346103305760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576120ce612de2565b73ffffffffffffffffffffffffffffffffffffffff6120eb612e05565b91165f52600560205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f5260205260405f206044355f52602052602060405f2054604051908152f35b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057612166612de2565b602435908115158092036103305773ffffffffffffffffffffffffffffffffffffffff90335f52600360205260405f208282165f5260205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff851617905560405192835216907fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa26760203392a3602060405160018152f35b346103305760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761223e36612f24565b612246612f12565b906280000062ffffff604083015116148015906122fa575b6122d25760a09061226e83613755565b205f52600660205260405f20906122848261363d565b81547fffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffff1660d09190911b7cffffff000000000000000000000000000000000000000000000000000016179055005b7f30d21641000000000000000000000000000000000000000000000000000000005f5260045ffd5b5073ffffffffffffffffffffffffffffffffffffffff60808201511633141561225e565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305760043567ffffffffffffffff81116103305761236d903690600401612ff0565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c612581576123f9915f9160017fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235d60405193849283927f91dd734600000000000000000000000000000000000000000000000000000000845260206004850152602484019161310f565b038183335af1908115612576575f916124ce575b507f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5c6124a65760406020915f7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f835194859381855280519182918282880152018686015e5f85828601015201168101030190f35b7f5212cba1000000000000000000000000000000000000000000000000000000005f5260045ffd5b90503d805f833e6124df8183612ed1565b8101906020818303126103305780519067ffffffffffffffff8211610330570181601f820112156103305780519067ffffffffffffffff821161085d576040519261255260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160185612ed1565b8284526020838301011161033057815f9260208093018386015e830101528161240d565b6040513d5f823e3d90fd5b7f5090d6c6000000000000000000000000000000000000000000000000000000005f5260045ffd5b346103305773ffffffffffffffffffffffffffffffffffffffff6125cc36612e28565b91929092335f52600560205260405f208282165f5260205260405f20845f526020528260405f205560405192835216907fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a760203392a4602060405160018152f35b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761265f612de2565b7fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576107b76020916134f4565b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576024356004356040519160408360208152826020820152019060051b8301916001602060408501935b83548152019101908483821015610a0e575060206001916126e9565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305760043573ffffffffffffffffffffffffffffffffffffffff81168091036103305761277773ffffffffffffffffffffffffffffffffffffffff5f541633146130aa565b807fffffffffffffffffffffffff000000000000000000000000000000000000000060025416176002557fb4bd8ef53df690b9943d3318996006dbb82a25f54719d8c8035b516a2a5b8acc5f80a2005b34610330576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305761280036612f24565b60c4359060a43560e43567ffffffffffffffff811161033057612827903690600401612ff0565b9190937fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7576128596135d6565b60a084205f52600660205260405f20946128728661363d565b6080850194855173ffffffffffffffffffffffffffffffffffffffff8116908133036129e6575b50506fffffffffffffffffffffffffffffffff6003880154169586156129be576020976128c586613223565b5f036128d086613223565b5f036fffffffffffffffffffffffffffffffff169060801b1797866129a5575b8561298a575b5050612903338884613719565b519273ffffffffffffffffffffffffffffffffffffffff84169384330361292f575b8888604051908152f35b60101661293d575b80612925565b61297e956112da936112ae926040519788957fe1b4af69000000000000000000000000000000000000000000000000000000008d8801523360248801613683565b50828080808080612937565b612996600291876140f9565b910190815401905588806128f6565b6129af81886140f9565b600183019081540190556128f0565b7fa74f97ab000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020166129f4575b80612899565b6040517fb6a8b0fa000000000000000000000000000000000000000000000000000000006020820152612a37916112da826112ae8a888a8c8a3360248801613683565b50876129ee565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057600435545f5260205ff35b3461033057612a8636612e28565b907fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d7577f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288596103ed73ffffffffffffffffffffffffffffffffffffffff805f941695612b05612af887613223565b8603600f0b339089613269565b16938484526004602052604084208685526020526040842061025f82825461309d565b5f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330577fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d75760206107b7336134f4565b346103305760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033057612bba612de2565b612bc2612e05565b604435907fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c156104d757610ae792612c0a612bfd84613223565b5f03600f0b339083613269565b613371565b346103305773ffffffffffffffffffffffffffffffffffffffff612c3236612e28565b91929092335f52600460205260405f20845f5260205260405f20612c57848254613090565b90551690815f52600460205260405f20835f5260205260405f20612c7c82825461309d565b9055604080513380825260208201939093527f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288599181908101610279565b346103305760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610330576004357fffffffff00000000000000000000000000000000000000000000000000000000811680910361033057807f01ffc9a70000000000000000000000000000000000000000000000000000000060209214908115612d4e575b506040519015158152f35b7f0f632fb30000000000000000000000000000000000000000000000000000000091501482612d43565b346103305760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103305760209073ffffffffffffffffffffffffffffffffffffffff612dc7612de2565b165f526004825260405f206024355f52825260405f20548152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361033057565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361033057565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126103305760043573ffffffffffffffffffffffffffffffffffffffff8116810361033057906024359060443590565b6080810190811067ffffffffffffffff82111761085d57604052565b60c0810190811067ffffffffffffffff82111761085d57604052565b60a0810190811067ffffffffffffffff82111761085d57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761085d57604052565b60a4359062ffffff8216820361033057565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60a09101126103305760405190612f5b82612eb5565b8160043573ffffffffffffffffffffffffffffffffffffffff8116810361033057815260243573ffffffffffffffffffffffffffffffffffffffff8116810361033057602082015260443562ffffff811681036103305760408201526064358060020b81036103305760608201526084359073ffffffffffffffffffffffffffffffffffffffff821682036103305760800152565b9181601f840112156103305782359167ffffffffffffffff8311610330576020838186019501011161033057565b9060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126103305760043567ffffffffffffffff811161033057826023820112156103305780600401359267ffffffffffffffff84116103305760248460051b83010111610330576024019190565b91908203918211610dad57565b91908201809211610dad57565b156130b157565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f554e415554484f52495a454400000000000000000000000000000000000000006044820152fd5b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b73ffffffffffffffffffffffffffffffffffffffff7f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95c166131fb5773ffffffffffffffffffffffffffffffffffffffff81169081156131f7576131b090613b48565b907f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95d7f1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd955d565b5050565b7f996caf65000000000000000000000000000000000000000000000000000000005f5260045ffd5b6f8000000000000000000000000000000081101561324157600f0b90565b7f93dafdf1000000000000000000000000000000000000000000000000000000005f5260045ffd5b9190600f0b91821561336c5761329f919073ffffffffffffffffffffffffffffffffffffffff8092165f521660205260405f2090565b6132ab815c9283613bdf565b80915d61331c57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5c017f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5d5b565b1561332357565b60017f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5c017f7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b5d565b505050565b90919073ffffffffffffffffffffffffffffffffffffffff8116908161340f5750505f80808093855af1156133a35750565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f3d604051937f8549db59000000000000000000000000000000000000000000000000000000008552600485015260406024850152806044850152805f606486013e011660640190fd5b60205f60448194968260409573ffffffffffffffffffffffffffffffffffffffff988751998a947fa9059cbb00000000000000000000000000000000000000000000000000000000865216600485015260248401525af13d15601f3d116001855114161716928281528260208201520152156134885750565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f3d604051937fb12c5f9c000000000000000000000000000000000000000000000000000000008552600485015260406024850152806044850152805f606486013e011660640190fd5b7f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95c919073ffffffffffffffffffffffffffffffffffffffff83166135495761331a9034935b61354385613223565b90613269565b346135ae5761331a906135857f1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd955c61358086613b48565b613090565b935f7f27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b95d61353a565b7f19d245cf000000000000000000000000000000000000000000000000000000005f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016300361361557565b7f0d89438e000000000000000000000000000000000000000000000000000000005f5260045ffd5b5473ffffffffffffffffffffffffffffffffffffffff161561365b57565b7f486aa307000000000000000000000000000000000000000000000000000000005f5260045ffd5b91926136fe6101209473ffffffffffffffffffffffffffffffffffffffff613716999794168552602085019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b60c083015260e082015281610100820152019161310f565b90565b9073ffffffffffffffffffffffffffffffffffffffff602061331a949361374885848351168660801d90613269565b01511690600f0b90613269565b62ffffff16620f424081116137675750565b7f14002113000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90613716959361380f6101609473ffffffffffffffffffffffffffffffffffffffff61383e94168552602085019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051600290810b60c08501526020820151900b60e0840152604081015161010084015260600151610120830152565b81610140820152019161310f565b9081600f0b91820361324157565b91610180936138d86139079273ffffffffffffffffffffffffffffffffffffffff6137169a9895168652602086019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051600290810b60c08601526020820151900b60e0850152604081015161010085015260600151610120840152565b61014082015281610160820152019161310f565b929593945f9573ffffffffffffffffffffffffffffffffffffffff85163314613a6357875f6040860151135f146139f557610400861661395e575b505050505050565b6139e9969897506139d6926139e2969594926139aa926040519687957f5a2a8100000000000000000000000000000000000000000000000000000000006020880152336024880161385a565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282612ed1565b60028216151591614925565b8092614945565b915f8080808080613956565b94939291906101008516613a0b57505050505050565b6139e9969850859750613a5792916139aa916139e2976040519687957f8db2b652000000000000000000000000000000000000000000000000000000006020880152336024880161385a565b60018216151591614925565b505f955050505050565b608081161580613b1f575b613af557604081161580613b13575b613af55761040081161580613b07575b613af55761010081161580613afb575b613af55773ffffffffffffffffffffffffffffffffffffffff8116613ad5575062ffffff1662800000141590565b613fff161590811591613ae6575090565b62800000915062ffffff161490565b50505f90565b50600181161515613aa7565b50600281161515613a97565b50600481161515613a87565b50600881161515613a78565b6280000062ffffff821614613b435761371681613755565b505f90565b73ffffffffffffffffffffffffffffffffffffffff1680613b6857504790565b6020602491604051928380927f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa908115612576575f91613bb0575090565b90506020813d602011613bd7575b81613bcb60209383612ed1565b81010312610330575190565b3d9150613bbe565b9190915f8382019384129112908015821691151617610dad57565b6020830151955f9586959194913373ffffffffffffffffffffffffffffffffffffffff851614613de35760808416613c34575b5050505050565b613d1c926139aa613d1692613d02946040519586947f575e24b4000000000000000000000000000000000000000000000000000000006020870152336024870152613ccc604487018c73ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051151560e487015260208101516101048701526040015173ffffffffffffffffffffffffffffffffffffffff16610124860152565b61014061014485015261016484019161310f565b82613fdb565b916060835103613dbb576040015162ffffff166280000014613daf575b600816613d4a575b80808080613c2d565b604001519250608083901d600f0b8015613d4157613d6b905f861295613bdf565b9315613da7575f84135b613d7f575f613d41565b7ffa0b71d6000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f8412613d75565b60608201519350613d39565b7f1e048e1d000000000000000000000000000000000000000000000000000000005f5260045ffd5b505f965086955050505050565b90600f0b90600f0b01907fffffffffffffffffffffffffffffffff8000000000000000000000000000000082126f7fffffffffffffffffffffffffffffff831317610dad57565b9196959394929473ffffffffffffffffffffffffffffffffffffffff83163314613fce578460801d94600f0b938860408516613ef6575b50505050505f9481600f0b15801590613eea575b613e8e575b5050509190565b613ec59395505f60208201511290511515145f14613ecd576fffffffffffffffffffffffffffffffff169060801b175b8093614945565b5f8080613e87565b906fffffffffffffffffffffffffffffffff169060801b17613ebe565b5082600f0b1515613e82565b613fb2613fbe946139aa611a5995613fc4999895613f97613ccc966040519788967fb47b2fb1000000000000000000000000000000000000000000000000000000006020890152336024890152604488019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8c61014485015261016061016485015261018484019161310f565b60048216151591614925565b90613df0565b5f80808088613e6e565b5050505050909150905f90565b9190915f80602085519501948582855af1156140775750604051917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f3d011683016040523d83523d9060208401915f833e6020845110918215614043575b5050613dbb57565b5190517fffffffff000000000000000000000000000000000000000000000000000000009182169116141590505f8061403b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f3d73ffffffffffffffffffffffffffffffffffffffff604051947f319d54c300000000000000000000000000000000000000000000000000000000865216600485015260406024850152806044850152805f606486013e011660640190fd5b908160801b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff70010000000000000000000000000000000084099282808510940393808503948584111561033057146141b35770010000000000000000000000000000000082910981805f03168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b5091500490565b90808202917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff828209918380841093039280840393847001000000000000000000000000000000001115610330571461423357700100000000000000000000000000000000910990828211900360801b910360801c1790565b50505060801c90565b818102907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83820990828083109203918083039283620f4240111561033057146142bb577fde8f6cefed634549b62c77574f722e1ac57e23f24d8fd5cb790fb65668c2613993620f4240910990828211900360fa1b910360061c170290565b5050620f424091500490565b90808202917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff828209918380841093039280840393846c0100000000000000000000000011156103305714614338576c01000000000000000000000000910990828211900360a01b910360601c1790565b50505060601c90565b908160601b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6c0100000000000000000000000084099282808510940393808503948584111561033057146141b3576c0100000000000000000000000082910981805f03168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b91818302917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81850993838086109503948086039586851115610330571461448a579082910981805f03168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b505091500490565b919060020b9060020b908181076144c05705908160081d5f52602052600160ff60405f2092161b8154189055565b601c906044926040519163d4d8f3e683526020830152604082015201fd5b60020b908160ff1d82810118620d89e881116148245763ffffffff9192600182167001fffcb933bd6fad37aa2d162d1a59400102700100000000000000000000000000000000189160028116614808575b600481166147ec575b600881166147d0575b601081166147b4575b60208116614798575b6040811661477c575b60808116614760575b6101008116614744575b6102008116614728575b610400811661470c575b61080081166146f0575b61100081166146d4575b61200081166146b8575b614000811661469c575b6180008116614680575b620100008116614664575b620200008116614649575b62040000811661462e575b6208000016614615575b5f126145ee575b0160201c90565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff046145e7565b6b048a170391f7dc42444e8fa290910260801c906145e0565b6d2216e584f5fa1ea926041bedfe9890920260801c916145d6565b916e5d6af8dedb81196699c329225ee6040260801c916145cb565b916f09aa508b5b7a84e1c677de54f3e99bc90260801c916145c0565b916f31be135f97d08fd981231505542fcfa60260801c916145b5565b916f70d869a156d2a1b890bb3df62baf32f70260801c916145ab565b916fa9f746462d870fdf8a65dc1f90e061e50260801c916145a1565b916fd097f3bdfd2022b8845ad8f792aa58250260801c91614597565b916fe7159475a2c29b7443b29c7fa6e889d90260801c9161458d565b916ff3392b0822b70005940c7a398e4b70f30260801c91614583565b916ff987a7253ac413176f2b074cf7815e540260801c91614579565b916ffcbe86c7900a88aedcffc83b479aa3a40260801c9161456f565b916ffe5dee046a99a2a811c461f1969c30530260801c91614565565b916fff2ea16466c96a3843ec78b326b528610260801c9161455c565b916fff973b41fa98c081472e6896dfb254c00260801c91614553565b916fffcb9843d60f6159c9db58835c9266440260801c9161454a565b916fffe5caca7e10e4e61c3624eaa0941cd00260801c91614541565b916ffff2e50f5f656932ef12357cf3c7fdcc0260801c91614538565b916ffff97272373d413259a46990580e213a0260801c9161452f565b827f8b86327a000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b905f83600f0b125f146148855761487b925f036fffffffffffffffffffffffffffffffff1691615c1a565b5f81126132415790565b6148a1926fffffffffffffffffffffffffffffffff1691615bbf565b5f8112613241575f0390565b905f83600f0b125f146148d85761487b925f036fffffffffffffffffffffffffffffffff1691615d11565b6148a1926fffffffffffffffffffffffffffffffff1691615c5a565b906fffffffffffffffffffffffffffffffff90600f0b911601908160801c61491857565b6393dafdf15f526004601cfd5b9061492f91613fdb565b9015613b43576040815103613dbb576040015190565b6149689061495a8360801d8260801d0361384c565b92600f0b90600f0b0361384c565b6fffffffffffffffffffffffffffffffff169060801b1790565b73fffd8963efd1fc6a506488495d951d516396168273ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffd895d83011611614cc75777ffffffffffffffffffffffffffffffffffffffff000000008160201b16806fffffffffffffffffffffffffffffffff811160071b90811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c9060ff821160031b91821c92600f841160021b93841c94600160038711811b96871c1196171717171717179060808210155f14614cbd577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8182011c5b800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c800280607f1c8160ff1c1c80029081607f1c8260ff1c1c80029283607f1c8460ff1c1c80029485607f1c8660ff1c1c80029687607f1c8860ff1c1c80029889607f1c8a60ff1c1c80029a8b607f1c8c60ff1c1c80029c8d80607f1c9060ff1c1c800260cd1c6604000000000000169d60cc1c6608000000000000169c60cb1c6610000000000000169b60ca1c6620000000000000169a60c91c6640000000000000169960c81c6680000000000000169860c71c670100000000000000169760c61c670200000000000000169660c51c670400000000000000169560c41c670800000000000000169460c31c671000000000000000169360c21c672000000000000000169260c11c674000000000000000169160c01c67800000000000000016907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800160401b1717171717171717171717171717693627a301d71055774c85027ffffffffffffffffffffffffffffffffffd709b7e5480fba5a50fed5e62ffc556810160801d60020b906fdb2df09e81959a81455e260799a0632f0160801d60020b918282145f14614c7a5750905090565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff614cae846144de565b1611614cb8575090565b905090565b81607f031b614a92565b73ffffffffffffffffffffffffffffffffffffffff907f61487524000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b8115614d13570490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b9190915f604051614d5081612e99565b5f815260208101905f8252604081015f815260608201965f885260808301965f885260a08401975f895284998854906020850151159a6fffffffffffffffffffffffffffffffff60038c0154169384928d155f14615bb0575f610fff8660b81c169b5b60408a01518c52528473ffffffffffffffffffffffffffffffffffffffff168099528460a01c60020b90528c155f14615ba55760018c0154905b5252608084015162400000811615615b965762bfffff16614e0d81613755565b61ffff8816615b7057945b855f60408701511297620f424062ffffff83161480615b68575b615b4057604087015115615b2a5750508a615aca57606085019073ffffffffffffffffffffffffffffffffffffffff82511681811015615a935750505173ffffffffffffffffffffffffffffffffffffffff166401000276a38110615a6857505b60405160805260e060805101608051811067ffffffffffffffff82111761085d576040525f608051525f602060805101525f604060805101525f606060805101525f6080805101525f60a060805101525f60c060805101525b8a51158015615a2b575b6158de5773ffffffffffffffffffffffffffffffffffffffff60408c0151166080515260608b015160020b845160020b90815f818307129105038b155f146157855760ff8116918160020b60081d60010b5f5260058c0160205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60018086011b0190541691821592831593845f1461575057610330577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2761894817f07060605060205040602030205040301060502050303040105050304000000006f8421084210842108cc6318c6db6d54be60ff946fffffffffffffffffffffffffffffffff1060071b83811c67ffffffffffffffff1060061b1783811c63ffffffff1060051b1783811c61ffff1060041b1783811c861060031b1792831c1c601f161a1790031660020b900360020b0260020b5b905b15156040608051015260020b80602060805101521315615723575b620d89e86020608051015160020b1215615713575b8973ffffffffffffffffffffffffffffffffffffffff6150a36020608051015160020b6144de565b1680606060805101528c73ffffffffffffffffffffffffffffffffffffffff60408201511673ffffffffffffffffffffffffffffffffffffffff606089015116906fffffffffffffffffffffffffffffffff60a08401511692519262ffffff8b169473ffffffffffffffffffffffffffffffffffffffff60ff8815168583101885831802851816831015935f86125f146155435786620f4240039161514a83885f0361423c565b861561552457615169858760ff8d151686861018868618028618615c5a565b975b88821061544b5750508160ff73ffffffffffffffffffffffffffffffffffffffff9a15168183101891180218958691620f424082145f1461543c575050845b935b1561542e57916151bb92615c1a565b905b60c0608051015260a060805101526080805101521660408c015285155f146153f05760a060805101515f8112613241578b51038b5261520860808051015160c060805101519061309d565b5f81126132415760208c018051918203918213600116610dad57525b61ffff87166153bd575b6fffffffffffffffffffffffffffffffff60a08c0151168061539d575b5073ffffffffffffffffffffffffffffffffffffffff60408c01511673ffffffffffffffffffffffffffffffffffffffff606060805101511681145f1461535f5750604060805101516152b6575b60ff8a15166020608051015160020b0360020b60608c0152614eec565b8961533b576fffffffffffffffffffffffffffffffff61532260808d015160028c01545b6020608051015160020b60020b5f5260048d0160205260405f2091600183019081549003905560028201908154900390555460801d8d8d1561532d575b60a0015183166148f4565b1660a08c0152615299565b5f91909103600f0b90615317565b6fffffffffffffffffffffffffffffffff6153228c608060018d01549101516152da565b73ffffffffffffffffffffffffffffffffffffffff60805151168103615386575b50614eec565b61538f90614982565b60020b60608c01525f615380565b6153ad9060c060805101516140f9565b60808c019081510190525f61524b565b96620f424060808051015161ffff89169060c06080510151010204908160c060805101510360c06080510152019661522e565b60808051015160c06080510151015f8112613241578b51018b5260a060805101515f81126132415761542760208d01918251613bdf565b9052615224565b61543792615d11565b6151bb565b6154469187615db8565b6151aa565b97509850505050809450158115176155175773ffffffffffffffffffffffffffffffffffffffff9482156154b357615484908583615e57565b935b8480828486156154a45761549992615c5a565b8095015f03936151ac565b916154ae92615bbf565b615499565b6154ef906154ea908781116154f5576154e2906fffffffffffffffffffffffffffffffff88169060601b614d09565b87841661309d565b615eeb565b93615486565b615512906fffffffffffffffffffffffffffffffff881690614341565b6154e2565b634f2461b85f526004601cfd5b61553d8560ff8c15168585101885851802851888615bbf565b9761516b565b91969395949386156156f457615568888560ff84151686861018868618028618615c1a565b945b8581106155cf57509173ffffffffffffffffffffffffffffffffffffffff97918160ff6155bc96951516818310189118021880965b156155c157916155ae92615c5a565b809380620f42400391615db8565b6151bd565b6155ca92615bbf565b6155ae565b94505050508185158215176155175784156156ca5773ffffffffffffffffffffffffffffffffffffffff81116156715760601b6fffffffffffffffffffffffffffffffff86168082061515910401905b73ffffffffffffffffffffffffffffffffffffffff811695828711156156645773ffffffffffffffffffffffffffffffffffffffff6155bc93819803165b809661559f565b634323a5555f526004601cfd5b6fffffffffffffffffffffffffffffffff861661569c816c01000000000000000000000000846143f3565b918115614d13576c0100000000000000000000000090096156be575b9061561f565b600101806156b8575f80fd5b90856156ef6155bc9373ffffffffffffffffffffffffffffffffffffffff9884615de1565b61565d565b61570d8860ff8315168585101885851802851886615d11565b9461556a565b620d89e86020608051015261507b565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2761860206080510152615066565b50507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276189360020b900360020b0260020b615049565b600101908160020b908260ff16928260081d60010b5f5260058d016020527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600160ff60405f2093161b011990541691821592831593845f146158a557610330577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276189481600160ff931901167e1f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405601f6101e07f804040554300526644320000502061067405302602000010750620017611707760fc7fb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff860260f81c161b60f71c1692831c63d76453e004161a17031660020b0160020b0260020b5b9061504b565b505060ff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff276189481031660020b0160020b0260020b61589f565b90919996989397929550606087015160a01b76ffffff0000000000000000000000000000000000000000167fffffffffffffffffff000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff60408a0151169216171782556fffffffffffffffffffffffffffffffff60a0870151168091036159e6575b5081156159d757600260808601519101555b15146159ba576159a06040615994602085015161384c565b9201518351900361384c565b6fffffffffffffffffffffffffffffffff169060801b1793565b6040015181516159ca910361384c565b6159a0602083015161384c565b6001608086015191015561597c565b6fffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffff000000000000000000000000000000006003830154161760038201555f61596a565b5073ffffffffffffffffffffffffffffffffffffffff60408c01511673ffffffffffffffffffffffffffffffffffffffff60608601511614614ef6565b7f9e4d7cc7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60449250604051917f7c9c6e8f00000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b606085019073ffffffffffffffffffffffffffffffffffffffff82511681811115615a935750505173ffffffffffffffffffffffffffffffffffffffff1673fffd8963efd1fc6a506488495d951d5263988d26811015615a685750614e93565b9a509a50505050975050505050505f925f929190565b7f96206246000000000000000000000000000000000000000000000000000000005f5260045ffd5b508815614e32565b62ffffff168061ffff891602620f424080820615159104019061ffff8916010394614e18565b508060d01c62ffffff16614e0d565b60028c015490614ded565b5f610fff8660c41c169b614db3565b6fffffffffffffffffffffffffffffffff6c010000000000000000000000009173ffffffffffffffffffffffffffffffffffffffff80600195169116038060ff1d90810118931692615c1181856142c7565b93091515160190565b6fffffffffffffffffffffffffffffffff9073ffffffffffffffffffffffffffffffffffffffff806137169594169116038060ff1d9081011891166142c7565b9073ffffffffffffffffffffffffffffffffffffffff811673ffffffffffffffffffffffffffffffffffffffff831611615d0b575b73ffffffffffffffffffffffffffffffffffffffff8216928315615cff577bffffffffffffffffffffffffffffffff00000000000000000000000073ffffffffffffffffffffffffffffffffffffffff615cf3948185169403169160601b16615db8565b90808206151591040190565b62bfc9215f526004601cfd5b90615c8f565b73ffffffffffffffffffffffffffffffffffffffff821673ffffffffffffffffffffffffffffffffffffffff821611615db2575b73ffffffffffffffffffffffffffffffffffffffff8116918215615cff57613716937bffffffffffffffffffffffffffffffff00000000000000000000000073ffffffffffffffffffffffffffffffffffffffff615dad948185169403169160601b166143f3565b614d09565b90615d45565b929190615dc68282866143f3565b938215614d135709615dd457565b9060010190811561033057565b91908115615e52577bffffffffffffffffffffffffffffffff00000000000000000000000073ffffffffffffffffffffffffffffffffffffffff9160601b169216918282029183838311918404141615615e4557613716926154ea92820391615db8565b63f5c787f15f526004601cfd5b505090565b90918015615ee55773ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffff000000000000000000000000819460601b16921680820281615ea78483614d09565b14615ecd575b5090615ebc615ec19284614d09565b61309d565b80820615159104011690565b8301838110615ead579150615ee192615db8565b1690565b50905090565b9073ffffffffffffffffffffffffffffffffffffffff82169182036132415756fea264697066735822122040181355f523bacc25c104b20052f2a671653c5fa6d294b06a8201eb65cf014864736f6c634300081a0033000000000000000000000000000000000000000000000000000000000007a120", + "nonce": "0x1f", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x602996", + "logs": [ + { + "address": "0x39bf2eff94201cfaa471932655404f63315147a4", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000007024cc7e60d6560f0b5877da2bb921fcbf1f4375" + ], + "data": "0x", + "blockHash": "0x147cc919e6b58d6c31c809c2f64b4540fc11b48eb713a38d209e296d2ffb082d", + "blockNumber": "0xd423a5", + "transactionHash": "0xd029dbd1f19f32a5512ebf04817925f32ef1480312e50d36570c2cadf990b812", + "transactionIndex": "0x14", + "logIndex": "0x11", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000001000000000000000000000000000000008000020000000000000000000800000000000000000000200010000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xd029dbd1f19f32a5512ebf04817925f32ef1480312e50d36570c2cadf990b812", + "transactionIndex": "0x14", + "blockHash": "0x147cc919e6b58d6c31c809c2f64b4540fc11b48eb713a38d209e296d2ffb082d", + "blockNumber": "0xd423a5", + "gasUsed": "0x514256", + "effectiveGasPrice": "0x7c92b", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0x39bf2eff94201cfaa471932655404f63315147a4", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x8f94f87e88", + "l1GasPrice": "0xbc65cce1", + "l1GasUsed": "0x2b435" + } + ], + "libraries": [], + "pending": [], + "returns": { + "manager": { + "internal_type": "contract IPoolManager", + "value": "0x39BF2eFF94201cfAA471932655404F63315147a4" + } + }, + "timestamp": 1723573824, + "chain": 84532, + "commit": "d0faf02" +} \ No newline at end of file diff --git a/broadcast/02_PoolModifyLiquidityTest.s.sol/11155111/run-latest.json b/broadcast/02_PoolModifyLiquidityTest.s.sol/11155111/run-latest.json new file mode 100644 index 000000000..f5a85f3da --- /dev/null +++ b/broadcast/02_PoolModifyLiquidityTest.s.sol/11155111/run-latest.json @@ -0,0 +1,48 @@ +{ + "transactions": [ + { + "hash": "0x0e36efb3560fe4f336ed0a49c320c1d094ef0b371a690efd6556c93cc8f86fd9", + "transactionType": "CREATE", + "contractName": "PoolModifyLiquidityTest", + "contractAddress": "0x39bf2eff94201cfaa471932655404f63315147a4", + "function": null, + "arguments": [ + "0xc021A7Deb4a939fd7E661a0669faB5ac7Ba2D5d6" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x1c11c6", + "value": "0x0", + "input": "0x60a034608257601f61194f38819003918201601f19168301916001600160401b03831184841017608657808492602094604052833981010312608257516001600160a01b03811681036082576080526040516118b4908161009b82396080518181816104c2015281816106070152818161093a01528181610c2f015261120e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c80630a5b11e414610054578063481c6a751461004f5780635a6bcfda1461004a576391dd734614610045575f80fd5b6106eb565b6104e6565b610478565b6101807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e35761008836610221565b61009136610323565b90610124359067ffffffffffffffff82116100e3576020926100ba6100db933690600401610413565b61014435916100c883610459565b61016435936100d685610459565b6108df565b604051908152f35b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60a0810190811067ffffffffffffffff82111761013057604052565b6100e7565b6080810190811067ffffffffffffffff82111761013057604052565b60c0810190811067ffffffffffffffff82111761013057604052565b6060810190811067ffffffffffffffff82111761013057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761013057604052565b604051906101d960c083610189565b565b604051906101d9606083610189565b73ffffffffffffffffffffffffffffffffffffffff8116036100e357565b35906101d9826101ea565b35908160020b82036100e357565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60a09101126100e3576040519061025882610114565b81600435610265816101ea565b8152602435610273816101ea565b602082015260443562ffffff811681036100e35760408201526064358060020b81036100e35760608201526080608435916102ad836101ea565b0152565b91908260a09103126100e3576040516102c981610114565b809280356102d6816101ea565b825260208101356102e6816101ea565b6020830152604081013562ffffff811681036100e3576080918291604085015261031260608201610213565b60608501520135916102ad836101ea565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5c60809101126100e3576040519061035a82610135565b8160a4358060020b81036100e357815260c4358060020b81036100e357602082015260e4356040820152606061010435910152565b91908260809103126100e3576040516103a781610135565b60608082946103b581610213565b84526103c360208201610213565b6020850152604081013560408501520135910152565b67ffffffffffffffff811161013057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b81601f820112156100e35780359061042a826103d9565b926104386040519485610189565b828452602083830101116100e357815f926020809301838601378301015290565b801515036100e357565b35906101d982610459565b5f9103126100e357565b346100e3575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e357602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b6101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e35761051a36610221565b61052336610323565b90610124359167ffffffffffffffff83116100e3576105ba5f9261058e926105526105ed963690600401610413565b9061055b6101ca565b33815292602084015260408301526060820152831515608082015283151560a08201525b60405192839160208301610778565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610189565b604051809381927f48c89491000000000000000000000000000000000000000000000000000000008352600483016106d7565b03818373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165af190811561068f5760209161064d915f9161066d575b508280825183010191016108d0565b478061065d575b50604051908152f35b610667903361107b565b5f610654565b61068991503d805f833e6106818183610189565b810190610862565b5f61063e565b6108c5565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b9060206106e8928181520190610694565b90565b346100e35760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e35760043567ffffffffffffffff81116100e357366023820112156100e35780600401359067ffffffffffffffff82116100e35736602483830101116100e3576107749160246107689201610c26565b604051918291826106d7565b0390f35b6020815273ffffffffffffffffffffffffffffffffffffffff82511660208201526107f56020830151604083019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b61082b604083015160e083019060608091805160020b8452602081015160020b6020850152604081015160408501520151910152565b6101a060a061084a6060850151836101608601526101c0850190610694565b93608081015115156101808501520151151591015290565b6020818303126100e35780519067ffffffffffffffff82116100e3570181601f820112156100e357805190610896826103d9565b926108a46040519485610189565b828452602083830101116100e357815f9260208093018386015e8301015290565b6040513d5f823e3d90fd5b908160209103126100e3575190565b61057f610920956109175f9661058e966105ba966108fb6101ca565b3381529660208801526040870152606086015215156080850152565b151560a0830152565b03818373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165af1801561068f5761097d915f91610991575b50602080825183010191016108d0565b9047806109875750565b6101d9903361107b565b6109a591503d805f833e6106818183610189565b5f61096d565b156100e357565b6020818303126100e35780359067ffffffffffffffff82116100e357016101a0818303126100e357604051916109e783610151565b6109f082610208565b83526109ff81602084016102b1565b6020840152610a118160c0840161038f565b60408401526101408201359167ffffffffffffffff83116100e357610a3e61018092610a5b948301610413565b6060850152610a506101608201610463565b608085015201610463565b60a082015290565b91908260409103126100e3576020825192015190565b610b066106e89493610ad8836101409573ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051600290810b60a08501526020820151900b60c0840152604081015160e084015260600151610100830152565b816101208201520190610694565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b9190915f8382019384129112908015821691151617610b5c57565b610b14565b15610b6857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6c6971756964697479206368616e676520696e636f72726563740000000000006044820152fd5b15610bcd57565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52600160045260245ffd5b7f80000000000000000000000000000000000000000000000000000000000000008114610b5c575f0390565b610d1a90610c7a7f00000000000000000000000000000000000000000000000000000000000000009173ffffffffffffffffffffffffffffffffffffffff831694610c728633146109ab565b8101906109b2565b906020820190610c8c825160a0902090565b946040610cd8610cc2828701988951610ca6815160020b90565b6060610cb6602084015160020b90565b92015192309089611097565b516fffffffffffffffffffffffffffffffff1690565b9184519088515f60608901519385519a8b95869485937f5a6bcfda00000000000000000000000000000000000000000000000000000000855260048501610a79565b03925af193841561068f5761058e966106e8965f96611049575b506040610d6c610cc2610d49885160a0902090565b8451805160020b6060610d60602084015160020b90565b9201519230908b611097565b91610e36610db9610d93895173ffffffffffffffffffffffffffffffffffffffff90511690565b3090610db3885173ffffffffffffffffffffffffffffffffffffffff1690565b906111ea565b959150506fffffffffffffffffffffffffffffffff610e2b610e17610df760208d510173ffffffffffffffffffffffffffffffffffffffff90511690565b3090610db38b5173ffffffffffffffffffffffffffffffffffffffff1690565b9a9150508287875101519116600f0b610b41565b9116600f0b14610b61565b5101515f81121561100457505f81138015610ffb575b610e5590610bc6565b5f81128015610ff2575b610e699015610bc6565b5f8112610faf575b5f8312610f54575b5f8113610f03575b505f8213610ea2575b50505050604051928391602083019190602083019252565b610ec86020610efa95510173ffffffffffffffffffffffffffffffffffffffff90511690565b92610ef460a0610eec845173ffffffffffffffffffffffffffffffffffffffff1690565b930151151590565b936116b7565b5f808080610e8a565b845151610f4e919073ffffffffffffffffffffffffffffffffffffffff1690610f40845173ffffffffffffffffffffffffffffffffffffffff1690565b86610ef460a0870151151590565b5f610e81565b845160200151610faa9073ffffffffffffffffffffffffffffffffffffffff16835173ffffffffffffffffffffffffffffffffffffffff16610f9586610bfa565b9087610fa46080880151151590565b936112cd565b610e79565b845151610fed9073ffffffffffffffffffffffffffffffffffffffff16835173ffffffffffffffffffffffffffffffffffffffff16610f9584610bfa565b610e71565b505f8312610e5f565b505f8313610e4c565b5f1215610e69575f81128015611040575b61101e90610bc6565b5f81138015611037575b6110329015610bc6565b610e69565b505f8313611028565b505f8312611015565b61106c91965060403d604011611074575b6110648183610189565b810190610a63565b50945f610d34565b503d61105a565b5f80809381935af11561108a57565b63f4b3b1bc5f526004601cfd5b949390939291925f604080516110ac8161016d565b82815282602082015201526026526006526003525f52603a600c20905f6026526040516020810191825260066040820152604081526110ec606082610189565b51902060068101809111610b5c5760408051602081019384529081019190915273ffffffffffffffffffffffffffffffffffffffff925f9261117b92611135816060810161058e565b5190206040519485809481937f35fd631a000000000000000000000000000000000000000000000000000000008352600483016020600391939293604081019481520152565b0392165afa90811561068f575f916111d0575b5060208101519060606040820151910151906111c36111ab6101db565b6fffffffffffffffffffffffffffffffff9094168452565b6020830152604082015290565b6111e491503d805f833e6106818183610189565b5f61118e565b92906111f690846117e4565b9273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169061123982826117e4565b935f52602052602060405f206024604051809481937ff135baaa00000000000000000000000000000000000000000000000000000000835260048301525afa90811561068f575f91611289575090565b90506020813d6020116112b0575b816112a460209383610189565b810103126100e3575190565b3d9150611297565b908160209103126100e357516106e881610459565b93919291156113b35761131061131073ffffffffffffffffffffffffffffffffffffffff61132993169573ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b92803b156100e3576040517ff5298aca00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909316600484015260248301939093526044820152905f908290818381606481015b03925af1801561068f5761139f5750565b806113ad5f6101d993610189565b8061046e565b9173ffffffffffffffffffffffffffffffffffffffff841661145957506040517f6a256b2900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff93841660048201529260209284926024928492165af1801561068f5761142e5750565b61144f9060203d602011611452575b6114478183610189565b8101906108d0565b50565b503d61143d565b6040517fa584119400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301529293909216916020816024815f875af1801561068f5761169a575b5073ffffffffffffffffffffffffffffffffffffffff811630146115f3576040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290821660248201526044810192909252602082806064810103815f73ffffffffffffffffffffffffffffffffffffffff88165af190811561068f575f936020936115b5936115c6575b505b6040519485809481937f6a256b290000000000000000000000000000000000000000000000000000000083526004830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03925af1801561068f5761142e5750565b6115e590853d87116115ec575b6115dd8183610189565b8101906112b8565b505f611561565b503d6115d3565b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024810192909252602082806044810103815f73ffffffffffffffffffffffffffffffffffffffff88165af190811561068f575f936020936115b59361167d575b50611563565b61169390853d87116115ec576115dd8183610189565b505f611677565b6116b29060203d602011611452576114478183610189565b6114b7565b93919291156117635761131061131073ffffffffffffffffffffffffffffffffffffffff6116fa93169573ffffffffffffffffffffffffffffffffffffffff1690565b92803b156100e3576040517f156e29f600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909316600484015260248301939093526044820152905f9082908183816064810161138e565b91929173ffffffffffffffffffffffffffffffffffffffff16803b156100e3576040517f0b0d9c0900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff938416600482015293909216602484015260448301525f9082908183816064810161138e565b73ffffffffffffffffffffffffffffffffffffffff811661180457503190565b9073ffffffffffffffffffffffffffffffffffffffff60246020928260405195869485937f70a08231000000000000000000000000000000000000000000000000000000008552166004840152165afa90811561068f575f91611865575090565b6106e8915060203d60201161145257611447818361018956fea2646970667358221220f765e66a67f7100c995a43007098f6f6e1f2758bba46beb22c8e7f6dc282d06b64736f6c634300081a0033000000000000000000000000c021a7deb4a939fd7e661a0669fab5ac7ba2d5d6", + "nonce": "0x1f", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xc005d1", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x0e36efb3560fe4f336ed0a49c320c1d094ef0b371a690efd6556c93cc8f86fd9", + "transactionIndex": "0x5c", + "blockHash": "0x2dd72ae243790b5e7bc4c6ae30d65e8cecbb7b1b6ec57cb87937cf647eac76c7", + "blockNumber": "0x5bc1f6", + "gasUsed": "0x159785", + "effectiveGasPrice": "0x33d9b23fd", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0x39bf2eff94201cfaa471932655404f63315147a4" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1717170897, + "chain": 11155111, + "commit": "1bb670c8" +} \ No newline at end of file diff --git a/broadcast/02_PoolModifyLiquidityTest.s.sol/84532/run-latest.json b/broadcast/02_PoolModifyLiquidityTest.s.sol/84532/run-latest.json new file mode 100644 index 000000000..8f277ff44 --- /dev/null +++ b/broadcast/02_PoolModifyLiquidityTest.s.sol/84532/run-latest.json @@ -0,0 +1,59 @@ +{ + "transactions": [ + { + "hash": "0x8bbc19ae42ccc39686195a249d1933363e71a3fd64df6079c49c772f51279094", + "transactionType": "CREATE", + "contractName": "PoolModifyLiquidityTest", + "contractAddress": "0x841b5a0b3dbc473c8a057e2391014aa4c4751351", + "function": null, + "arguments": [ + "0x39BF2eFF94201cfAA471932655404F63315147a4" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x1e6f97", + "value": "0x0", + "input": "0x60a0604052348015600e575f80fd5b50604051611ba9380380611ba9833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f80fd5b81516001600160a01b0381168114605f575f80fd5b9392505050565b608051611adf6100ca5f395f81816079015281816101020152818161027f015281816102f301528181610324015281816104090152818161058d015281816105f001528181610679015281816106d7015281816108c201526109020152611adf5ff3fe60806040526004361061003e575f3560e01c80630a5b11e414610042578063481c6a75146100685780635a6bcfda146100c057806391dd7346146100d3575b5f80fd5b610055610050366004611406565b6100ff565b6040519081526020015b60405180910390f35b348015610073575f80fd5b5061009b7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161005f565b6100556100ce36600461148d565b61024f565b3480156100de575f80fd5b506100f26100ed3660046114eb565b610265565b60405161005f91906115a5565b5f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166348c894916040518060c001604052803373ffffffffffffffffffffffffffffffffffffffff168152602001898152602001888152602001878152602001861515815260200185151581525060405160200161019391906115be565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016101be91906115a5565b5f604051808303815f875af11580156101d9573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261021e91908101906116dd565b8060200190518101906102319190611752565b9050478015610245576102455f3383610729565b5095945050505050565b5f61025d8484845f806100ff565b949350505050565b60603373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146102a8575f80fd5b5f6102b583850185611769565b90505f61031d6102ca836020015160a0902090565b60408401518051602082015160609092015173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016939230929161082a565b505090505f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16635a6bcfda8460200151856040015186606001516040518463ffffffff1660e01b815260040161038b93929190611832565b60408051808303815f875af11580156103a6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103ca91906118f6565b5090505f6104336103e0856020015160a0902090565b60408601518051602082015160609092015173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016939230929161082a565b505090505f61044d85602001515f0151865f015130610881565b925050505f610468866020015160200151875f015130610881565b9250505082600f0b86604001516040015186600f0b6104879190611945565b146104f2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6c6971756964697479206368616e676520696e636f7272656374000000000000604482015260640160405180910390fd5b5f866040015160400151121561053a575f82138061050f57505f81135b61051b5761051b61196c565b5f82128061052857505f81125b156105355761053561196c565b61057d565b5f866040015160400151131561057d575f82128061055757505f81125b6105635761056361196c565b5f82138061057057505f81135b1561057d5761057d61196c565b5f8212156105e05785516105e0907f0000000000000000000000000000000000000000000000000000000000000000906105b685611999565b60808a015160208b01515173ffffffffffffffffffffffffffffffffffffffff1693929190610934565b5f811215610645578551610645907f00000000000000000000000000000000000000000000000000000000000000009061061984611999565b60808a01516020808c0151015173ffffffffffffffffffffffffffffffffffffffff1693929190610934565b5f8213156106a157855160a08701516020880151516106a19273ffffffffffffffffffffffffffffffffffffffff909116917f000000000000000000000000000000000000000000000000000000000000000091908690610cf2565b5f8113156106ff57855160a087015160208089015101516106ff9273ffffffffffffffffffffffffffffffffffffffff909116917f000000000000000000000000000000000000000000000000000000000000000091908590610cf2565b60408051602081018690520160405160208183030381529060405296505050505050505b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff8416610783575f805f8085875af190508061077e5761077e7f8549db590000000000000000000000000000000000000000000000000000000084610e36565b610824565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505f81525f60208201525f60408201525080610824576108247fb12c5f9c0000000000000000000000000000000000000000000000000000000085610e36565b50505050565b60408051602681018390526006810184905260038101859052858152603a600c8201205f92820183905260208201839052908290528190819061086e8a8a83610e69565b919c909b50909950975050505050505050565b5f80806108a473ffffffffffffffffffffffffffffffffffffffff871686610f50565b92506108e673ffffffffffffffffffffffffffffffffffffffff87167f0000000000000000000000000000000000000000000000000000000000000000610f50565b915061092973ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016858861101f565b905093509350939050565b8015610a15578373ffffffffffffffffffffffffffffffffffffffff1663f5298aca8461098a8873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1690565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604481018590526064015b5f604051808303815f87803b1580156109fa575f80fd5b505af1158015610a0c573d5f803e3d5ffd5b50505050610ceb565b73ffffffffffffffffffffffffffffffffffffffff8516610aa6578373ffffffffffffffffffffffffffffffffffffffff166311da60b4836040518263ffffffff1660e01b815260040160206040518083038185885af1158015610a7b573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610aa09190611752565b50610ceb565b6040517fa584119400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015285169063a5841194906024015f604051808303815f87803b158015610b0c575f80fd5b505af1158015610b1e573d5f803e3d5ffd5b5050505073ffffffffffffffffffffffffffffffffffffffff83163014610be3576040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301528581166024830152604482018490528616906323b872dd906064016020604051808303815f875af1158015610bb9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bdd91906119cf565b50610c7b565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301526024820184905286169063a9059cbb906044016020604051808303815f875af1158015610c55573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c7991906119cf565b505b8373ffffffffffffffffffffffffffffffffffffffff166311da60b46040518163ffffffff1660e01b81526004016020604051808303815f875af1158015610cc5573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ce99190611752565b505b5050505050565b80610d5a576040517f0b0d9c0900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152848116602483015260448201849052851690630b0d9c09906064016109e3565b8373ffffffffffffffffffffffffffffffffffffffff1663156e29f684610daa8873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1690565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604481018590526064015f604051808303815f87803b158015610e19575f80fd5b505af1158015610e2b573d5f803e3d5ffd5b505050505050505050565b3d60405183815282600482015260406024820152816044820152815f606483013e602080601f8401040260640191508181fd5b5f805f80610e7786866110dd565b6040517f35fd631a00000000000000000000000000000000000000000000000000000000815260048101829052600360248201529091505f9073ffffffffffffffffffffffffffffffffffffffff8916906335fd631a906044015f60405180830381865afa158015610eeb573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610f3091908101906119ea565b60208101516040820151606090920151909a919950975095505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff8316610f8a575073ffffffffffffffffffffffffffffffffffffffff811631610723565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301528416906370a0823190602401602060405180830381865afa158015610ff4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110189190611752565b9050610723565b5f8073ffffffffffffffffffffffffffffffffffffffff84165f5273ffffffffffffffffffffffffffffffffffffffff831660205260405f2090508473ffffffffffffffffffffffffffffffffffffffff1663f135baaa826040518263ffffffff1660e01b815260040161109591815260200190565b602060405180830381865afa1580156110b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110d49190611752565b95945050505050565b5f806110e88461112d565b90505f6110f6600683611a96565b6040805160208101879052908101829052909150606001604051602081830303815290604052805190602001209250505092915050565b6040515f9061114c908390600690602001918252602082015260400190565b604051602081830303815290604052805190602001209050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60405160c0810167ffffffffffffffff811182821017156111b9576111b9611169565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561120657611206611169565b604052919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461122f575f80fd5b50565b8035600281900b8114611243575f80fd5b919050565b5f60a08284031215611258575f80fd5b60405160a0810167ffffffffffffffff8111828210171561127b5761127b611169565b604052905080823561128c8161120e565b8152602083013561129c8161120e565b6020820152604083013562ffffff811681146112b6575f80fd5b60408201526112c760608401611232565b606082015260808301356112da8161120e565b6080919091015292915050565b5f608082840312156112f7575f80fd5b6040516080810167ffffffffffffffff8111828210171561131a5761131a611169565b60405290508061132983611232565b815261133760208401611232565b602082015260408301356040820152606083013560608201525092915050565b5f67ffffffffffffffff82111561137057611370611169565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f8301126113ab575f80fd5b81356113be6113b982611357565b6111bf565b8181528460208386010111156113d2575f80fd5b816020850160208301375f918101602001919091529392505050565b801515811461122f575f80fd5b8035611243816113ee565b5f805f805f610180868803121561141b575f80fd5b6114258787611248565b94506114348760a088016112e7565b935061012086013567ffffffffffffffff811115611450575f80fd5b61145c8882890161139c565b93505061014086013561146e816113ee565b915061016086013561147f816113ee565b809150509295509295909350565b5f805f61014084860312156114a0575f80fd5b6114aa8585611248565b92506114b98560a086016112e7565b915061012084013567ffffffffffffffff8111156114d5575f80fd5b6114e18682870161139c565b9150509250925092565b5f80602083850312156114fc575f80fd5b823567ffffffffffffffff811115611512575f80fd5b8301601f81018513611522575f80fd5b803567ffffffffffffffff811115611538575f80fd5b856020828401011115611549575f80fd5b6020919091019590945092505050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f6115b76020830184611559565b9392505050565b6020815273ffffffffffffffffffffffffffffffffffffffff82511660208201525f6020830151611667604084018273ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff602082015116602083015262ffffff6040820151166040830152606081015160020b606083015273ffffffffffffffffffffffffffffffffffffffff60808201511660808301525050565b506040838101518051600290810b60e08601526020820151900b610100850152908101516101208401526060908101516101408401528301516101a06101608401526116b76101c0840182611559565b90506080840151151561018084015260a084015115156101a08401528091505092915050565b5f602082840312156116ed575f80fd5b815167ffffffffffffffff811115611703575f80fd5b8201601f81018413611713575f80fd5b80516117216113b982611357565b818152856020838501011115611735575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b5f60208284031215611762575f80fd5b5051919050565b5f60208284031215611779575f80fd5b813567ffffffffffffffff81111561178f575f80fd5b82016101a081850312156117a1575f80fd5b6117a9611196565b81356117b48161120e565b81526117c38560208401611248565b60208201526117d58560c084016112e7565b604082015261014082013567ffffffffffffffff8111156117f4575f80fd5b6118008682850161139c565b60608301525061181361016083016113fb565b608082015261182561018083016113fb565b60a0820152949350505050565b6118b1818573ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff602082015116602083015262ffffff6040820151166040830152606081015160020b606083015273ffffffffffffffffffffffffffffffffffffffff60808201511660808301525050565b8251600290810b60a08301526020840151900b60c0820152604083015160e082015260608301516101008201526101406101208201525f6110d4610140830184611559565b5f8060408385031215611907575f80fd5b505080516020909101519092909150565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8082018281125f83128015821682158216171561196457611964611918565b505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52600160045260245ffd5b5f7f800000000000000000000000000000000000000000000000000000000000000082036119c9576119c9611918565b505f0390565b5f602082840312156119df575f80fd5b81516115b7816113ee565b5f602082840312156119fa575f80fd5b815167ffffffffffffffff811115611a10575f80fd5b8201601f81018413611a20575f80fd5b805167ffffffffffffffff811115611a3a57611a3a611169565b8060051b611a4a602082016111bf565b91825260208184018101929081019087841115611a65575f80fd5b6020850194505b83851015611a8b57845180835260209586019590935090910190611a6c565b979650505050505050565b808201808211156107235761072361191856fea2646970667358221220bef3a566d7840408aaf430c594584b8d9764401d185dd9f04a3ba61280252c7064736f6c634300081a003300000000000000000000000039bf2eff94201cfaa471932655404f63315147a4", + "nonce": "0x20", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x3ed71d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x8bbc19ae42ccc39686195a249d1933363e71a3fd64df6079c49c772f51279094", + "transactionIndex": "0x10", + "blockHash": "0x16bb0c9767b876af2854968d85668c9f2b497d353a2d442c99c61cbc4b81875c", + "blockNumber": "0xd423cd", + "gasUsed": "0x176988", + "effectiveGasPrice": "0x2b73", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0x841b5a0b3dbc473c8a057e2391014aa4c4751351", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x280f190c82", + "l1GasPrice": "0xb92ea636", + "l1GasUsed": "0xc47a" + } + ], + "libraries": [], + "pending": [], + "returns": { + "testModifyRouter": { + "internal_type": "contract PoolModifyLiquidityTest", + "value": "0x841B5A0b3DBc473c8A057E2391014aa4C4751351" + } + }, + "timestamp": 1723573904, + "chain": 84532, + "commit": "d0faf02" +} \ No newline at end of file diff --git a/broadcast/03_PoolSwapTest.s.sol/11155111/run-latest.json b/broadcast/03_PoolSwapTest.s.sol/11155111/run-latest.json new file mode 100644 index 000000000..57bafe848 --- /dev/null +++ b/broadcast/03_PoolSwapTest.s.sol/11155111/run-latest.json @@ -0,0 +1,48 @@ +{ + "transactions": [ + { + "hash": "0x74ca96c398e6f9b91fbf04982a07b79ea867746de5e1e4d3fdc3f11a5226e7b1", + "transactionType": "CREATE", + "contractName": "PoolSwapTest", + "contractAddress": "0x841b5a0b3dbc473c8a057e2391014aa4c4751351", + "function": null, + "arguments": [ + "0xc021A7Deb4a939fd7E661a0669faB5ac7Ba2D5d6" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x1e2b89", + "value": "0x0", + "input": "0x60a034607a57601f611b4738819003918201601f19168301916001600160401b03831184841017607e57808492602094604052833981010312607a57516001600160a01b0381168103607a57608052604051611ab49081610093823960805181818160ab01528181610d29015281816110a601526114ed0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c80632229d0b414610d4d578063481c6a7514610cdf576391dd73461461003a575f80fd5b34610c0c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610c0c5760043567ffffffffffffffff8111610c0c5736602382011215610c0c57806004013567ffffffffffffffff8111610c0c578101906024820191368311610c0c577f00000000000000000000000000000000000000000000000000000000000000009273ffffffffffffffffffffffffffffffffffffffff841692833303610c0c57602081840312610c0c5760248101359067ffffffffffffffff8211610c0c57019061018082840312610c0c576040519161012383611198565b61012f6024820161125a565b835260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08286030112610c0c576040519061016a826111fd565b6101766044820161127b565b82526101846064820161127b565b60208301526020840191825260a060247fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c838803010112610c0c57604051946101cc86611198565b6101d86084830161125a565b86526101e660a4830161125a565b602087015260c482013562ffffff81168103610c0c57604087015260e48201358060020b8103610c0c57606087015261010482013573ffffffffffffffffffffffffffffffffffffffff81168103610c0c577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedc8360609360249360808b015260408901998a5203010112610c0c5760405192610281846111e1565b61028e610124830161127b565b8452610144820135602085015261016482013573ffffffffffffffffffffffffffffffffffffffff81168103610c0c576040850152606085019384526101848201359167ffffffffffffffff8311610c0c576102ed92016024016112c2565b946080840195865261033073ffffffffffffffffffffffffffffffffffffffff8651511673ffffffffffffffffffffffffffffffffffffffff86511630916114c9565b91505061037273ffffffffffffffffffffffffffffffffffffffff60208851015116309073ffffffffffffffffffffffffffffffffffffffff885116906114c9565b92915050610c8157610c2357610447956020916104135f885161045b885195516040519c8d97889687957ff3cd914c000000000000000000000000000000000000000000000000000000008752600487019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051151560a4860152602081015160c48601526040015173ffffffffffffffffffffffffffffffffffffffff1660e4850152565b610120610104840152610124830190611308565b03925af1948515610c18575f95610be0575b506104aa73ffffffffffffffffffffffffffffffffffffffff85515116309073ffffffffffffffffffffffffffffffffffffffff865116906114c9565b979150506104ed73ffffffffffffffffffffffffffffffffffffffff60208751015116309073ffffffffffffffffffffffffffffffffffffffff875116906114c9565b855180519196935091501561092357505f602082510151125f146107cb575160200151871261072157610527878760801d600f0b1461135a565b5f831261069d57610582965b5f811261065b575b5f8412610606575b5f81136105be575b505f8313610586575b86866040519060208201526020815261056e604082611219565b604051918291602083526020830190611308565b0390f35b73ffffffffffffffffffffffffffffffffffffffff8060206105b497510151169451169151511515936118ee565b5f80808080610554565b6106009073ffffffffffffffffffffffffffffffffffffffff875151169073ffffffffffffffffffffffffffffffffffffffff875116848651511515936118ee565b5f61054b565b61065673ffffffffffffffffffffffffffffffffffffffff6020885101511673ffffffffffffffffffffffffffffffffffffffff87511661064687611470565b90856020885101511515936115af565b610543565b61069873ffffffffffffffffffffffffffffffffffffffff8751511673ffffffffffffffffffffffffffffffffffffffff87511661064684611470565b61053b565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f64656c7461416674657231206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f2030000000000000000000000000000000000000006064820152fd5b60a46040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604760248201527f64656c7461416674657230206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f20646174612e706172616d732e616d6f756e74537060648201527f65636966696564000000000000000000000000000000000000000000000000006084820152fd5b5f881361089f576020906107e38589600f0b146113e5565b51015183136107f55761058296610533565b60a46040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604460248201527f64656c7461416674657231206973206e6f74206c657373207468616e206f722060448201527f657175616c20746f20646174612e706172616d732e616d6f756e74537065636960648201527f66696564000000000000000000000000000000000000000000000000000000006084820152fd5b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f64656c7461416674657230206973206e6f74206c657373207468616e206f722060448201527f657175616c20746f207a65726f000000000000000000000000000000000000006064820152fd5b602001515f1315610a8557516020015183126109db576109478387600f0b146113e5565b5f87126109575761058296610533565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f64656c7461416674657230206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f2030000000000000000000000000000000000000006064820152fd5b60a46040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604760248201527f64656c7461416674657231206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f20646174612e706172616d732e616d6f756e74537060648201527f65636966696564000000000000000000000000000000000000000000000000006084820152fd5b5f8413610b5c57602090610aa0898960801d600f0b1461135a565b5101518713610ab25761058296610533565b60a46040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604460248201527f64656c7461416674657230206973206e6f74206c657373207468616e206f722060448201527f657175616c20746f20646174612e706172616d732e616d6f756e74537065636960648201527f66696564000000000000000000000000000000000000000000000000000000006084820152fd5b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f64656c7461416674657231206973206e6f74206c657373207468616e206f722060448201527f657175616c20746f2030000000000000000000000000000000000000000000006064820152fd5b9094506020813d602011610c10575b81610bfc60209383611219565b81010312610c0c5751935f61046d565b5f80fd5b3d9150610bef565b6040513d5f823e3d90fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f64656c74614265666f726531206973206e6f7420657175616c20746f203000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f64656c74614265666f726530206973206e6f7420657175616c20746f203000006044820152fd5b34610c0c575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610c0c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36016101608112610c0c5760a013610c0c57604051610d8c81611198565b60043573ffffffffffffffffffffffffffffffffffffffff81168103610c0c57815260243573ffffffffffffffffffffffffffffffffffffffff81168103610c0c57602082015260443562ffffff81168103610c0c5760408201526064358060020b8103610c0c57606082015260843573ffffffffffffffffffffffffffffffffffffffff81168103610c0c57608082015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5c360112610c0c57604051610e54816111e1565b60a4358015158103610c0c57815260c435602082015260e43573ffffffffffffffffffffffffffffffffffffffff81168103610c0c57604082015260407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefc360112610c0c5760405191610ec6836111fd565b610104358015158103610c0c578352610124358015158103610c0c5760208401526101443567ffffffffffffffff8111610c0c5761108c9361102573ffffffffffffffffffffffffffffffffffffffff936110105f96610fd8610f306110519736906004016112c2565b93602060405197610f4089611198565b3389528189019081526040890192835260608901948552608089019687526040519a8b9983808c0152511660408a0152518051151560608a01520151151560808801525160a087019073ffffffffffffffffffffffffffffffffffffffff6080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b518051151561014086015260208101516101608601526040015173ffffffffffffffffffffffffffffffffffffffff16610180850152565b516101806101a08401526101c0830190611308565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611219565b604051809381927f48c89491000000000000000000000000000000000000000000000000000000008352602060048401526024830190611308565b03818373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165af1908115610c18575f9161111e575b5060208151918180820193849201010312610c0c575147806110fe575b602082604051908152f35b5f80808093335af11561111157816110f3565b63f4b3b1bc5f526004601cfd5b90503d805f833e61112f8183611219565b810190602081830312610c0c5780519067ffffffffffffffff8211610c0c570181601f82011215610c0c5780519061116682611288565b926111746040519485611219565b82845260208383010111610c0c57815f9260208093018386015e83010152816110d6565b60a0810190811067ffffffffffffffff8211176111b457604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6060810190811067ffffffffffffffff8211176111b457604052565b6040810190811067ffffffffffffffff8211176111b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176111b457604052565b359073ffffffffffffffffffffffffffffffffffffffff82168203610c0c57565b35908115158203610c0c57565b67ffffffffffffffff81116111b457601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b81601f82011215610c0c578035906112d982611288565b926112e76040519485611219565b82845260208383010111610c0c57815f926020809301838601378301015290565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b90816020910312610c0c575190565b1561136157565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f64656c74612e616d6f756e74302829206973206e6f7420657175616c20746f2060448201527f64656c74614166746572300000000000000000000000000000000000000000006064820152fd5b156113ec57565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f64656c74612e616d6f756e74312829206973206e6f7420657175616c20746f2060448201527f64656c74614166746572310000000000000000000000000000000000000000006064820152fd5b7f8000000000000000000000000000000000000000000000000000000000000000811461149c575f0390565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b92906114d590846119ff565b9273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169061151882826119ff565b935f52602052602060405f206024604051809481937ff135baaa00000000000000000000000000000000000000000000000000000000835260048301525afa908115610c18575f91611568575090565b90506020813d60201161158f575b8161158360209383611219565b81010312610c0c575190565b3d9150611576565b90816020910312610c0c57518015158103610c0c5790565b9293156116515773ffffffffffffffffffffffffffffffffffffffff16803b15610c0c576040517ff5298aca00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529290911660248301526044820192909252905f908290818381606481015b03925af18015610c18576116455750565b5f61164f91611219565b565b73ffffffffffffffffffffffffffffffffffffffff9092169290836116f8575090602473ffffffffffffffffffffffffffffffffffffffff9360209360405195869485937f6a256b290000000000000000000000000000000000000000000000000000000085526004850152165af18015610c18576116cd5750565b6116ee9060203d6020116116f1575b6116e68183611219565b81019061134b565b50565b503d6116dc565b909173ffffffffffffffffffffffffffffffffffffffff1690604051907fa58411940000000000000000000000000000000000000000000000000000000082528460048301526020826024815f875af18015610c18576118a9575b73ffffffffffffffffffffffffffffffffffffffff1692905030831461183557604051927f23b872dd000000000000000000000000000000000000000000000000000000008452600484015281602484015260448301526020826064815f875af18015610c18575f93602093602492611808575b505b60405194859384927f6a256b2900000000000000000000000000000000000000000000000000000000845260048401525af18015610c18576116cd5750565b61182790853d871161182e575b61181f8183611219565b810190611597565b505f6117c7565b503d611815565b9150604051917fa9059cbb00000000000000000000000000000000000000000000000000000000835281600484015260248301526020826044815f875af18015610c18575f9360209360249261188c575b506117c9565b6118a290853d871161182e5761181f8183611219565b505f611886565b6020823d6020116118e6575b816118c260209383611219565b81010312610c0c5773ffffffffffffffffffffffffffffffffffffffff9150611753565b3d91506118b5565b9293156119775773ffffffffffffffffffffffffffffffffffffffff16803b15610c0c576040517f156e29f600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529290911660248301526044820192909252905f90829081838160648101611634565b90929073ffffffffffffffffffffffffffffffffffffffff16803b15610c0c575f928360649273ffffffffffffffffffffffffffffffffffffffff948560405198899788967f0b0d9c0900000000000000000000000000000000000000000000000000000000885216600487015216602485015260448401525af18015610c18576116455750565b73ffffffffffffffffffffffffffffffffffffffff1680611a1f57503190565b9073ffffffffffffffffffffffffffffffffffffffff602460209260405194859384927f70a082310000000000000000000000000000000000000000000000000000000084521660048301525afa908115610c18575f9161156857509056fea2646970667358221220179f25b6830b36c42ba8103208b1ae8a755e3dca8fab12de58749fb99b5108a764736f6c634300081a0033000000000000000000000000c021a7deb4a939fd7e661a0669fab5ac7ba2d5d6", + "nonce": "0x20", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xbfc25a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x74ca96c398e6f9b91fbf04982a07b79ea867746de5e1e4d3fdc3f11a5226e7b1", + "transactionIndex": "0x45", + "blockHash": "0xaf30ad54fa5f08c8e49d5e2d14ceb881f09593af54065dd9d6a033f0e05a5a62", + "blockNumber": "0x5bc1fc", + "gasUsed": "0x17352f", + "effectiveGasPrice": "0x37c29ef55", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0x841b5a0b3dbc473c8a057e2391014aa4c4751351" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1717170991, + "chain": 11155111, + "commit": "1bb670c8" +} \ No newline at end of file diff --git a/broadcast/03_PoolSwapTest.s.sol/84532/run-latest.json b/broadcast/03_PoolSwapTest.s.sol/84532/run-latest.json new file mode 100644 index 000000000..0dcd2b2f6 --- /dev/null +++ b/broadcast/03_PoolSwapTest.s.sol/84532/run-latest.json @@ -0,0 +1,59 @@ +{ + "transactions": [ + { + "hash": "0x7bebfebe929ae028733b2a61af5b33e35153db820916dee5023fb9415ab2a790", + "transactionType": "CREATE", + "contractName": "PoolSwapTest", + "contractAddress": "0xff34e285f8ed393e366046153e3c16484a4dd674", + "function": null, + "arguments": [ + "0x39BF2eFF94201cfAA471932655404F63315147a4" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x222648", + "value": "0x0", + "input": "0x60a0604052348015600e575f80fd5b50604051611f19380380611f19833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f80fd5b81516001600160a01b0381168114605f575f80fd5b9392505050565b608051611e5e6100bb5f395f8181606e0152818160e4015281816102400152818161038601528181610c4c01528181610cb201528181610d4901528181610daa01528181610f3e0152610f7e0152611e5e5ff3fe608060405260043610610033575f3560e01c80632229d0b414610037578063481c6a751461005d57806391dd7346146100b5575b5f80fd5b61004a61004536600461193f565b6100e1565b6040519081526020015b60405180910390f35b348015610068575f80fd5b506100907f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610054565b3480156100c0575f80fd5b506100d46100cf3660046119b0565b610226565b6040516100549190611a6a565b5f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166348c894916040518060a001604052803373ffffffffffffffffffffffffffffffffffffffff1681526020018681526020018881526020018781526020018581525060405160200161016b9190611a83565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016101969190611a6a565b5f604051808303815f875af11580156101b1573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526101f69190810190611ba4565b8060200190518101906102099190611c19565b905047801561021d5761021d5f3383610dfc565b50949350505050565b60603373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610269575f80fd5b5f61027683850185611c30565b90505f61028e82604001515f0151835f015130610efd565b925050505f6102a9836040015160200151845f015130610efd565b92505050815f1461031b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f64656c74614265666f726530206973206e6f7420657175616c20746f2030000060448201526064015b60405180910390fd5b8015610383576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f64656c74614265666f726531206973206e6f7420657175616c20746f203000006044820152606401610312565b5f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f3cd914c8560400151866060015187608001516040518463ffffffff1660e01b81526004016103ed93929190611ce8565b6020604051808303815f875af1158015610409573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061042d9190611c19565b90505f61044585604001515f0151865f015130610efd565b925050505f610460866040015160200151875f015130610efd565b9250505085606001515f015115610859575f866060015160200151121561066f5785606001516020015182121561053f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604760248201527f64656c7461416674657230206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f20646174612e706172616d732e616d6f756e74537060648201527f6563696669656400000000000000000000000000000000000000000000000000608482015260a401610312565b8161054a8460801d90565b600f0b146105da576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f64656c74612e616d6f756e74302829206973206e6f7420657175616c20746f2060448201527f64656c74614166746572300000000000000000000000000000000000000000006064820152608401610312565b5f81121561066a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f64656c7461416674657231206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f2030000000000000000000000000000000000000006064820152608401610312565b610c3c565b5f8213156106ff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f64656c7461416674657230206973206e6f74206c657373207468616e206f722060448201527f657175616c20746f207a65726f000000000000000000000000000000000000006064820152608401610312565b8061070a84600f0b90565b600f0b1461079a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f64656c74612e616d6f756e74312829206973206e6f7420657175616c20746f2060448201527f64656c74614166746572310000000000000000000000000000000000000000006064820152608401610312565b85606001516020015181131561066a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526044602482018190527f64656c7461416674657231206973206e6f74206c657373207468616e206f7220908201527f657175616c20746f20646174612e706172616d732e616d6f756e74537065636960648201527f6669656400000000000000000000000000000000000000000000000000000000608482015260a401610312565b5f8660600151602001511215610a5257856060015160200151811215610927576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604760248201527f64656c7461416674657231206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f20646174612e706172616d732e616d6f756e74537060648201527f6563696669656400000000000000000000000000000000000000000000000000608482015260a401610312565b8061093284600f0b90565b600f0b146109c2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f64656c74612e616d6f756e74312829206973206e6f7420657175616c20746f2060448201527f64656c74614166746572310000000000000000000000000000000000000000006064820152608401610312565b5f82121561066a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f64656c7461416674657230206973206e6f742067726561746572207468616e2060448201527f6f7220657175616c20746f2030000000000000000000000000000000000000006064820152608401610312565b5f811315610ae2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f64656c7461416674657231206973206e6f74206c657373207468616e206f722060448201527f657175616c20746f2030000000000000000000000000000000000000000000006064820152608401610312565b81610aed8460801d90565b600f0b14610b7d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f64656c74612e616d6f756e74302829206973206e6f7420657175616c20746f2060448201527f64656c74614166746572300000000000000000000000000000000000000000006064820152608401610312565b856060015160200151821315610c3c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526044602482018190527f64656c7461416674657230206973206e6f74206c657373207468616e206f7220908201527f657175616c20746f20646174612e706172616d732e616d6f756e74537065636960648201527f6669656400000000000000000000000000000000000000000000000000000000608482015260a401610312565b5f821215610ca2578551610ca2907f000000000000000000000000000000000000000000000000000000000000000090610c7585611db2565b6020808b0151015160408b01515173ffffffffffffffffffffffffffffffffffffffff1693929190610fb0565b5f811215610d14578551610d14907f000000000000000000000000000000000000000000000000000000000000000090610cdb84611db2565b8960200151602001518a604001516020015173ffffffffffffffffffffffffffffffffffffffff16610fb090949392919063ffffffff16565b5f821315610d71578551602087015151604088015151610d719273ffffffffffffffffffffffffffffffffffffffff909116917f00000000000000000000000000000000000000000000000000000000000000009190869061136e565b5f811315610dd257855160208088015151604089015190910151610dd29273ffffffffffffffffffffffffffffffffffffffff909116917f00000000000000000000000000000000000000000000000000000000000000009190859061136e565b60408051602081018590520160405160208183030381529060405296505050505050505b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff8416610e56575f805f8085875af1905080610e5157610e517f8549db5900000000000000000000000000000000000000000000000000000000846114b2565b610ef7565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505f81525f60208201525f60408201525080610ef757610ef77fb12c5f9c00000000000000000000000000000000000000000000000000000000856114b2565b50505050565b5f8080610f2073ffffffffffffffffffffffffffffffffffffffff8716866114e5565b9250610f6273ffffffffffffffffffffffffffffffffffffffff87167f00000000000000000000000000000000000000000000000000000000000000006114e5565b9150610fa573ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001685886115b4565b905093509350939050565b8015611091578373ffffffffffffffffffffffffffffffffffffffff1663f5298aca846110068873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1690565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604481018590526064015b5f604051808303815f87803b158015611076575f80fd5b505af1158015611088573d5f803e3d5ffd5b50505050611367565b73ffffffffffffffffffffffffffffffffffffffff8516611122578373ffffffffffffffffffffffffffffffffffffffff166311da60b4836040518263ffffffff1660e01b815260040160206040518083038185885af11580156110f7573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061111c9190611c19565b50611367565b6040517fa584119400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015285169063a5841194906024015f604051808303815f87803b158015611188575f80fd5b505af115801561119a573d5f803e3d5ffd5b5050505073ffffffffffffffffffffffffffffffffffffffff8316301461125f576040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301528581166024830152604482018490528616906323b872dd906064016020604051808303815f875af1158015611235573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112599190611e0d565b506112f7565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301526024820184905286169063a9059cbb906044016020604051808303815f875af11580156112d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112f59190611e0d565b505b8373ffffffffffffffffffffffffffffffffffffffff166311da60b46040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611341573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113659190611c19565b505b5050505050565b806113d6576040517f0b0d9c0900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152848116602483015260448201849052851690630b0d9c099060640161105f565b8373ffffffffffffffffffffffffffffffffffffffff1663156e29f6846114268873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1690565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604481018590526064015f604051808303815f87803b158015611495575f80fd5b505af11580156114a7573d5f803e3d5ffd5b505050505050505050565b3d60405183815282600482015260406024820152816044820152815f606483013e602080601f8401040260640191508181fd5b5f73ffffffffffffffffffffffffffffffffffffffff831661151f575073ffffffffffffffffffffffffffffffffffffffff811631610df6565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301528416906370a0823190602401602060405180830381865afa158015611589573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115ad9190611c19565b9050610df6565b5f8073ffffffffffffffffffffffffffffffffffffffff84165f5273ffffffffffffffffffffffffffffffffffffffff831660205260405f2090508473ffffffffffffffffffffffffffffffffffffffff1663f135baaa826040518263ffffffff1660e01b815260040161162a91815260200190565b602060405180830381865afa158015611645573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116699190611c19565b95945050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60405160a0810167ffffffffffffffff811182821017156116c2576116c2611672565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561170f5761170f611672565b604052919050565b73ffffffffffffffffffffffffffffffffffffffff81168114611738575f80fd5b50565b803561174681611717565b919050565b5f60a0828403121561175b575f80fd5b61176361169f565b9050813561177081611717565b8152602082013561178081611717565b6020820152604082013562ffffff8116811461179a575f80fd5b60408201526060820135600281900b81146117b3575f80fd5b60608201526117c46080830161173b565b608082015292915050565b8015158114611738575f80fd5b5f606082840312156117ec575f80fd5b6040516060810167ffffffffffffffff8111828210171561180f5761180f611672565b6040529050808235611820816117cf565b815260208381013590820152604083013561183a81611717565b6040919091015292915050565b5f60408284031215611857575f80fd5b6040805190810167ffffffffffffffff8111828210171561187a5761187a611672565b604052905080823561188b816117cf565b8152602083013561189b816117cf565b6020919091015292915050565b5f67ffffffffffffffff8211156118c1576118c1611672565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f8301126118fc575f80fd5b813561190f61190a826118a8565b6116c8565b818152846020838601011115611923575f80fd5b816020850160208301375f918101602001919091529392505050565b5f805f806101608587031215611953575f80fd5b61195d868661174b565b935061196c8660a087016117dc565b925061197c866101008701611847565b915061014085013567ffffffffffffffff811115611998575f80fd5b6119a4878288016118ed565b91505092959194509250565b5f80602083850312156119c1575f80fd5b823567ffffffffffffffff8111156119d7575f80fd5b8301601f810185136119e7575f80fd5b803567ffffffffffffffff8111156119fd575f80fd5b856020828401011115611a0e575f80fd5b6020919091019590945092505050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f611a7c6020830184611a1e565b9392505050565b6020815273ffffffffffffffffffffffffffffffffffffffff82511660208201525f6020830151805115156040840152602081015115156060840152506040830151611b47608084018273ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff602082015116602083015262ffffff6040820151166040830152606081015160020b606083015273ffffffffffffffffffffffffffffffffffffffff60808201511660808301525050565b5060608301518051151561012084015260208101516101408401526040015173ffffffffffffffffffffffffffffffffffffffff16610160830152608083015161018080840152611b9c6101a0840182611a1e565b949350505050565b5f60208284031215611bb4575f80fd5b815167ffffffffffffffff811115611bca575f80fd5b8201601f81018413611bda575f80fd5b8051611be861190a826118a8565b818152856020838501011115611bfc575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b5f60208284031215611c29575f80fd5b5051919050565b5f60208284031215611c40575f80fd5b813567ffffffffffffffff811115611c56575f80fd5b82016101808185031215611c68575f80fd5b611c7061169f565b8135611c7b81611717565b8152611c8a8560208401611847565b6020820152611c9c856060840161174b565b6040820152611caf8561010084016117dc565b606082015261016082013567ffffffffffffffff811115611cce575f80fd5b611cda868285016118ed565b608083015250949350505050565b611d67818573ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff602082015116602083015262ffffff6040820151166040830152606081015160020b606083015273ffffffffffffffffffffffffffffffffffffffff60808201511660808301525050565b8251151560a0820152602083015160c0820152604083015173ffffffffffffffffffffffffffffffffffffffff1660e08201526101206101008201525f611669610120830184611a1e565b5f7f80000000000000000000000000000000000000000000000000000000000000008203611e07577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b505f0390565b5f60208284031215611e1d575f80fd5b8151611a7c816117cf56fea264697066735822122098619b0c057e47edcbae987b35164fda2896bebf591be5f029988b67193a93d364736f6c634300081a003300000000000000000000000039bf2eff94201cfaa471932655404f63315147a4", + "nonce": "0x21", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x404a67", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x7bebfebe929ae028733b2a61af5b33e35153db820916dee5023fb9415ab2a790", + "transactionIndex": "0xc", + "blockHash": "0x86051f7d526eed731a30eb551256e16de113d3f8b13bd87206d1feb6f3b2b879", + "blockNumber": "0xd423e5", + "gasUsed": "0x1a44d5", + "effectiveGasPrice": "0x2d2d", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0xff34e285f8ed393e366046153e3c16484a4dd674", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x2754749673", + "l1GasPrice": "0xc20ca7c9", + "l1GasUsed": "0xb816" + } + ], + "libraries": [], + "pending": [], + "returns": { + "testSwapRouter": { + "internal_type": "contract PoolSwapTest", + "value": "0xFf34e285F8ED393E366046153e3C16484A4dD674" + } + }, + "timestamp": 1723573950, + "chain": 84532, + "commit": "d0faf02" +} \ No newline at end of file diff --git a/broadcast/DeployPosm.s.sol/84532/run-latest.json b/broadcast/DeployPosm.s.sol/84532/run-latest.json new file mode 100644 index 000000000..d96c8c6e1 --- /dev/null +++ b/broadcast/DeployPosm.s.sol/84532/run-latest.json @@ -0,0 +1,61 @@ +{ + "transactions": [ + { + "hash": "0x6868534dcf11b1774abdd69602eee0fc3b3d77b9f5e13437dedf8fbb145dea2c", + "transactionType": "CREATE2", + "contractName": "PositionManager", + "contractAddress": "0xa2f16f0bb5dea7c9a6675ec88193471dee805e6e", + "function": null, + "arguments": [ + "0x39BF2eFF94201cfAA471932655404F63315147a4", + "0x000000000022D473030F116dDEE9F6B43aC78BA3" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x65e448", + "value": "0x0", + "input": "0x03000000000000000000000000000000000000000000000000000000000000006101206040526001600855348015610015575f80fd5b5060405161591738038061591783398101604081905261003491610161565b808280806040518060400160405280601881526020017f556e697377617020563420506f736974696f6e73204e465400000000000000008152506040518060400160405280600b81526020016a554e492d56342d504f534d60a81b815250818282815f90816100a39190610231565b5060016100b08282610231565b50508151602083012060c052504660a05261012460c051604080517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a8666020820152908101919091524660608201523060808201525f9060a00160405160208183030381529060405280519060200120905090565b6080525050506001600160a01b0390811660e0529290921661010052506102eb92505050565b6001600160a01b038116811461015e575f80fd5b50565b5f8060408385031215610172575f80fd5b825161017d8161014a565b602084015190925061018e8161014a565b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c908216806101c157607f821691505b6020821081036101df57634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561022c57805f5260205f20601f840160051c8101602085101561020a5750805b601f840160051c820191505b81811015610229575f8155600101610216565b50505b505050565b81516001600160401b0381111561024a5761024a610199565b61025e8161025884546101ad565b846101e5565b6020601f821160018114610290575f83156102795750848201515b5f19600385901b1c1916600184901b178455610229565b5f84815260208120601f198516915b828110156102bf578785015182556020948501946001909201910161029f565b50848210156102dc57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b60805160a05160c05160e051610100516155716103a65f395f81816103250152818161075201528181610b10015261403c01525f81816106a701528181610d2901528181610fa6015281816117de015281816127ab0152818161334b0152818161341e0152818161348201528181613571015281816135e0015281816136b3015281816137a901528181613d9701528181613e3701528181613f9a015261400401525f610bb801525f610b6401525f610c1201526155715ff3fe60806040526004361061020e575f3560e01c8063502e1a161161011757806395d89b41116100ac578063c87b56dd1161007c578063dc4c90d311610062578063dc4c90d314610696578063dd46508f146106c9578063e985e9c5146106dc575f80fd5b8063c87b56dd14610611578063d737d0c714610663575f80fd5b806395d89b411461059f578063a22cb465146105b3578063ac9650d8146105d2578063b88d4fde146105f2575f80fd5b8063645add16116100e7578063645add161461053957806370a082311461054c57806375794a3c1461056b57806391dd734614610580575f80fd5b8063502e1a16146104b257806357b22ebf146104e85780636352211e14610507578063635ba6de14610526575f80fd5b806323b872dd116101a75780633aea60f01161017757806342842e0e1161015d57806342842e0e1461044057806345074cef1461045f5780634afe393c1461049f575f80fd5b80633aea60f0146103fb5780633b1fda971461040e575f80fd5b806323b872dd146103885780632b67b570146103a75780633355c213146103ba5780633644e515146103d9575f80fd5b8063095ea7b3116101e2578063095ea7b3146102e25780630f5730f11461030157806312261ee71461031457806316a2413114610347575f80fd5b80622a3e3a1461021257806301ffc9a71461022757806306fdde031461025b578063081812fc1461027c575b5f80fd5b6102256102203660046143bd565b610715565b005b348015610232575f80fd5b50610246610241366004614467565b6107c0565b60405190151581526020015b60405180910390f35b348015610266575f80fd5b5061026f6108a4565b60405161025291906144ce565b348015610287575f80fd5b506102bd6102963660046144e0565b60046020525f908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610252565b3480156102ed575f80fd5b506102256102fc3660046144f7565b61092f565b61022561030f366004614521565b6109da565b34801561031f575f80fd5b506102bd7f000000000000000000000000000000000000000000000000000000000000000081565b348015610352575f80fd5b506102bd6103613660046144e0565b60076020525f908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b348015610393575f80fd5b506102256103a236600461458f565b610aa7565b6102256103b53660046145cd565b610ad3565b3480156103c5575f80fd5b506102466103d43660046144e0565b610b4b565b3480156103e4575f80fd5b506103ed610b61565b604051908152602001610252565b61022561040936600461464d565b610c34565b348015610419575f80fd5b5061042d6104283660046146d4565b610cea565b60405160029190910b8152602001610252565b34801561044b575f80fd5b5061022561045a36600461458f565b610dad565b34801561046a575f80fd5b5061047e610479366004614738565b610f13565b6040516fffffffffffffffffffffffffffffffff9091168152602001610252565b6102256104ad3660046147a5565b610fd5565b3480156104bd575f80fd5b506103ed6104cc3660046144f7565b600660209081525f928352604080842090915290825290205481565b3480156104f3575f80fd5b506103ed6105023660046144e0565b611068565b348015610512575f80fd5b506102bd6105213660046144e0565b61109d565b610225610534366004614805565b61112d565b610225610547366004614875565b611491565b348015610557575f80fd5b506103ed6105663660046148b6565b61171e565b348015610576575f80fd5b506103ed60085481565b34801561058b575f80fd5b5061026f61059a3660046148d1565b6117c4565b3480156105aa575f80fd5b5061026f611846565b3480156105be575f80fd5b506102256105cd366004614910565b611853565b6105e56105e036600461493a565b611862565b604051610252919061496d565b3480156105fd575f80fd5b5061022561060c3660046149ee565b611970565b34801561061c575f80fd5b5061026f61062b3660046144e0565b5060408051808201909152601381527f68747470733a2f2f6578616d706c652e636f6d00000000000000000000000000602082015290565b34801561066e575f80fd5b507f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c6102bd565b3480156106a1575f80fd5b506102bd7f000000000000000000000000000000000000000000000000000000000000000081565b6102256106d7366004614a3f565b611ac7565b3480156106e7575f80fd5b506102466106f6366004614a87565b600560209081525f928352604080842090915290825290205460ff1681565b6040517f2a2d80d100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690632a2d80d19061078d908790879087908790600401614b9c565b5f604051808303815f87803b1580156107a4575f80fd5b505af11580156107b6573d5f803e3d5ffd5b5050505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316148061085257507f80ac58cd000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b8061089e57507f5b5e139f000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b5f80546108b090614caf565b80601f01602080910402602001604051908101604052809291908181526020018280546108dc90614caf565b80156109275780601f106108fe57610100808354040283529160200191610927565b820191905f5260205f20905b81548152906001019060200180831161090a57829003601f168201915b505050505081565b5f8181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16338114801590610993575073ffffffffffffffffffffffffffffffffffffffff81165f90815260056020908152604080832033845290915290205460ff16155b156109ca576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109d5818484611b8e565b505050565b8380421115610a15576040517f5a9165ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f610a1f8761109d565b9050610a2b8189611c0e565b604080517f49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad8152602081018a9052908101889052606081018690526080810187905260a09020610a87610a7d82611c73565b8690869085611cbc565b610a918287611ff5565b610a9c828a8a611b8e565b505050505050505050565b610ab2838383612082565b5f8181526009602052604090205460ff1c156109d5576109d5818484612345565b6040517f2b67b57000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690632b67b5709061078d908790879087908790600401614cfa565b5f8181526009602052604081205460ff1c61089e565b5f7f00000000000000000000000000000000000000000000000000000000000000004614610c0f57610c0a604080517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86660208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091524660608201523060808201525f9060a00160405160208183030381529060405280519060200120905090565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b8380421115610c6f576040517f5a9165ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c798888611c0e565b604080517f6673cb397ee2a50b6b8401653d3638b4ac8b3db9c28aa6870ffceb7574ec2f76815260208101899052908101879052606081018590526080810186905260a09020610cd5610ccb82611c73565b859085908c611cbc565b610cdf8986611ff5565b610a9c8989896123e8565b6040517f695c5bf50000000000000000000000000000000000000000000000000000000081525f9073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063695c5bf590610d64908890889088908890600401614e3e565b6020604051808303815f875af1158015610d80573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da49190614e7a565b95945050505050565b610db8838383610aa7565b73ffffffffffffffffffffffffffffffffffffffff82163b1580610ea857506040517f150b7a020000000000000000000000000000000000000000000000000000000080825233600483015273ffffffffffffffffffffffffffffffffffffffff858116602484015260448301849052608060648401525f608484015290919084169063150b7a029060a4016020604051808303815f875af1158015610e60573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e849190614e95565b7fffffffff0000000000000000000000000000000000000000000000000000000016145b6109d5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f554e534146455f524543495049454e540000000000000000000000000000000060448201526064015b60405180910390fd5b5f80610f7130610f2960c0860160a08701614eb0565b610f3960e0870160c08801614eb0565b60408051602681018a905260068101929092526003820192909252918252603a600c8301205f91830182905260208301829052915290565b9050610fcd610f8f610f8836869003860186614fdf565b60a0902090565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016908361247f565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c1615611044576040517f6f5ffb7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61104d3361251c565b61105984848484612542565b6110625f61251c565b50505050565b5f818152600960205260408120547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1661089e565b5f8181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1680611128576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f4e4f545f4d494e544544000000000000000000000000000000000000000000006044820152606401610f0a565b919050565b338561113982826125db565b611187576040517f0ca968d800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610f0a565b86866111ec816040805160c0830135603482015260a083013560318201526080830135602e8201526060830135601a8201528282013560178201526020808401356014830152923581526048600c8201205f9282018390529281018290525260011c90565b5f838152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1614611256576040517fa640d5e000000000000000000000000000000000000000000000000000000000815260048101839052602401610f0a565b5f89815260096020908152604080832080547f8000000000000000000000000000000000000000000000000000000000000000179055600790915290205473ffffffffffffffffffffffffffffffffffffffff1680156112fa576040517f73a4164900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610f0a565b5f8a81526007602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8c1617905551611405908a907f140e8f140000000000000000000000000000000000000000000000000000000090611383908f908f908e908e90602401615035565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612696565b905080611436576114367ff0aa7ed4000000000000000000000000000000000000000000000000000000008a6126aa565b604080518c815273ffffffffffffffffffffffffffffffffffffffff8b1660208201527f5db0e562b58e88ae25b795493b5a9c538bb02bd38430aa3194dbf8c68f619f54910160405180910390a15050505050505050505050565b338461149d82826125db565b6114eb576040517f0ca968d800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610f0a565b8585611550816040805160c0830135603482015260a083013560318201526080830135602e8201526060830135601a8201528282013560178201526020808401356014830152923581526048600c8201205f9282018390529281018290525260011c90565b5f838152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16146115ba576040517fa640d5e000000000000000000000000000000000000000000000000000000000815260048101839052602401610f0a565b5f88815260096020908152604080832080547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff169055600790915281205473ffffffffffffffffffffffffffffffffffffffff169061161a4560646126dd565b90508173ffffffffffffffffffffffffffffffffffffffff1663a774548d828c8c8c8c6040518663ffffffff1660e01b815260040161165c9493929190615035565b5f604051808303815f88803b158015611673575f80fd5b5087f193505050508015611685575060015b505f8a8152600760205260409081902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055517fcf0a5b88aeaa4455f33d4a50e7ee5e61255edaad94b95183268b86c5f41f4eff9061170a908c90859091825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b60405180910390a150505050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff821661179c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f5a45524f5f4144445245535300000000000000000000000000000000000000006044820152606401610f0a565b5073ffffffffffffffffffffffffffffffffffffffff165f9081526003602052604090205490565b60603373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614611835576040517fae18210a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61183f8383612731565b9392505050565b600180546108b090614caf565b61185e3383836123e8565b5050565b60608167ffffffffffffffff81111561187d5761187d614ecb565b6040519080825280602002602001820160405280156118b057816020015b606081526020019060019003908161189b5790505b5090505f5b82811015611969575f80308686858181106118d2576118d261505e565b90506020028101906118e4919061508b565b6040516118f29291906150ec565b5f60405180830381855af49150503d805f811461192a576040519150601f19603f3d011682016040523d82523d5f602084013e61192f565b606091505b50915091508161194157805160208201fd5b808484815181106119545761195461505e565b602090810291909101015250506001016118b5565b5092915050565b61197b858585610aa7565b73ffffffffffffffffffffffffffffffffffffffff84163b1580611a5a57506040517f150b7a02000000000000000000000000000000000000000000000000000000008082529073ffffffffffffffffffffffffffffffffffffffff86169063150b7a02906119f69033908a908990899089906004016150fb565b6020604051808303815f875af1158015611a12573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a369190614e95565b7fffffffff0000000000000000000000000000000000000000000000000000000016145b611ac0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f554e534146455f524543495049454e54000000000000000000000000000000006044820152606401610f0a565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c1615611b36576040517f6f5ffb7e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b3f3361251c565b8080421115611b7a576040517f70f65caa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b84848461276e565b506109d55f61251c565b5f8181526004602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361185e576040517f80e05c0000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80611c7d610b61565b6040517f190100000000000000000000000000000000000000000000000000000000000081526002810191909152602281019390935250506042902090565b5f805f8373ffffffffffffffffffffffffffffffffffffffff163b5f03611ed8576041869003611d1b57611cf286880188615157565b909350915086866040818110611d0a57611d0a61505e565b919091013560f81c9150611da49050565b6040869003611d72575f611d3187890189615157565b9094507f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811693509050611d6a60ff82901c601b6151a4565b915050611da4565b6040517f4be6321b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080515f8082526020820180845288905260ff841692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa158015611df5573d5f803e3d5ffd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff8116611e6d576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614611ed2576040517f815e1d6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50611fec565b6040517f1626ba7e0000000000000000000000000000000000000000000000000000000081525f9073ffffffffffffffffffffffffffffffffffffffff861690631626ba7e90611f309089908c908c906004016151bd565b602060405180830381865afa158015611f4b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f6f9190614e95565b90507fffffffff0000000000000000000000000000000000000000000000000000000081167f1626ba7e00000000000000000000000000000000000000000000000000000000146107b6576040517fb0669cbc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050505050565b73ffffffffffffffffffffffffffffffffffffffff82165f908152600660209081526040808320600885901c808552925282208054600160ff861690811b918218928390559293909190818316900361207a576040517f1fb09b8000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050565b5f8181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff848116911614612111576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f57524f4e475f46524f4d000000000000000000000000000000000000000000006044820152606401610f0a565b73ffffffffffffffffffffffffffffffffffffffff821661218e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f494e56414c49445f524543495049454e540000000000000000000000000000006044820152606401610f0a565b3373ffffffffffffffffffffffffffffffffffffffff841614806121e1575073ffffffffffffffffffffffffffffffffffffffff83165f90815260056020908152604080832033845290915290205460ff165b8061220e57505f8181526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1633145b612274576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f4e4f545f415554484f52495a45440000000000000000000000000000000000006044820152606401610f0a565b73ffffffffffffffffffffffffffffffffffffffff8084165f81815260036020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019055938616808352848320805460010190558583526002825284832080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081168317909155600490925284832080549092169091559251849392917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b5f838152600760205260408082205490516024810186905273ffffffffffffffffffffffffffffffffffffffff8581166044830152848116606483015290911691906123b79083907f876fda070000000000000000000000000000000000000000000000000000000090608401611383565b905080611ac057611ac07f263bc7b500000000000000000000000000000000000000000000000000000000836126aa565b73ffffffffffffffffffffffffffffffffffffffff8381165f8181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b5f8061248b8484612842565b6040517f1e2eaeaf0000000000000000000000000000000000000000000000000000000081526004810182905290915073ffffffffffffffffffffffffffffffffffffffff861690631e2eaeaf906024015b602060405180830381865afa1580156124f8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610da491906151d6565b807f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5d50565b8281811461257c576040517faaad13f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8181101561207a575f8686838181106125995761259961505e565b919091013560f81c91506125d29050818686858181106125bb576125bb61505e565b90506020028101906125cd919061508b565b612892565b5060010161257e565b5f6125e58261109d565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16148061264357505f8281526004602052604090205473ffffffffffffffffffffffffffffffffffffffff8481169116145b8061183f575060055f6126558461109d565b73ffffffffffffffffffffffffffffffffffffffff908116825260208083019390935260409182015f9081209187168152925290205460ff16905092915050565b5f805f8351602085015f875af19392505050565b3d60405183815282600482015260406024820152816044820152815f606483013e602080601f8401040260640191508181fd5b5f61271082111561271a576040517fdeaa01e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61271061272783856151ed565b61183f9190615204565b6060365f365f6127418787612a9a565b935093509350935061275584848484612542565b505060408051602081019091525f815295945050505050565b6040517f48c8949100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906348c89491906127e2908590859060040161523c565b5f604051808303815f875af11580156127fd573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526109d5919081019061524f565b5f8061284d84612af7565b90505f61285b600683615301565b6040805160208101879052908101829052909150606001604051602081830303815290604052805190602001209250505092915050565b60098310156129b557826128da575f365f805f365f6128b18989612b33565b96509650965096509650965096506128ce87878787878787612b69565b50505050505050505050565b60018303612910575f365f805f365f6128f38989612b33565b96509650965096509650965096506128ce87878787878787612cf3565b6002830361294e57365f805f80365f6129298989612e6f565b96509650965096509650965096506128ce8787878761294788612e95565b8787612f42565b60038303612980575f365f80365f612966888861303b565b955095509550955095509550610a9c868686868686613069565b6040517f5cda29d700000000000000000000000000000000000000000000000000000000815260048101849052602401610f0a565b601183036129ce5781356020830135611ac08282613232565b601583036129ed5781356020830135604084013561207a838383613270565b60098303612a1d5781356020830135604084013561207a83612a0e83613298565b612a1885876132cb565b61332e565b60128303612a4d5781356020830135604084013561207a83612a3e84612e95565b612a48848761350e565b61351d565b60178303612a60578135611062816135c5565b60188303612a795781356020830135611ac08282613654565b601983036129805781356020830135611ac082612a9583612e95565b61370e565b813582016020818101913590848101358501808201918135918790038082019190830101861015612aed577f3b99b53d000000000000000000000000000000000000000000000000000000005f5260045ffd5b5092959194509250565b6040515f90612b16908390600690602001918252602082015260400190565b604051602081830303815290604052805190602001209050919050565b813560208301610100840135610120850135610140860135365f612b598989600b613757565b9150915092959891949750929550565b7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c87612b9682826125db565b612be4576040517f0ca968d800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610f0a565b8888612c49816040805160c0830135603482015260a083013560318201526080830135602e8201526060830135601a8201528282013560178201526020808401356014830152923581526048600c8201205f9282018390529281018290525260011c90565b5f838152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1614612cb3576040517fa640d5e000000000000000000000000000000000000000000000000000000000815260048101839052602401610f0a565b5f80612cca8c612cc28d613773565b8f8b8b6137a5565b91509150612ce48a8a612cdd85856138cd565b9190613913565b50505050505050505050505050565b7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c87612d2082826125db565b612d6e576040517f0ca968d800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610f0a565b8888612dd3816040805160c0830135603482015260a083013560318201526080830135602e8201526060830135601a8201528282013560178201526020808401356014830152923581526048600c8201205f9282018390529281018290525260011c90565b5f838152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1614612e3d576040517fa640d5e000000000000000000000000000000000000000000000000000000000815260048101839052602401610f0a565b5f80612e558c612e4c8d613773565b612cc290615314565b91509150612ce48a8a612e6885856138cd565b91906139f0565b8160e0810135610100820135610120830135610140840135365f612b598789600b613757565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff831601612efa577f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c61089e565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe73ffffffffffffffffffffffffffffffffffffffff831601612f3e575030919050565b5090565b6008805460018101909155612f578482613a9c565b5f80612f6e8a612f668b613773565b8588886137a5565b91509150612f818888612cdd85856138cd565b612ff7612fe78b6040805160c0830135603482015260a083013560318201526080830135602e8201526060830135601a8201528282013560178201526020808401356014830152923581526048600c8201205f9282018390529281018290525260011c90565b5f85815260096020526040902055565b827f6cd04883f8403d89a9190c0a8c58275a0cd6a294ebca6f5e90f119f626b375848b604051613027919061534a565b60405180910390a250505050505050505050565b813560208301610100840135610120850135365f61305b8888600a613757565b915091509295509295509295565b7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c8661309682826125db565b6130e4576040517f0ca968d800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610f0a565b8787613149816040805160c0830135603482015260a083013560318201526080830135602e8201526060830135601a8201528282013560178201526020808401356014830152923581526048600c8201205f9282018390529281018290525260011c90565b5f838152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16146131b3576040517fa640d5e000000000000000000000000000000000000000000000000000000000815260048101839052602401610f0a565b5f6131be8b8b610f13565b6fffffffffffffffffffffffffffffffff169050801561320e575f806131f88c6131e785613773565b6131f090615314565b8f8c8c6137a5565b9150915061320b8b8b612e6885856138cd565b50505b5f8b8152600960205260408120556132258b613c33565b5050505050505050505050565b7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c6132628382612a1882613d7b565b6109d58282612a1885613d7b565b5f61327a82612e95565b905061328a8482612a4887613e1b565b6110628382612a4886613e1b565b5f816132a4573061089e565b7f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c61089e565b5f7f80000000000000000000000000000000000000000000000000000000000000008303613319576133128273ffffffffffffffffffffffffffffffffffffffff16613eb2565b905061089e565b826133275761331282613d7b565b5090919050565b73ffffffffffffffffffffffffffffffffffffffff83166133d9577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166311da60b4826040518263ffffffff1660e01b815260040160206040518083038185885af11580156133b4573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061106291906151d6565b6040517fa584119400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063a5841194906024015f604051808303815f87803b15801561345f575f80fd5b505af1158015613471573d5f803e3d5ffd5b50505050613480838383613f61565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166311da60b46040518163ffffffff1660e01b81526004016020604051808303815f875af11580156134ea573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061106291906151d6565b5f826133275761331282613e1b565b6040517f0b0d9c0900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301528381166024830152604482018390527f00000000000000000000000000000000000000000000000000000000000000001690630b0d9c09906064015b5f604051808303815f87803b1580156135b3575f80fd5b505af1158015611fec573d5f803e3d5ffd5b5f61360773ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016308461406b565b90507f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c5f821215613641576109d58382612a1885615314565b5f8213156109d5576109d583828461351d565b5f61365e83613e1b565b90508181116136e2576040517f80f0b44c00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906380f0b44c9060440161359c565b6109d5837f0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a5c8361351d565b5f61372e8373ffffffffffffffffffffffffffffffffffffffff16613eb2565b905080156109d5576109d573ffffffffffffffffffffffffffffffffffffffff841683836140e1565b365f805f6137668787876141dc565b9890975095505050505050565b805f811215611128576111287f93dafdf10000000000000000000000000000000000000000000000000000000061423f565b5f807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16635a6bcfda885f0160405180608001604052808b60a00160208101906138049190614eb0565b60020b815260200161381c60e08d0160c08e01614eb0565b60020b81526020018a81526020018981525087876040518563ffffffff1660e01b815260040161384f9493929190615358565b60408051808303815f875af115801561386a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061388e91906153a7565b5f87815260096020526040902054919350915060ff1c156138c3576138c3856138bc368a90038a018a6153c9565b8884614247565b9550959350505050565b5f608082811d9084901d03600f83810b9085900b03610da46138ee836142df565b6138f7836142df565b6fffffffffffffffffffffffffffffffff1660809190911b1790565b5f61391e8460801d90565b600f0b12801561396357506139338360801d90565b61393c9061543a565b6fffffffffffffffffffffffffffffffff16826fffffffffffffffffffffffffffffffff16105b806139b957505f61397484600f0b90565b600f0b1280156139b9575061398983600f0b90565b6139929061543a565b6fffffffffffffffffffffffffffffffff16816fffffffffffffffffffffffffffffffff16105b156109d5576040517fe377983c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816fffffffffffffffffffffffffffffffff16613a18613a108560801d90565b600f0b614314565b6fffffffffffffffffffffffffffffffff161080613a655750806fffffffffffffffffffffffffffffffff16613a51613a1085600f0b90565b6fffffffffffffffffffffffffffffffff16105b156109d5576040517f9ad5ea6900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613b19576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f494e56414c49445f524543495049454e540000000000000000000000000000006044820152606401610f0a565b5f8181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1615613ba4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f414c52454144595f4d494e5445440000000000000000000000000000000000006044820152606401610f0a565b73ffffffffffffffffffffffffffffffffffffffff82165f81815260036020908152604080832080546001019055848352600290915280822080547fffffffffffffffffffffffff0000000000000000000000000000000000000000168417905551839291907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b5f8181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1680613cbe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f4e4f545f4d494e544544000000000000000000000000000000000000000000006044820152606401610f0a565b73ffffffffffffffffffffffffffffffffffffffff81165f81815260036020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190558583526002825280832080547fffffffffffffffffffffffff000000000000000000000000000000000000000090811690915560049092528083208054909216909155518492907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b5f80613dbe73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016308561406b565b90505f811315613e12576040517f3351b26000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152602401610f0a565b61183f81615314565b5f80613e5e73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016308561406b565b90505f81121561089e576040517f4c085bf100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152602401610f0a565b5f73ffffffffffffffffffffffffffffffffffffffff8216613ed5575047919050565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa158015613f3d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061089e91906151d6565b3073ffffffffffffffffffffffffffffffffffffffff831603613fbf576109d573ffffffffffffffffffffffffffffffffffffffff84167f0000000000000000000000000000000000000000000000000000000000000000836140e1565b6040517f36c7851600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301527f000000000000000000000000000000000000000000000000000000000000000081166024830152828116604483015284811660648301527f000000000000000000000000000000000000000000000000000000000000000016906336c785169060840161359c565b5f8073ffffffffffffffffffffffffffffffffffffffff84165f5273ffffffffffffffffffffffffffffffffffffffff831660205260405f2090508473ffffffffffffffffffffffffffffffffffffffff1663f135baaa826040518263ffffffff1660e01b81526004016124dd91815260200190565b5f73ffffffffffffffffffffffffffffffffffffffff841661413b575f805f8085875af1905080614136576141367f8549db5900000000000000000000000000000000000000000000000000000000846126aa565b611062565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505f81525f60208201525f60408201525080611062576110627fb12c5f9c00000000000000000000000000000000000000000000000000000000856126aa565b600581901b8301358301803590602080820191869003016141fd8184615301565b851015614236576040517f3b99b53d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50935093915050565b805f5260045ffd5b5f8481526007602052604080822054905173ffffffffffffffffffffffffffffffffffffffff90911691906142ae9083907fa6ae447d0000000000000000000000000000000000000000000000000000000090611383908a908a908a908a90602401615476565b90508061207a5761207a7f0ab45b4000000000000000000000000000000000000000000000000000000000836126aa565b80600f81900b8114611128576111287f93dafdf10000000000000000000000000000000000000000000000000000000061423f565b5f8082600f0b1215612f3e57612f3e7f93dafdf10000000000000000000000000000000000000000000000000000000061423f565b73ffffffffffffffffffffffffffffffffffffffff8116811461436a575f80fd5b50565b803561112881614349565b5f8083601f840112614388575f80fd5b50813567ffffffffffffffff81111561439f575f80fd5b6020830191508360208285010111156143b6575f80fd5b9250929050565b5f805f80606085870312156143d0575f80fd5b84356143db81614349565b9350602085013567ffffffffffffffff8111156143f6575f80fd5b850160608188031215614407575f80fd5b9250604085013567ffffffffffffffff811115614422575f80fd5b61442e87828801614378565b95989497509550505050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461436a575f80fd5b5f60208284031215614477575f80fd5b813561183f8161443a565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f61183f6020830184614482565b5f602082840312156144f0575f80fd5b5035919050565b5f8060408385031215614508575f80fd5b823561451381614349565b946020939093013593505050565b5f805f805f8060a08789031215614536575f80fd5b863561454181614349565b9550602087013594506040870135935060608701359250608087013567ffffffffffffffff811115614571575f80fd5b61457d89828a01614378565b979a9699509497509295939492505050565b5f805f606084860312156145a1575f80fd5b83356145ac81614349565b925060208401356145bc81614349565b929592945050506040919091013590565b5f805f808486036101008112156145e2575f80fd5b85356145ed81614349565b945060c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08201121561461e575f80fd5b5060208501925060e085013567ffffffffffffffff811115614422575f80fd5b80358015158114611128575f80fd5b5f805f805f805f60c0888a031215614663575f80fd5b873561466e81614349565b9650602088013561467e81614349565b955061468c6040890161463e565b9450606088013593506080880135925060a088013567ffffffffffffffff8111156146b5575f80fd5b6146c18a828b01614378565b989b979a50959850939692959293505050565b5f805f8084860360e08112156146e8575f80fd5b60a08112156146f5575f80fd5b5084935060a085013561470781614349565b925060c085013567ffffffffffffffff811115614422575f80fd5b5f60e08284031215614732575f80fd5b50919050565b5f80610100838503121561474a575f80fd5b8235915061475b8460208501614722565b90509250929050565b5f8083601f840112614774575f80fd5b50813567ffffffffffffffff81111561478b575f80fd5b6020830191508360208260051b85010111156143b6575f80fd5b5f805f80604085870312156147b8575f80fd5b843567ffffffffffffffff8111156147ce575f80fd5b6147da87828801614378565b909550935050602085013567ffffffffffffffff8111156147f9575f80fd5b61442e87828801614764565b5f805f805f610140868803121561481a575f80fd5b8535945061482b8760208801614722565b935061010086013561483c81614349565b925061012086013567ffffffffffffffff811115614858575f80fd5b61486488828901614378565b969995985093965092949392505050565b5f805f806101208587031215614889575f80fd5b8435935061489a8660208701614722565b925061010085013567ffffffffffffffff811115614422575f80fd5b5f602082840312156148c6575f80fd5b813561183f81614349565b5f80602083850312156148e2575f80fd5b823567ffffffffffffffff8111156148f8575f80fd5b61490485828601614378565b90969095509350505050565b5f8060408385031215614921575f80fd5b823561492c81614349565b915061475b6020840161463e565b5f806020838503121561494b575f80fd5b823567ffffffffffffffff811115614961575f80fd5b61490485828601614764565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b828110156149e2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08786030184526149cd858351614482565b94506020938401939190910190600101614993565b50929695505050505050565b5f805f805f60808688031215614a02575f80fd5b8535614a0d81614349565b94506020860135614a1d81614349565b935060408601359250606086013567ffffffffffffffff811115614858575f80fd5b5f805f60408486031215614a51575f80fd5b833567ffffffffffffffff811115614a67575f80fd5b614a7386828701614378565b909790965060209590950135949350505050565b5f8060408385031215614a98575f80fd5b8235614aa381614349565b91506020830135614ab381614349565b809150509250929050565b803565ffffffffffff81168114611128575f80fd5b8035614ade81614349565b73ffffffffffffffffffffffffffffffffffffffff1682526020810135614b0481614349565b73ffffffffffffffffffffffffffffffffffffffff16602083015265ffffffffffff614b3260408301614abe565b16604083015265ffffffffffff614b4b60608301614abe565b1660608301525050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff85168152606060208201525f60c0820185357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1873603018112614bf2575f80fd5b860160208101903567ffffffffffffffff811115614c0e575f80fd5b8060071b3603821315614c1f575f80fd5b60608581015291829052905f9060e085015b81831015614c5657614c438185614ad3565b6080938401936001939093019201614c31565b614c6260208a0161436d565b73ffffffffffffffffffffffffffffffffffffffff16608087015260408981013560a08801819052878303918801919091529350614ca181888a614b55565b9a9950505050505050505050565b600181811c90821680614cc357607f821691505b602082108103614732577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff85168152614d206020820185614ad3565b5f6080850135614d2f81614349565b73ffffffffffffffffffffffffffffffffffffffff1660a08381019190915285013560c083015261010060e08301819052614d6d9083018486614b55565b9695505050505050565b803562ffffff81168114611128575f80fd5b8060020b811461436a575f80fd5b8035614da281614349565b73ffffffffffffffffffffffffffffffffffffffff1682526020810135614dc881614349565b73ffffffffffffffffffffffffffffffffffffffff16602083015262ffffff614df360408301614d77565b1660408301526060810135614e0781614d89565b60020b60608301526080810135614e1d81614349565b73ffffffffffffffffffffffffffffffffffffffff81166080840152505050565b614e488186614d97565b73ffffffffffffffffffffffffffffffffffffffff841660a082015260e060c08201525f614d6d60e083018486614b55565b5f60208284031215614e8a575f80fd5b815161183f81614d89565b5f60208284031215614ea5575f80fd5b815161183f8161443a565b5f60208284031215614ec0575f80fd5b813561183f81614d89565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614f3f57614f3f614ecb565b604052919050565b5f60a08284031215614f57575f80fd5b60405160a0810167ffffffffffffffff81118282101715614f7a57614f7a614ecb565b6040529050808235614f8b81614349565b81526020830135614f9b81614349565b6020820152614fac60408401614d77565b60408201526060830135614fbf81614d89565b60608201526080830135614fd281614349565b6080919091015292915050565b5f60a08284031215614fef575f80fd5b61183f8383614f47565b6150038282614d97565b60a081013561501181614d89565b60020b60a083015260c081013561502781614d89565b8060020b60c0840152505050565b8481526150456020820185614ff9565b6101206101008201525f614d6d61012083018486614b55565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f8083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126150be575f80fd5b83018035915067ffffffffffffffff8211156150d8575f80fd5b6020019150368190038213156143b6575f80fd5b818382375f9101908152919050565b73ffffffffffffffffffffffffffffffffffffffff8616815273ffffffffffffffffffffffffffffffffffffffff85166020820152836040820152608060608201525f61514c608083018486614b55565b979650505050505050565b5f8060408385031215615168575f80fd5b50508035926020909101359150565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b60ff818116838216019081111561089e5761089e615177565b838152604060208201525f610da4604083018486614b55565b5f602082840312156151e6575f80fd5b5051919050565b808202811582820484141761089e5761089e615177565b5f82615237577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b500490565b602081525f610fcd602083018486614b55565b5f6020828403121561525f575f80fd5b815167ffffffffffffffff811115615275575f80fd5b8201601f81018413615285575f80fd5b805167ffffffffffffffff81111561529f5761529f614ecb565b6152d060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614ef8565b8181528560208385010111156152e4575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b8082018082111561089e5761089e615177565b5f7f8000000000000000000000000000000000000000000000000000000000000000820361534457615344615177565b505f0390565b60e0810161089e8284614ff9565b6153628186614d97565b835160020b60a0820152602084015160020b60c0820152604084015160e082015260608401516101008201526101406101208201525f614d6d61014083018486614b55565b5f80604083850312156153b8575f80fd5b505080516020909101519092909150565b5f60e08284031280156153da575f80fd5b506040516060810167ffffffffffffffff811182821017156153fe576153fe614ecb565b60405261540b8484614f47565b815260a083013561541b81614d89565b602082015260c083013561542e81614d89565b60408201529392505050565b5f81600f0b7fffffffffffffffffffffffffffffffff80000000000000000000000000000000810361546e5761546e615177565b5f0392915050565b5f61014082019050858252845173ffffffffffffffffffffffffffffffffffffffff815116602084015273ffffffffffffffffffffffffffffffffffffffff602082015116604084015262ffffff6040820151166060840152606081015160020b608084015273ffffffffffffffffffffffffffffffffffffffff60808201511660a084015250602085015161551160c084018260020b9052565b50604085015161552660e084018260020b9052565b5061010082019390935261012001529291505056fea2646970667358221220c30b8b0386f2b6244c7ceb9a27e3a9944aa04174e0ed871e016d80b8f6caebdb64736f6c634300081a003300000000000000000000000039bf2eff94201cfaa471932655404f63315147a4000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3", + "nonce": "0x23", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x72f892", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6868534dcf11b1774abdd69602eee0fc3b3d77b9f5e13437dedf8fbb145dea2c", + "transactionIndex": "0xd", + "blockHash": "0xa09987571186d1c7aeb03045042967627f80c1b6ce6773c166a9630a01758496", + "blockNumber": "0xd42418", + "gasUsed": "0x49c48d", + "effectiveGasPrice": "0x3140", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": "0xa2f16f0bb5dea7c9a6675ec88193471dee805e6e", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x86b5d5b1a8", + "l1GasPrice": "0xcde1c41a", + "l1GasUsed": "0x25249" + } + ], + "libraries": [], + "pending": [], + "returns": { + "posm": { + "internal_type": "contract PositionManager", + "value": "0xA2f16f0BB5dEA7c9A6675Ec88193471dEe805e6e" + } + }, + "timestamp": 1723574053, + "chain": 84532, + "commit": "d0faf02" +} \ No newline at end of file diff --git a/broadcast/DeployQuoter.s.sol/84532/run-latest.json b/broadcast/DeployQuoter.s.sol/84532/run-latest.json new file mode 100644 index 000000000..03e79da61 --- /dev/null +++ b/broadcast/DeployQuoter.s.sol/84532/run-latest.json @@ -0,0 +1,59 @@ +{ + "transactions": [ + { + "hash": "0xa0b95b295011dabcdde8df00f3ea4dec3c06b28a0e887dfa1f4d09284377bfee", + "transactionType": "CREATE", + "contractName": "Quoter", + "contractAddress": "0xf3a39c86dbd13c45365e57fb90fe413371f65af8", + "function": null, + "arguments": [ + "0x39BF2eFF94201cfAA471932655404F63315147a4" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x373b67", + "value": "0x0", + "input": "0x60a0604052348015600e575f80fd5b5060405161323f38038061323f833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f80fd5b81516001600160a01b0381168114605f575f80fd5b9392505050565b60805161317b6100c45f395f81816101a1015281816102570152818161038e015281816103fa015281816105900152818161071001528181610d0001528181610da0015281816112bd015281816113640152611621015261317b5ff3fe608060405234801561000f575f80fd5b50600436106100b9575f3560e01c806391dd734611610072578063ca253dc911610058578063ca253dc914610176578063ceebece714610189578063dc4c90d31461019c575f80fd5b806391dd734614610150578063aa2f150114610163575f80fd5b8063147d2af9116100a2578063147d2af91461010857806354b20ff61461012a5780636a36a38c1461013d575f80fd5b806302a00c25146100bd57806311359648146100e6575b5f80fd5b6100d06100cb366004611f55565b6101e8565b6040516100dd9190611fe0565b60405180910390f35b6100f96100f4366004612240565b6103f4565b6040516100dd93929190612332565b61011b610116366004612399565b610589565b6040516100dd93929190612540565b6100f9610138366004612240565b61070a565b6100d061014b3660046125ef565b6108d3565b6100d061015e366004612626565b610d86565b6100d06101713660046125ef565b610e0a565b61011b610184366004612399565b61135d565b6100d0610197366004611f55565b6113b7565b6101c37f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100dd565b6060333014610223576040517f29c3b7ee00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f61027d61024061023936869003860186612694565b60a0902090565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690611537565b509092505f9150819050806102ea61029a36889003880188612694565b6102aa60c0890160a08a016126ae565b6102ba60e08a0160c08b016126c7565b600f0b6102c69061270d565b6102d76101008b0160e08c01612743565b6102e56101008c018c61275e565b61161c565b919450925090505f60025b60405190808252806020026020018201604052801561031e578160200160208202803683370190505b50905061032b8460801d90565b610334906127c6565b815f8151811061034657610346612802565b600f92830b602091820292909201015284900b610362906127c6565b8160018151811061037557610375612802565b600f9290920b602092830291909101909101525f6103c37f00000000000000000000000000000000000000000000000000000000000000006103bc368b90038b018b612694565b88866117a8565b90505f8285836040516020016103db93929190612332565b6040516020818303038152906040529050805181602001fd5b60605f807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166348c894916302a00c2560e01b8660405160240161044d919061282f565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e084901b90921682526104dd91600401611fe0565b5f604051808303815f875af192505050801561053857506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526105359190810190612922565b60015b610580573d808015610565576040519150601f19603f3d011682016040523d82523d5f602084013e61056a565b606091505b5061057481611af9565b93509350935050610582565b505b9193909250565b60608060607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166348c8949163aa2f150160e01b866040516024016105e39190612997565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e084901b909216825261067391600401611fe0565b5f604051808303815f875af19250505080156106ce57506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526106cb9190810190612922565b60015b610580573d8080156106fb576040519150601f19603f3d011682016040523d82523d5f602084013e610700565b606091505b5061057481611b29565b60605f807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166348c8949163ceebece760e01b86604051602401610763919061282f565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e084901b90921682526107f391600401611fe0565b5f604051808303815f875af192505050801561084e57506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261084b9190810190612922565b60015b610580573d80801561087b576040519150601f19603f3d011682016040523d82523d5f602084013e610880565b606091505b50846060015173ffffffffffffffffffffffffffffffffffffffff165f036108ca575f80547fffffffffffffffffffffffffffffffff000000000000000000000000000000001690555b61057481611af9565b606033301461090e576040517f29c3b7ee00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f61091c6020840184612ac9565b905090505f60405180606001604052808360016109399190612b2d565b67ffffffffffffffff81111561095157610951611ff2565b60405190808252806020026020018201604052801561097a578160200160208202803683370190505b5081526020018367ffffffffffffffff81111561099957610999611ff2565b6040519080825280602002602001820160405280156109c2578160200160208202803683370190505b5081526020018367ffffffffffffffff8111156109e1576109e1611ff2565b604051908082528060200260200182016040528015610a0a578160200160208202803683370190505b50905260408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290529192505b83811015610d64575f80610aaf8315610a6d578460c00151610a7a565b610a7a60208a018a612743565b610a8760208b018b612ac9565b86818110610a9757610a97612802565b9050602002810190610aa99190612b40565b90611b4d565b91509150610ac16102408360a0902090565b505060020b608086015250610b3b82828515610ae1578660200151610af1565b610af160608c0160408d016126c7565b600f0b610afd9061270d565b5f610b0b60208e018e612ac9565b89818110610b1b57610b1b612802565b9050602002810190610b2d9190612b40565b6102e590608081019061275e565b60020b60a087015273ffffffffffffffffffffffffffffffffffffffff1660e0860152845280610b86578351600f0b610b73906127c6565b845160801d610b81906127c6565b610ba2565b835160801d610b94906127c6565b8451600f0b610ba2906127c6565b600f90810b60608701520b604085018190528551805185908110610bc857610bc8612802565b60200260200101818151610bdc9190612b7c565b600f0b90525060608401518551610bf4856001612b2d565b81518110610c0457610c04612802565b60200260200101818151610c189190612b7c565b600f0b90525080610c2d57835160801d610c33565b8351600f0b5b6fffffffffffffffffffffffffffffffff16602080860191909152610c5a90890189612ac9565b84818110610c6a57610c6a612802565b9050602002810190610c7c9190612b40565b610c8a906020810190612743565b73ffffffffffffffffffffffffffffffffffffffff1660c085015260e08401516020860151805185908110610cc157610cc1612802565b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050610d2f7f00000000000000000000000000000000000000000000000000000000000000008386608001518760a001516117a8565b85604001518481518110610d4557610d45612802565b63ffffffff909216602092830291909101909101525050600101610a50565b505f825f0151836020015184604001516040516020016103db93929190612540565b60603373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610df7576040517fae18210a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e018383611c84565b90505b92915050565b6060333014610e45576040517f29c3b7ee00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f610e536020840184612ac9565b905090505f6040518060600160405280836001610e709190612b2d565b67ffffffffffffffff811115610e8857610e88611ff2565b604051908082528060200260200182016040528015610eb1578160200160208202803683370190505b5081526020018367ffffffffffffffff811115610ed057610ed0611ff2565b604051908082528060200260200182016040528015610ef9578160200160208202803683370190505b5081526020018367ffffffffffffffff811115610f1857610f18611ff2565b604051908082528060200260200182016040528015610f41578160200160208202803683370190505b50905260408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052919250835b801561133b57848114610f9f578260200151610faf565b610faf60608801604089016126c7565b5f80547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff83161781559092508061104d610ffe60208b018b612ac9565b611009600187612bca565b81811061101857611018612802565b905060200281019061102a9190612b40565b88851461103b578660c00151611b4d565b61104860208c018c612743565b611b4d565b9150915061105f6102408360a0902090565b505060020b6080870152506110ab8282156fffffffffffffffffffffffffffffffff87165f61109160208f018f612ac9565b61109c60018b612bca565b818110610b1b57610b1b612802565b60020b60a088015273ffffffffffffffffffffffffffffffffffffffff1660e087015285525f80547fffffffffffffffffffffffffffffffff00000000000000000000000000000000169055801561111e578451600f0b61110b906127c6565b855160801d611119906127c6565b61113a565b845160801d61112c906127c6565b8551600f0b61113a906127c6565b600f90810b60608801520b604086018190528651611159600186612bca565b8151811061116957611169612802565b6020026020010181815161117d9190612b7c565b600f0b9052506060850151865180518590811061119c5761119c612802565b602002602001018181516111b09190612b7c565b600f0b90525080156111cf578451600f0b6111ca906127c6565b6111dd565b845160801d6111dd906127c6565b6fffffffffffffffffffffffffffffffff16602080870191909152611204908a018a612ac9565b61120f600186612bca565b81811061121e5761121e612802565b90506020028101906112309190612b40565b61123e906020810190612743565b73ffffffffffffffffffffffffffffffffffffffff1660c086015260e0850151602087015161126e600186612bca565b8151811061127e5761127e612802565b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250506112ec7f00000000000000000000000000000000000000000000000000000000000000008387608001518860a001516117a8565b60408701516112fc600186612bca565b8151811061130c5761130c612802565b602002602001019063ffffffff16908163ffffffff16815250505050808061133390612bdd565b915050610f88565b505f835f0151846020015185604001516040516020016103db93929190612540565b60608060607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166348c89491636a36a38c60e01b866040516024016105e39190612997565b60603330146113f2576040517f29c3b7ee00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611403610100830160e08401612743565b73ffffffffffffffffffffffffffffffffffffffff165f036114705761142f60e0830160c084016126c7565b5f80547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b5f61148661024061023936869003860186612694565b509092505f9150819050806114e66114a336889003880188612694565b6114b360c0890160a08a016126ae565b6114c360e08a0160c08b016126c7565b6fffffffffffffffffffffffffffffffff166102d76101008b0160e08c01612743565b5f5492955090935091506fffffffffffffffffffffffffffffffff161561152f575f80547fffffffffffffffffffffffffffffffff000000000000000000000000000000001690555b5f60026102f5565b5f805f805f61154586611d43565b6040517f1e2eaeaf000000000000000000000000000000000000000000000000000000008152600481018290529091505f9073ffffffffffffffffffffffffffffffffffffffff891690631e2eaeaf90602401602060405180830381865afa1580156115b3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115d79190612c11565b905073ffffffffffffffffffffffffffffffffffffffff811695508060a01c60020b945062ffffff8160b81c16935062ffffff8160d01c169250505092959194509250565b5f805f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f3cd914c8a60405180606001604052808c151581526020018b815260200161167f8b8e611d7f565b73ffffffffffffffffffffffffffffffffffffffff1681525088886040518563ffffffff1660e01b81526004016116b99493929190612c28565b6020604051808303815f875af11580156116d5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116f99190612c11565b5f549093506fffffffffffffffffffffffffffffffff161580159061175257508761172d576117288360801d90565b611737565b61173783600f0b90565b5f546fffffffffffffffffffffffffffffffff908116911614155b15611789576040517fe52970aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a0892061179690610240565b50949b919a5098509650505050505050565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a081018290525f60088660600151866117eb9190612d5d565b60020b901d90505f6101008760600151876118069190612d5d565b6118109190612dd0565b90505f60088860600151876118259190612d5d565b60020b901d90505f6101008960600151886118409190612d5d565b61184a9190612dd0565b90505f61187961185b8b60a0902090565b73ffffffffffffffffffffffffffffffffffffffff8d169085611ddb565b9050600160ff83161b8116158015906118a1575060608a015161189c9089612dd0565b60020b155b80156118b257508760020b8960020b135b151560a08701525f6118e66118c88c60a0902090565b73ffffffffffffffffffffffffffffffffffffffff8e169088611ddb565b9050600160ff86161b81161580159061190e575060608b0151611909908b612dd0565b60020b155b801561191f57508860020b8a60020b125b15156080880152600184810b9087900b128061195157508360010b8660010b14801561195157508260ff168560ff1611155b1561197c57600186810b885260ff80871660408a01529085900b60208901528316606088015261199e565b600184810b885260ff80851660408a01529087900b6020890152851660608801525b50505060408401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60ff9091161b925050505b816020015160010b825f015160010b13611abf57816020015160010b825f015160010b03611a34576060820151611a0a9060ff612df1565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60ff919091161c165b5f611a63611a438860a0902090565b845173ffffffffffffffffffffffffffffffffffffffff8b169190611ddb565b9050818116611a7181611ede565b611a7f9061ffff1686612e0a565b845190955084611a8e82612e26565b60010b9052507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff92506119d2915050565b8160a0015115611ad757611ad4600184612e45565b92505b816080015115611aef57611aec600184612e45565b92505b5050949350505050565b60605f80611b0684611f09565b935083806020019051810190611b1c9190612ee7565b9196909550909350915050565b6060806060611b3784611f09565b935083806020019051810190611b1c9190612fa5565b6040805160a0810182525f808252602082018190529181018290526060810182905260808101919091525f80611b866020860186612743565b90505f8073ffffffffffffffffffffffffffffffffffffffff80841690871610611bb1578286611bb4565b85835b909250905073ffffffffffffffffffffffffffffffffffffffff8087169083161493506040518060a001604052808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff168152602001886020016020810190611c2d9190613095565b62ffffff168152602001611c4760608a0160408b016130ae565b60020b8152602001611c5f60808a0160608b01612743565b73ffffffffffffffffffffffffffffffffffffffff1681525094505050509250929050565b60605f803073ffffffffffffffffffffffffffffffffffffffff168585604051611caf9291906130c7565b5f604051808303815f865af19150503d805f8114611ce8576040519150601f19603f3d011682016040523d82523d5f602084013e611ced565b606091505b50915091508115611d01579150610e049050565b80515f03611d3b576040517fa40afa3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805160208201fd5b6040515f90611d62908390600690602001918252602082015260400190565b604051602081830303815290604052805190602001209050919050565b5f73ffffffffffffffffffffffffffffffffffffffff831615611da25782610e01565b81611dcb57611dc6600173fffd8963efd1fc6a506488495d951d5263988d266130d6565b610e01565b610e016401000276a36001613102565b5f80611de684611d43565b90505f611df4600583612b2d565b60408051600187900b60208201529081018290529091505f90606001604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00181529082905280516020909101207f1e2eaeaf00000000000000000000000000000000000000000000000000000000825260048201819052915073ffffffffffffffffffffffffffffffffffffffff881690631e2eaeaf90602401602060405180830381865afa158015611eaf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ed39190612c11565b979650505050505050565b5f805b8215610e045780611ef18161312e565b9150611f009050600184612bca565b83169250611ee1565b60608082511015611f5157816040517f6190b2b0000000000000000000000000000000000000000000000000000000008152600401611f489190611fe0565b60405180910390fd5b5090565b5f60208284031215611f65575f80fd5b813567ffffffffffffffff811115611f7b575f80fd5b82016101208185031215611f8d575f80fd5b9392505050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f610e016020830184611f94565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60405160a0810167ffffffffffffffff8111828210171561204257612042611ff2565b60405290565b6040516060810167ffffffffffffffff8111828210171561204257612042611ff2565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156120b2576120b2611ff2565b604052919050565b73ffffffffffffffffffffffffffffffffffffffff811681146120db575f80fd5b50565b803562ffffff811681146120f0575f80fd5b919050565b8035600281900b81146120f0575f80fd5b5f60a08284031215612116575f80fd5b61211e61201f565b9050813561212b816120ba565b8152602082013561213b816120ba565b602082015261214c604083016120de565b604082015261215d606083016120f5565b60608201526080820135612170816120ba565b608082015292915050565b803580151581146120f0575f80fd5b80356fffffffffffffffffffffffffffffffff811681146120f0575f80fd5b5f67ffffffffffffffff8211156121c2576121c2611ff2565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f8301126121fd575f80fd5b813561221061220b826121a9565b61206b565b818152846020838601011115612224575f80fd5b816020850160208301375f918101602001919091529392505050565b5f60208284031215612250575f80fd5b813567ffffffffffffffff811115612266575f80fd5b82016101208185031215612278575f80fd5b61228061201f565b61228a8583612106565b815261229860a0830161217b565b60208201526122a960c0830161218a565b604082015260e08201356122bc816120ba565b606082015261010082013567ffffffffffffffff8111156122db575f80fd5b6122e7868285016121ee565b608083015250949350505050565b5f8151808452602084019350602083015f5b82811015612328578151600f0b865260209586019590910190600101612307565b5093949350505050565b606081525f61234460608301866122f5565b905073ffffffffffffffffffffffffffffffffffffffff8416602083015263ffffffff83166040830152949350505050565b5f67ffffffffffffffff82111561238f5761238f611ff2565b5060051b60200190565b5f602082840312156123a9575f80fd5b813567ffffffffffffffff8111156123bf575f80fd5b8201606081850312156123d0575f80fd5b6123d8612048565b81356123e3816120ba565b8152602082013567ffffffffffffffff8111156123fe575f80fd5b8201601f8101861361240e575f80fd5b803561241c61220b82612376565b8082825260208201915060208360051b85010192508883111561243d575f80fd5b602084015b8381101561251d57803567ffffffffffffffff811115612460575f80fd5b850160a0818c037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0011215612493575f80fd5b61249b61201f565b60208201356124a9816120ba565b81526124b7604083016120de565b60208201526124c8606083016120f5565b604082015260808201356124db816120ba565b606082015260a082013567ffffffffffffffff8111156124f9575f80fd5b6125088d6020838601016121ee565b60808301525084525060209283019201612442565b506020850152506125339150506040830161218a565b6040820152949350505050565b606081525f61255260608301866122f5565b82810360208401528085518083526020830191506020870192505f5b818110156125a257835173ffffffffffffffffffffffffffffffffffffffff1683526020938401939092019160010161256e565b50508381036040850152845180825260209182019250908501905f5b818110156125e257825163ffffffff168452602093840193909201916001016125be565b5091979650505050505050565b5f602082840312156125ff575f80fd5b813567ffffffffffffffff811115612615575f80fd5b820160608185031215611f8d575f80fd5b5f8060208385031215612637575f80fd5b823567ffffffffffffffff81111561264d575f80fd5b8301601f8101851361265d575f80fd5b803567ffffffffffffffff811115612673575f80fd5b856020828401011115612684575f80fd5b6020919091019590945092505050565b5f60a082840312156126a4575f80fd5b610e018383612106565b5f602082840312156126be575f80fd5b610e018261217b565b5f602082840312156126d7575f80fd5b610e018261218a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f7f8000000000000000000000000000000000000000000000000000000000000000820361273d5761273d6126e0565b505f0390565b5f60208284031215612753575f80fd5b8135611f8d816120ba565b5f8083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112612791575f80fd5b83018035915067ffffffffffffffff8211156127ab575f80fd5b6020019150368190038213156127bf575f80fd5b9250929050565b5f81600f0b7fffffffffffffffffffffffffffffffff8000000000000000000000000000000081036127fa576127fa6126e0565b5f0392915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b602081526128b660208201835173ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff602082015116602083015262ffffff6040820151166040830152606081015160020b606083015273ffffffffffffffffffffffffffffffffffffffff60808201511660808301525050565b6020820151151560c08201526fffffffffffffffffffffffffffffffff60408301511660e082015273ffffffffffffffffffffffffffffffffffffffff6060830151166101008201525f60808301516101208084015261291a610140840182611f94565b949350505050565b5f60208284031215612932575f80fd5b815167ffffffffffffffff811115612948575f80fd5b8201601f81018413612958575f80fd5b805161296661220b826121a9565b81815285602083850101111561297a575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b602081525f6080820173ffffffffffffffffffffffffffffffffffffffff845116602084015260208401516060604085015281815180845260a08601915060a08160051b87010193506020830192505f5b81811015612aa0577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60878603018352835173ffffffffffffffffffffffffffffffffffffffff815116865262ffffff6020820151166020870152604081015160020b604087015273ffffffffffffffffffffffffffffffffffffffff60608201511660608701526080810151905060a06080870152612a8a60a0870182611f94565b95505060209384019392909201916001016129e8565b5050505060408401516fffffffffffffffffffffffffffffffff81166060850152509392505050565b5f8083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112612afc575f80fd5b83018035915067ffffffffffffffff821115612b16575f80fd5b6020019150600581901b36038213156127bf575f80fd5b80820180821115610e0457610e046126e0565b5f82357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61833603018112612b72575f80fd5b9190910192915050565b600f81810b9083900b016f7fffffffffffffffffffffffffffffff81137fffffffffffffffffffffffffffffffff8000000000000000000000000000000082121715610e0457610e046126e0565b81810381811115610e0457610e046126e0565b5f81612beb57612beb6126e0565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b5f60208284031215612c21575f80fd5b5051919050565b612ca7818673ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff602082015116602083015262ffffff6040820151166040830152606081015160020b606083015273ffffffffffffffffffffffffffffffffffffffff60808201511660808301525050565b8351151560a0820152602084015160c082015273ffffffffffffffffffffffffffffffffffffffff60408501511660e08201526101206101008201528161012082015281836101408301375f81830161014090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01601019392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f8160020b8360020b80612d7357612d73612d30565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000083141615612dc757612dc76126e0565b90059392505050565b5f8260020b80612de257612de2612d30565b808360020b0791505092915050565b60ff8281168282160390811115610e0457610e046126e0565b63ffffffff8181168382160190811115610e0457610e046126e0565b5f8160010b617fff8103612e3c57612e3c6126e0565b60010192915050565b63ffffffff8281168282160390811115610e0457610e046126e0565b5f82601f830112612e70575f80fd5b8151612e7e61220b82612376565b8082825260208201915060208360051b860101925085831115612e9f575f80fd5b602085015b83811015612eca57805180600f0b8114612ebc575f80fd5b835260209283019201612ea4565b5095945050505050565b805163ffffffff811681146120f0575f80fd5b5f805f60608486031215612ef9575f80fd5b835167ffffffffffffffff811115612f0f575f80fd5b612f1b86828701612e61565b9350506020840151612f2c816120ba565b9150612f3a60408501612ed4565b90509250925092565b5f82601f830112612f52575f80fd5b8151612f6061220b82612376565b8082825260208201915060208360051b860101925085831115612f81575f80fd5b602085015b83811015612eca57612f9781612ed4565b835260209283019201612f86565b5f805f60608486031215612fb7575f80fd5b835167ffffffffffffffff811115612fcd575f80fd5b612fd986828701612e61565b935050602084015167ffffffffffffffff811115612ff5575f80fd5b8401601f81018613613005575f80fd5b805161301361220b82612376565b8082825260208201915060208360051b850101925088831115613034575f80fd5b6020840193505b8284101561305f57835161304e816120ba565b82526020938401939091019061303b565b80955050505050604084015167ffffffffffffffff81111561307f575f80fd5b61308b86828701612f43565b9150509250925092565b5f602082840312156130a5575f80fd5b610e01826120de565b5f602082840312156130be575f80fd5b610e01826120f5565b818382375f9101908152919050565b73ffffffffffffffffffffffffffffffffffffffff8281168282160390811115610e0457610e046126e0565b73ffffffffffffffffffffffffffffffffffffffff8181168382160190811115610e0457610e046126e0565b5f61ffff821661ffff8103612e3c57612e3c6126e056fea2646970667358221220f4ba8ac14ae069bff9e4bdb1bfce63907204639259133b9a38d025cd4de5dbec64736f6c634300081a003300000000000000000000000039bf2eff94201cfaa471932655404f63315147a4", + "nonce": "0x24", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x8011e1", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xa0b95b295011dabcdde8df00f3ea4dec3c06b28a0e887dfa1f4d09284377bfee", + "transactionIndex": "0x10", + "blockHash": "0x68745f3e8f86f7a5ddfb12843e78cdef8cb4b4ddfd34db01174297d0a0e7f944", + "blockNumber": "0xd4244a", + "gasUsed": "0x2a7c77", + "effectiveGasPrice": "0x33d5", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0xf3a39c86dbd13c45365e57fb90fe413371f65af8", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x51d75ab15c", + "l1GasPrice": "0xd549e132", + "l1GasUsed": "0x15c82" + } + ], + "libraries": [], + "pending": [], + "returns": { + "state": { + "internal_type": "contract Quoter", + "value": "0xf3A39C86dbd13C45365E57FB90fe413371F65AF8" + } + }, + "timestamp": 1723574153, + "chain": 84532, + "commit": "d0faf02" +} \ No newline at end of file diff --git a/broadcast/DeployStateView.s.sol/11155111/run-1721766499.json b/broadcast/DeployStateView.s.sol/11155111/run-latest.json similarity index 100% rename from broadcast/DeployStateView.s.sol/11155111/run-1721766499.json rename to broadcast/DeployStateView.s.sol/11155111/run-latest.json diff --git a/broadcast/DeployStateView.s.sol/84532/run-latest.json b/broadcast/DeployStateView.s.sol/84532/run-latest.json new file mode 100644 index 000000000..6fe34b0af --- /dev/null +++ b/broadcast/DeployStateView.s.sol/84532/run-latest.json @@ -0,0 +1,59 @@ +{ + "transactions": [ + { + "hash": "0xb998c1967306d62218346705921f76c078f25ae631d8102ebc9960c3e48794cf", + "transactionType": "CREATE", + "contractName": "StateView", + "contractAddress": "0xfb3e0c6f74eb1a21cc1da29aec80d2dfe6c9a317", + "function": null, + "arguments": [ + "0x39BF2eFF94201cfAA471932655404F63315147a4" + ], + "transaction": { + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "gas": "0x1466f7", + "value": "0x0", + "input": "0x60a0604052348015600e575f80fd5b5060405161127d38038061127d833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f80fd5b81516001600160a01b0381168114605f575f80fd5b9392505050565b6080516111b26100cb5f395f818161027f0152818161032801528181610374015281816103c60152818161041901528181610468015281816104b801528181610505015281816105560152818161059a015281816105f0015261063201526111b25ff3fe608060405234801561000f575f80fd5b50600436106100cf575f3560e01c8063c815641c1161007d578063dc4c90d311610058578063dc4c90d31461027a578063f0928f29146102c6578063fa6793d5146102fa575f80fd5b8063c815641c146101d1578063caedab5414610229578063dacf1d2f14610267575f80fd5b80638a2bb9e6116100ad5780638a2bb9e61461016a57806397fd7b421461017d5780639ec538c8146101be575f80fd5b80631c7ccb4c146100d357806353e9c1fb146100f95780637c40f1fe14610121575b5f80fd5b6100e66100e1366004610ecb565b61030d565b6040519081526020015b60405180910390f35b61010c610107366004610f15565b610358565b604080519283526020830191909152016100f0565b61013461012f366004610f4e565b6103a8565b604080516fffffffffffffffffffffffffffffffff9095168552600f9390930b60208501529183015260608201526080016100f0565b61010c610178366004610f4e565b6103fd565b61019061018b366004610f78565b61044b565b604080516fffffffffffffffffffffffffffffffff90941684526020840192909252908201526060016100f0565b61010c6101cc366004610f98565b61049c565b6101e46101df366004610f98565b6104e7565b6040805173ffffffffffffffffffffffffffffffffffffffff909516855260029390930b602085015262ffffff918216928401929092521660608201526080016100f0565b61023c610237366004610f4e565b61053a565b604080516fffffffffffffffffffffffffffffffff9093168352600f9190910b6020830152016100f0565b610190610275366004610faf565b61057d565b6102a17f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100f0565b6102d96102d4366004610f78565b6105d5565b6040516fffffffffffffffffffffffffffffffff90911681526020016100f0565b6102d9610308366004610f98565b610617565b5f61034f73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168484610658565b90505b92915050565b5f8061039c73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001686868661075b565b91509150935093915050565b5f8080806103ed73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001687876107f7565b9299919850965090945092505050565b5f8061044073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001685856108fa565b915091509250929050565b5f808061048f73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001686866109e8565b9250925092509250925092565b5f806104de73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001684610acf565b91509150915091565b5f80808061052b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001686610b48565b93509350935093509193509193565b5f8061044073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168585610c2d565b5f80806105c473ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168989898989610cf1565b925092509250955095509592505050565b5f61034f73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168484610d48565b5f61035273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001683610ded565b5f8061066384610e06565b90505f61067160058361101a565b60408051600187900b60208201529081018290529091505f90606001604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00181529082905280516020909101207f1e2eaeaf00000000000000000000000000000000000000000000000000000000825260048201819052915073ffffffffffffffffffffffffffffffffffffffff881690631e2eaeaf90602401602060405180830381865afa15801561072c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107509190611052565b979650505050505050565b5f805f806107698888610acf565b915091505f8061077a8a8a8a6108fa565b915091505f8061078b8c8c8b6108fa565b915091505f61079a8d8d610b48565b50509150508a60020b8160020b12156107bc57828503985081840397506107e7565b8960020b8160020b126107d857848303985083820397506107e7565b82858803039850818487030397505b5050505050505094509492505050565b5f805f805f6108068787610e42565b6040517f35fd631a00000000000000000000000000000000000000000000000000000000815260048101829052600360248201529091505f9073ffffffffffffffffffffffffffffffffffffffff8a16906335fd631a906044015f60405180830381865afa15801561087a573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526108bf9190810190611096565b602081015160408201516060909201516fffffffffffffffffffffffffffffffff82169c60809290921d9b5091995090975095505050505050565b5f805f6109078585610e42565b90505f73ffffffffffffffffffffffffffffffffffffffff87166335fd631a61093184600161101a565b60405160e083901b7fffffffff000000000000000000000000000000000000000000000000000000001681526004810191909152600260248201526044015b5f60405180830381865afa15801561098a573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526109cf9190810190611096565b6020810151604090910151909890975095505050505050565b5f805f806109f68686610e96565b6040517f35fd631a00000000000000000000000000000000000000000000000000000000815260048101829052600360248201529091505f9073ffffffffffffffffffffffffffffffffffffffff8916906335fd631a906044015f60405180830381865afa158015610a6a573d5f803e3d5ffd5b505050506040513d5f823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610aaf9190810190611096565b60208101516040820151606090920151909a919950975095505050505050565b5f805f610adb84610e06565b90505f610ae960018361101a565b6040517f35fd631a00000000000000000000000000000000000000000000000000000000815260048101829052600260248201529091505f9073ffffffffffffffffffffffffffffffffffffffff8816906335fd631a90604401610970565b5f805f805f610b5686610e06565b6040517f1e2eaeaf000000000000000000000000000000000000000000000000000000008152600481018290529091505f9073ffffffffffffffffffffffffffffffffffffffff891690631e2eaeaf90602401602060405180830381865afa158015610bc4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610be89190611052565b905073ffffffffffffffffffffffffffffffffffffffff811695508060a01c60020b945062ffffff8160b81c16935062ffffff8160d01c169250505092959194509250565b5f805f610c3a8585610e42565b6040517f1e2eaeaf000000000000000000000000000000000000000000000000000000008152600481018290529091505f9073ffffffffffffffffffffffffffffffffffffffff881690631e2eaeaf90602401602060405180830381865afa158015610ca8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ccc9190611052565b6fffffffffffffffffffffffffffffffff81169860809190911d975095505050505050565b60408051602681018390526006810184905260038101859052858152603a600c8201205f928201839052602082018390529082905281908190610d358a8a836109e8565b919c909b50909950975050505050505050565b5f80610d548484610e96565b6040517f1e2eaeaf0000000000000000000000000000000000000000000000000000000081526004810182905290915073ffffffffffffffffffffffffffffffffffffffff861690631e2eaeaf90602401602060405180830381865afa158015610dc0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610de49190611052565b95945050505050565b5f80610df883610e06565b90505f610d5460038361101a565b6040515f90610e25908390600690602001918252602082015260400190565b604051602081830303815290604052805190602001209050919050565b5f80610e4d84610e06565b90505f610e5b60048361101a565b60408051600287900b60208201529081018290529091506060015b604051602081830303815290604052805190602001209250505092915050565b5f80610ea184610e06565b90505f610eaf60068361101a565b6040805160208101879052908101829052909150606001610e76565b5f8060408385031215610edc575f80fd5b823591506020830135600181900b8114610ef4575f80fd5b809150509250929050565b8035600281900b8114610f10575f80fd5b919050565b5f805f60608486031215610f27575f80fd5b83359250610f3760208501610eff565b9150610f4560408501610eff565b90509250925092565b5f8060408385031215610f5f575f80fd5b82359150610f6f60208401610eff565b90509250929050565b5f8060408385031215610f89575f80fd5b50508035926020909101359150565b5f60208284031215610fa8575f80fd5b5035919050565b5f805f805f60a08688031215610fc3575f80fd5b85359450602086013573ffffffffffffffffffffffffffffffffffffffff81168114610fed575f80fd5b9350610ffb60408701610eff565b925061100960608701610eff565b949793965091946080013592915050565b80820180821115610352577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215611062575f80fd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f602082840312156110a6575f80fd5b815167ffffffffffffffff8111156110bc575f80fd5b8201601f810184136110cc575f80fd5b805167ffffffffffffffff8111156110e6576110e6611069565b8060051b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f830116810181811067ffffffffffffffff8211171561113157611131611069565b60405291825260208184018101929081018784111561114e575f80fd5b6020850194505b8385101561117157845180825260209586019590935001611155565b50969550505050505056fea2646970667358221220e3392799363126caf4101679e15da75a864c873bf04dacbe3be09a1b8b16869164736f6c634300081a003300000000000000000000000039bf2eff94201cfaa471932655404f63315147a4", + "nonce": "0x22", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x30c3f7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xb998c1967306d62218346705921f76c078f25ae631d8102ebc9960c3e48794cf", + "transactionIndex": "0x12", + "blockHash": "0x7b7a030204424316808e8c6bc75bd1b82297d58610f91337c3aadecc524c86be", + "blockNumber": "0xd423fa", + "gasUsed": "0xfb1ab", + "effectiveGasPrice": "0x2e83", + "from": "0x7024cc7e60d6560f0b5877da2bb921fcbf1f4375", + "to": null, + "contractAddress": "0xfb3e0c6f74eb1a21cc1da29aec80d2dfe6c9a317", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x19d5df2804", + "l1GasPrice": "0xb94e9f65", + "l1GasUsed": "0x7ea1" + } + ], + "libraries": [], + "pending": [], + "returns": { + "state": { + "internal_type": "contract StateView", + "value": "0xFB3e0C6F74eB1a21CC1Da29aeC80D2Dfe6C9a317" + } + }, + "timestamp": 1723573992, + "chain": 84532, + "commit": "d0faf02" +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 002bc8be1..30dff3ef4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,12 +1,19 @@ [profile.default] out = 'foundry-out' solc_version = '0.8.26' -optimizer_runs = 1_000_000 +optimizer_runs = 1 +via_ir = true ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] evm_version = "cancun" gas_limit = "3000000000" fuzz_runs = 10_000 +bytecode_hash = "none" + +[profile.debug] +via_ir = false +optimizer_runs = 200 +fuzz.runs = 100 [profile.ci] fuzz_runs = 100_000 diff --git a/lib/forge-gas-snapshot b/lib/forge-gas-snapshot new file mode 160000 index 000000000..9fc447c73 --- /dev/null +++ b/lib/forge-gas-snapshot @@ -0,0 +1 @@ +Subproject commit 9fc447c732c89b6dd6352c096042d8d82b44faed diff --git a/lib/v4-core b/lib/v4-core index 799dd2cb9..b619b6718 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 799dd2cb980319a8d3b827b6a7aa59a606634553 +Subproject commit b619b6718e31aa5b4fa0286520c455ceb950276d diff --git a/remappings.txt b/remappings.txt index e7868fe96..c4f006e70 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,6 +1,6 @@ @uniswap/v4-core/=lib/v4-core/ -forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/ +forge-gas-snapshot/=lib/forge-gas-snapshot/src/ ds-test/=lib/v4-core/lib/forge-std/lib/ds-test/src/ forge-std/=lib/v4-core/lib/forge-std/src/ openzeppelin-contracts/=lib/v4-core/lib/openzeppelin-contracts/ -solmate/=lib/v4-core/lib/solmate/ \ No newline at end of file +solmate/=lib/v4-core/lib/solmate/ diff --git a/script/01_PoolManager.s.sol b/script/01_PoolManager.s.sol new file mode 100644 index 000000000..3d9d570d8 --- /dev/null +++ b/script/01_PoolManager.s.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +import "forge-std/console2.sol"; + +contract DeployPoolManager is Script { + function setUp() public {} + + function run() public returns (IPoolManager manager) { + vm.startBroadcast(); + + manager = new PoolManager(address(this)); + console2.log("PoolManager", address(manager)); + + vm.stopBroadcast(); + } +} diff --git a/script/02_PoolModifyLiquidityTest.s.sol b/script/02_PoolModifyLiquidityTest.s.sol new file mode 100644 index 000000000..a2c4975ed --- /dev/null +++ b/script/02_PoolModifyLiquidityTest.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Script} from "forge-std/Script.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; + +import "forge-std/console2.sol"; + +contract DeployPoolModifyLiquidityTest is Script { + function setUp() public {} + + function run(address poolManager) public returns (PoolModifyLiquidityTest testModifyRouter) { + vm.broadcast(); + testModifyRouter = new PoolModifyLiquidityTest(IPoolManager(poolManager)); + console2.log("PoolModifyLiquidityTest", address(testModifyRouter)); + } +} diff --git a/script/03_PoolSwapTest.s.sol b/script/03_PoolSwapTest.s.sol new file mode 100644 index 000000000..acd650d25 --- /dev/null +++ b/script/03_PoolSwapTest.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Script} from "forge-std/Script.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; + +import "forge-std/console2.sol"; + +contract DeployPoolSwapTest is Script { + function setUp() public {} + + function run(address poolManager) public returns (PoolSwapTest testSwapRouter) { + vm.broadcast(); + testSwapRouter = new PoolSwapTest(IPoolManager(poolManager)); + console2.log("PoolSwapTest", address(testSwapRouter)); + } +} diff --git a/script/DeployPosm.s.sol b/script/DeployPosm.s.sol new file mode 100644 index 000000000..b87fbea0f --- /dev/null +++ b/script/DeployPosm.s.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/console2.sol"; +import "forge-std/Script.sol"; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {StateView} from "../src/lens/StateView.sol"; +import {PositionManager} from "../src/PositionManager.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {IPositionDescriptor} from "../src/interfaces/IPositionDescriptor.sol"; +import {PositionDescriptor} from "../src/PositionDescriptor.sol"; +import {IWETH9} from "../src/interfaces/external/IWETH9.sol"; + +contract DeployPosmTest is Script { + function setUp() public {} + + function run( + address poolManager, + address permit2, + uint256 unsubscribeGasLimit, + address wrappedNative, + string memory nativeCurrencyLabel + ) public returns (PositionDescriptor positionDescriptor, PositionManager posm) { + vm.startBroadcast(); + + positionDescriptor = new PositionDescriptor(IPoolManager(poolManager), wrappedNative, nativeCurrencyLabel); + console2.log("PositionDescriptor", address(positionDescriptor)); + + posm = new PositionManager{salt: hex"03"}( + IPoolManager(poolManager), + IAllowanceTransfer(permit2), + unsubscribeGasLimit, + IPositionDescriptor(address(positionDescriptor)), + IWETH9(wrappedNative) + ); + console2.log("PositionManager", address(posm)); + + vm.stopBroadcast(); + } +} diff --git a/script/DeployStateView.s.sol b/script/DeployStateView.s.sol index b48526bc9..9584099d2 100644 --- a/script/DeployStateView.s.sol +++ b/script/DeployStateView.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/console2.sol"; diff --git a/script/DeployV4Quoter.s.sol b/script/DeployV4Quoter.s.sol new file mode 100644 index 000000000..7cc61d5f9 --- /dev/null +++ b/script/DeployV4Quoter.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/console2.sol"; +import "forge-std/Script.sol"; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {V4Quoter} from "../src/lens/V4Quoter.sol"; + +contract DeployV4Quoter is Script { + function setUp() public {} + + function run(address poolManager) public returns (V4Quoter state) { + vm.startBroadcast(); + + // forge script --broadcast --sig 'run(address)' --rpc-url --private-key --verify script/DeployV4Quoter.s.sol:DeployV4Quoter + state = new V4Quoter(IPoolManager(poolManager)); + console2.log("V4Quoter", address(state)); + console2.log("PoolManager", address(state.poolManager())); + + vm.stopBroadcast(); + } +} diff --git a/src/PositionDescriptor.sol b/src/PositionDescriptor.sol new file mode 100644 index 000000000..021cbd5e5 --- /dev/null +++ b/src/PositionDescriptor.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPositionManager} from "./interfaces/IPositionManager.sol"; +import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol"; +import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol"; +import {Descriptor} from "./libraries/Descriptor.sol"; +import {CurrencyRatioSortOrder} from "./libraries/CurrencyRatioSortOrder.sol"; +import {SafeCurrencyMetadata} from "./libraries/SafeCurrencyMetadata.sol"; + +/// @title Describes NFT token positions +/// @notice Produces a string containing the data URI for a JSON metadata string +contract PositionDescriptor is IPositionDescriptor { + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + using PositionInfoLibrary for PositionInfo; + + error InvalidTokenId(uint256 tokenId); + + // mainnet addresses + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address private constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + address private constant TBTC = 0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa; + address private constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + + address public immutable wrappedNative; + string public nativeCurrencyLabel; + + IPoolManager public immutable poolManager; + + constructor(IPoolManager _poolManager, address _wrappedNative, string memory _nativeCurrencyLabel) { + poolManager = _poolManager; + wrappedNative = _wrappedNative; + nativeCurrencyLabel = _nativeCurrencyLabel; + } + + /// @inheritdoc IPositionDescriptor + function tokenURI(IPositionManager positionManager, uint256 tokenId) + external + view + override + returns (string memory) + { + (PoolKey memory poolKey, PositionInfo positionInfo) = positionManager.getPoolAndPositionInfo(tokenId); + if (positionInfo.poolId() == 0) { + revert InvalidTokenId(tokenId); + } + (, int24 tick,,) = poolManager.getSlot0(poolKey.toId()); + + address currency0 = Currency.unwrap(poolKey.currency0); + address currency1 = Currency.unwrap(poolKey.currency1); + + // If possible, flip currencies to get the larger currency as the base currency, so that the price (quote/base) is more readable + // flip if currency0 priority is greater than currency1 priority + bool _flipRatio = flipRatio(currency0, currency1); + + // If not flipped, quote currency is currency1, base currency is currency0 + // If flipped, quote currency is currency0, base currency is currency1 + address quoteCurrency = !_flipRatio ? currency1 : currency0; + address baseCurrency = !_flipRatio ? currency0 : currency1; + + return Descriptor.constructTokenURI( + Descriptor.ConstructTokenURIParams({ + tokenId: tokenId, + quoteCurrency: quoteCurrency, + baseCurrency: baseCurrency, + quoteCurrencySymbol: SafeCurrencyMetadata.currencySymbol(quoteCurrency, nativeCurrencyLabel), + baseCurrencySymbol: SafeCurrencyMetadata.currencySymbol(baseCurrency, nativeCurrencyLabel), + quoteCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(quoteCurrency), + baseCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(baseCurrency), + flipRatio: _flipRatio, + tickLower: positionInfo.tickLower(), + tickUpper: positionInfo.tickUpper(), + tickCurrent: tick, + tickSpacing: poolKey.tickSpacing, + fee: poolKey.fee, + poolManager: address(poolManager), + hooks: address(poolKey.hooks) + }) + ); + } + + /// @notice Returns true if currency0 has higher priority than currency1 + /// @param currency0 The first currency address + /// @param currency1 The second currency address + /// @return flipRatio True if currency0 has higher priority than currency1 + function flipRatio(address currency0, address currency1) public view returns (bool) { + return currencyRatioPriority(currency0) > currencyRatioPriority(currency1); + } + + /// @notice Returns the priority of a currency. + /// For certain currencies on mainnet, the smaller the currency, the higher the priority + /// @param currency The currency address + /// @return priority The priority of the currency + function currencyRatioPriority(address currency) public view returns (int256) { + // Currencies in order of priority on mainnet: USDC, USDT, DAI, (ETH, WETH), TBTC, WBTC + // wrapped native is different address on different chains. passed in constructor + + // native currency + if (currency == address(0) || currency == wrappedNative) { + return CurrencyRatioSortOrder.DENOMINATOR; + } + if (block.chainid == 1) { + if (currency == USDC) { + return CurrencyRatioSortOrder.NUMERATOR_MOST; + } else if (currency == USDT) { + return CurrencyRatioSortOrder.NUMERATOR_MORE; + } else if (currency == DAI) { + return CurrencyRatioSortOrder.NUMERATOR; + } else if (currency == TBTC) { + return CurrencyRatioSortOrder.DENOMINATOR_MORE; + } else if (currency == WBTC) { + return CurrencyRatioSortOrder.DENOMINATOR_MOST; + } + } + return 0; + } +} diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 32e95810d..f9dbd2e38 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; @@ -10,26 +10,93 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; -import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; -import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol"; import {ERC721Permit_v4} from "./base/ERC721Permit_v4.sol"; import {ReentrancyLock} from "./base/ReentrancyLock.sol"; import {IPositionManager} from "./interfaces/IPositionManager.sol"; import {Multicall_v4} from "./base/Multicall_v4.sol"; import {PoolInitializer} from "./base/PoolInitializer.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; -import {PositionConfig, PositionConfigLibrary} from "./libraries/PositionConfig.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {Actions} from "./libraries/Actions.sol"; import {Notifier} from "./base/Notifier.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; -import {INotifier} from "./interfaces/INotifier.sol"; import {Permit2Forwarder} from "./base/Permit2Forwarder.sol"; -import {SlippageCheckLibrary} from "./libraries/SlippageCheck.sol"; - +import {SlippageCheck} from "./libraries/SlippageCheck.sol"; +import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol"; +import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol"; +import {NativeWrapper} from "./base/NativeWrapper.sol"; +import {IWETH9} from "./interfaces/external/IWETH9.sol"; + +// 444444444 +// 444444444444 444444 +// 444 44 4444 +// 44 4 44 444 +// 44 44 44 44 +// 44 44 44 44 +// 44 444444 44 44 +// 444 4444 4444 44 +// 44 4444 444444444444444444 +// 44 44 4 44444 444444 +// 444444444444 44 4 444 44 +// 44 44444444 4 444 44 +// 444 44 44 444 +// 44 4 4444444444444444444444444 4444444444 4444 +// 44 44444444444444444444444444 444 44444 +// 444 44 44444444444444444444444444 +// 4444 444444444444444444444444 +// 4444 444444 444444444444444 +// 44444 444444 44444444444444444444444 +// 444444444444444 4 44444 44444444444444444444 +// 444 444444444444444444444444444 +// 444 44444 44444444444 44444444 +// 444 4 44444444444444 444444444 +// 4444 444 44 4444444444444 44444444 +// 44 44444 44444444 44444444444444444444 44444 +// 444 444444 4444 4444 444444444444444444 44 4444 +// 4444 44 44444 44444444444 444444444444444444444 44444444 +// 44444 4444 4444444444 444444444444444444444444 44444 +// 44444 44444 444 444444 4444444444444444444444444 44444 +// 4444 44 44 4 44444444444444444444444444 444 44444 +// 44444444 444 44 4 4 444444 4 44444444444444444444444444444 4444444 +// 444444 44 44444444444 44444444444444 444444444444444 444444 +// 444444 44 4444 44444 44 44444444444444444444444 4444444 44444 +// 44 444444 44 444444444 444 4444444444444444444444444444444444 4444444 +// 44 4444444444444 44 44 44 4444444444444444444444444444444 444444 +// 44 44444444444444444444444444 4 44 4444444444444444444444444444444 4 444444 +// 4 4444 4 4 4444444444444444444444444 44 4444444 +// 4444 4444444444444444444444444 4 4444 44444444 +// 4444 444444444444444444444444 44444 44444 4444444444 +// 44444 44 444444444444444444444444444444444444444444444444444444 +// 44444444444 4444444444444444444444444444444444444444444444444444444 +// 4444444444444 44444444444444444444444444444444444444444444444444444444 +// 444444444444444 444444444444444444444444444444444444444444444444444444444 +// 44444444444444444 4444444444444444444444444444444444444444444444444444444444 +// 44444444444444444 44444444444444444444444444444444444444444444444444444444 +// 44444444444444444444 444444444444444444444444444444444444444444444444444444444 +// 444444444444444444444 444444444444444444444444444444444444444444444444444444444 +// 444444444444444444444 4444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444444444444444444444444444444444444444 +// 444444444444444444444444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444444444444 444444444444444444 +// 444444444444444444444444444444444444444444444444 44444444444444444444 +// 444 444 444 44 444444444444444444444 4444 444444444444444444444 +// 444 444 44 44 44444444 4444444444444 44444444444444444444444 +// 444 444 4444 4444 4444444444444444 44444444444444444444444444 +// 4444444444444444444444444444444444444444 44444444444444444444444444444 +// 444 4444444444444444444444444 44444444444444444444444444444444 +// 4444444 444444444444 4444444444444444444444444444444444 +// 4444444444 44444444444444444444444444444444444 +// 444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444 +// 4444444444444444444 + +/// @notice The PositionManager (PosM) contract is responsible for creating liquidity positions on v4. +/// PosM mints and manages ERC721 tokens associated with each position. contract PositionManager is IPositionManager, ERC721Permit_v4, @@ -39,62 +106,71 @@ contract PositionManager is ReentrancyLock, BaseActionsRouter, Notifier, - Permit2Forwarder + Permit2Forwarder, + NativeWrapper { - using SafeTransferLib for *; - using CurrencyLibrary for Currency; using PoolIdLibrary for PoolKey; - using PositionConfigLibrary for *; using StateLibrary for IPoolManager; using TransientStateLibrary for IPoolManager; using SafeCast for uint256; using SafeCast for int256; using CalldataDecoder for bytes; - using SlippageCheckLibrary for BalanceDelta; + using SlippageCheck for BalanceDelta; + using PositionInfoLibrary for PositionInfo; + /// @inheritdoc IPositionManager /// @dev The ID of the next token that will be minted. Skips 0 uint256 public nextTokenId = 1; - mapping(uint256 tokenId => bytes32 config) private positionConfigs; + IPositionDescriptor public immutable tokenDescriptor; - constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2) + mapping(uint256 tokenId => PositionInfo info) public positionInfo; + mapping(bytes25 poolId => PoolKey poolKey) public poolKeys; + + constructor( + IPoolManager _poolManager, + IAllowanceTransfer _permit2, + uint256 _unsubscribeGasLimit, + IPositionDescriptor _tokenDescriptor, + IWETH9 _weth9 + ) BaseActionsRouter(_poolManager) Permit2Forwarder(_permit2) - ERC721Permit_v4("Uniswap V4 Positions NFT", "UNI-V4-POSM") - {} + ERC721Permit_v4("Uniswap v4 Positions NFT", "UNI-V4-POSM") + Notifier(_unsubscribeGasLimit) + NativeWrapper(_weth9) + { + tokenDescriptor = _tokenDescriptor; + } /// @notice Reverts if the deadline has passed /// @param deadline The timestamp at which the call is no longer valid, passed in by the caller modifier checkDeadline(uint256 deadline) { - if (block.timestamp > deadline) revert DeadlinePassed(); + if (block.timestamp > deadline) revert DeadlinePassed(deadline); _; } - // TODO: to be implemented after audits - function tokenURI(uint256) public pure override returns (string memory) { - return "https://example.com"; - } - /// @notice Reverts if the caller is not the owner or approved for the ERC721 token /// @param caller The address of the caller /// @param tokenId the unique identifier of the ERC721 token - /// @dev either msg.sender or _msgSender() is passed in as the caller - /// _msgSender() should ONLY be used if this is being called from within the unlockCallback - modifier onlyIfApproved(address caller, uint256 tokenId) { + /// @dev either msg.sender or msgSender() is passed in as the caller + /// msgSender() should ONLY be used if this is called from within the unlockCallback, unless the codepath has reentrancy protection + modifier onlyIfApproved(address caller, uint256 tokenId) override { if (!_isApprovedOrOwner(caller, tokenId)) revert NotApproved(caller); _; } - /// @notice Reverts if the hash of the config does not equal the saved hash - /// @param tokenId the unique identifier of the ERC721 token - /// @param config the PositionConfig to check against - modifier onlyValidConfig(uint256 tokenId, PositionConfig calldata config) { - if (positionConfigs.getConfigId(tokenId) != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); + /// @notice Enforces that the PoolManager is locked. + modifier onlyIfPoolManagerLocked() override { + if (poolManager.isUnlocked()) revert PoolManagerMustBeLocked(); _; } - /// @param unlockData is an encoding of actions, params, and currencies - /// @param deadline is the timestamp at which the unlockData will no longer be valid + function tokenURI(uint256 tokenId) public view override returns (string memory) { + return IPositionDescriptor(tokenDescriptor).tokenURI(this, tokenId); + } + + /// @inheritdoc IPositionManager function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable @@ -104,136 +180,174 @@ contract PositionManager is _executeActions(unlockData); } - /// @inheritdoc INotifier - function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber, bytes calldata data) + /// @inheritdoc IPositionManager + function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable - onlyIfApproved(msg.sender, tokenId) - onlyValidConfig(tokenId, config) + isNotLocked { - // call to _subscribe will revert if the user already has a sub - positionConfigs.setSubscribe(tokenId); - _subscribe(tokenId, config, subscriber, data); + _executeActionsWithoutUnlock(actions, params); } - /// @inheritdoc INotifier - function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) - external - payable - onlyIfApproved(msg.sender, tokenId) - onlyValidConfig(tokenId, config) - { - positionConfigs.setUnsubscribe(tokenId); - _unsubscribe(tokenId, config, data); + /// @inheritdoc BaseActionsRouter + function msgSender() public view override returns (address) { + return _getLocker(); } function _handleAction(uint256 action, bytes calldata params) internal virtual override { if (action < Actions.SETTLE) { if (action == Actions.INCREASE_LIQUIDITY) { + (uint256 tokenId, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) = + params.decodeModifyLiquidityParams(); + _increase(tokenId, liquidity, amount0Max, amount1Max, hookData); + return; + } else if (action == Actions.INCREASE_LIQUIDITY_FROM_DELTAS) { + (uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) = + params.decodeIncreaseLiquidityFromDeltasParams(); + _increaseFromDeltas(tokenId, amount0Max, amount1Max, hookData); + return; + } else if (action == Actions.DECREASE_LIQUIDITY) { + (uint256 tokenId, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) = + params.decodeModifyLiquidityParams(); + _decrease(tokenId, liquidity, amount0Min, amount1Min, hookData); + return; + } else if (action == Actions.MINT_POSITION) { ( - uint256 tokenId, - PositionConfig calldata config, + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, + address owner, bytes calldata hookData - ) = params.decodeModifyLiquidityParams(); - _increase(tokenId, config, liquidity, amount0Max, amount1Max, hookData); - } else if (action == Actions.DECREASE_LIQUIDITY) { - ( - uint256 tokenId, - PositionConfig calldata config, - uint256 liquidity, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) = params.decodeModifyLiquidityParams(); - _decrease(tokenId, config, liquidity, amount0Min, amount1Min, hookData); - } else if (action == Actions.MINT_POSITION) { + ) = params.decodeMintParams(); + _mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); + return; + } else if (action == Actions.MINT_POSITION_FROM_DELTAS) { ( - PositionConfig calldata config, - uint256 liquidity, + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, uint128 amount0Max, uint128 amount1Max, address owner, bytes calldata hookData - ) = params.decodeMintParams(); - _mint(config, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); + ) = params.decodeMintFromDeltasParams(); + _mintFromDeltas(poolKey, tickLower, tickUpper, amount0Max, amount1Max, _mapRecipient(owner), hookData); + return; } else if (action == Actions.BURN_POSITION) { // Will automatically decrease liquidity to 0 if the position is not already empty. - ( - uint256 tokenId, - PositionConfig calldata config, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) = params.decodeBurnParams(); - _burn(tokenId, config, amount0Min, amount1Min, hookData); - } else { - revert UnsupportedAction(action); + (uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) = + params.decodeBurnParams(); + _burn(tokenId, amount0Min, amount1Min, hookData); + return; } } else { - if (action == Actions.CLOSE_CURRENCY) { + if (action == Actions.SETTLE_PAIR) { + (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); + _settlePair(currency0, currency1); + return; + } else if (action == Actions.TAKE_PAIR) { + (Currency currency0, Currency currency1, address recipient) = params.decodeCurrencyPairAndAddress(); + _takePair(currency0, currency1, _mapRecipient(recipient)); + return; + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + return; + } else if (action == Actions.TAKE) { + (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + return; + } else if (action == Actions.CLOSE_CURRENCY) { Currency currency = params.decodeCurrency(); _close(currency); + return; } else if (action == Actions.CLEAR_OR_TAKE) { (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); _clearOrTake(currency, amountMax); - } else if (action == Actions.SETTLE) { - (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); - _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); - } else if (action == Actions.SETTLE_PAIR) { - (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); - _settlePair(currency0, currency1); - } else if (action == Actions.TAKE_PAIR) { - (Currency currency0, Currency currency1, address to) = params.decodeCurrencyPairAndAddress(); - _takePair(currency0, currency1, to); + return; } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, _mapRecipient(to)); - } else if (action == Actions.TAKE) { - (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); - _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); - } else { - revert UnsupportedAction(action); + return; + } else if (action == Actions.WRAP) { + uint256 amount = params.decodeUint256(); + _wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9)))); + return; + } else if (action == Actions.UNWRAP) { + uint256 amount = params.decodeUint256(); + _unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO)); + return; } } - } - - function msgSender() public view override returns (address) { - return _getLocker(); + revert UnsupportedAction(action); } /// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position function _increase( uint256 tokenId, - PositionConfig calldata config, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData - ) internal onlyValidConfig(tokenId, config) { + ) internal onlyIfApproved(msgSender(), tokenId) { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. - BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); - liquidityDelta.validateMaxInNegative(amount0Max, amount1Max); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); + } + + /// @dev The liquidity delta is derived from open deltas in the pool manager. + function _increaseFromDeltas(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) + internal + onlyIfApproved(msgSender(), tokenId) + { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + // Use the credit on the pool manager as the amounts for the mint. + uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(info.tickLower()), + TickMath.getSqrtPriceAtTick(info.tickUpper()), + _getFullCredit(poolKey.currency0), + _getFullCredit(poolKey.currency1) + ); + + // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); } /// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position function _decrease( uint256 tokenId, - PositionConfig calldata config, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData - ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { + ) internal onlyIfApproved(msgSender(), tokenId) { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + // Note: the tokenId is used as the salt. - BalanceDelta liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(info, poolKey, -(liquidity.toInt256()), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); } function _mint( - PositionConfig calldata config, + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, @@ -248,10 +362,95 @@ contract PositionManager is } _mint(owner, tokenId); - // _beforeModify is not called here because the tokenId is newly minted - BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); + // Initialize the position info + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + positionInfo[tokenId] = info; + + // Store the poolKey if it is not already stored. + // On UniswapV4, the minimum tick spacing is 1, which means that if the tick spacing is 0, the pool key has not been set. + bytes25 poolId = info.poolId(); + if (poolKeys[poolId].tickSpacing == 0) { + poolKeys[poolId] = poolKey; + } + + // fee delta can be ignored as this is a new position + (BalanceDelta liquidityDelta,) = + _modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData); liquidityDelta.validateMaxIn(amount0Max, amount1Max); - positionConfigs.setConfigId(tokenId, config); + } + + function _mintFromDeltas( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) internal { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + // Use the credit on the pool manager as the amounts for the mint. + uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + _getFullCredit(poolKey.currency0), + _getFullCredit(poolKey.currency1) + ); + + _mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData); + } + + /// @dev this is overloaded with ERC721Permit_v4._burn + function _burn(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) + internal + onlyIfApproved(msgSender(), tokenId) + { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + + uint256 liquidity = uint256(_getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper())); + + address owner = ownerOf(tokenId); + + // Clear the position info. + positionInfo[tokenId] = PositionInfoLibrary.EMPTY_POSITION_INFO; + // Burn the token. + _burn(tokenId); + + // Can only call modify if there is non zero liquidity. + BalanceDelta feesAccrued; + if (liquidity > 0) { + BalanceDelta liquidityDelta; + // do not use _modifyLiquidity as we do not need to notify on modification for burns. + (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams({ + tickLower: info.tickLower(), + tickUpper: info.tickUpper(), + liquidityDelta: -(liquidity.toInt256()), + salt: bytes32(tokenId) + }), + hookData + ); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); + } + + // deletes then notifies the subscriber + if (info.hasSubscriber()) _removeSubscriberAndNotifyBurn(tokenId, owner, info, liquidity, feesAccrued); + } + + function _settlePair(Currency currency0, Currency currency1) internal { + // the locker is the payer when settling + address caller = msgSender(); + _settle(currency0, caller, _getFullDebt(currency0)); + _settle(currency1, caller, _getFullDebt(currency1)); + } + + function _takePair(Currency currency0, Currency currency1, address recipient) internal { + _take(currency0, recipient, _getFullCredit(currency0)); + _take(currency1, recipient, _getFullCredit(currency1)); } function _close(Currency currency) internal { @@ -262,16 +461,19 @@ contract PositionManager is // the locker is the payer or receiver address caller = msgSender(); if (currencyDelta < 0) { + // Casting is safe due to limits on the total supply of a pool _settle(currency, caller, uint256(-currencyDelta)); - } else if (currencyDelta > 0) { + } else { _take(currency, caller, uint256(currencyDelta)); } } /// @dev integrators may elect to forfeit positive deltas with clear /// if the forfeit amount exceeds the user-specified max, the amount is taken instead + /// if there is no credit, no call is made. function _clearOrTake(Currency currency, uint256 amountMax) internal { uint256 delta = _getFullCredit(currency); + if (delta == 0) return; // forfeit the delta if its less than or equal to the user-specified limit if (delta <= amountMax) { @@ -281,102 +483,81 @@ contract PositionManager is } } - function _settlePair(Currency currency0, Currency currency1) internal { - // the locker is the payer when settling - address caller = msgSender(); - _settle(currency0, caller, _getFullDebt(currency0)); - _settle(currency1, caller, _getFullDebt(currency1)); - } - - function _takePair(Currency currency0, Currency currency1, address to) internal { - address recipient = _mapRecipient(to); - _take(currency0, recipient, _getFullCredit(currency0)); - _take(currency1, recipient, _getFullCredit(currency1)); - } - - /// @dev this is overloaded with ERC721Permit_v4._burn - function _burn( - uint256 tokenId, - PositionConfig calldata config, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { - uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); - - BalanceDelta liquidityDelta; - // Can only call modify if there is non zero liquidity. - if (liquidity > 0) { - liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); - } - - delete positionConfigs[tokenId]; - // Burn the token. - _burn(tokenId); + /// @notice Sweeps the entire contract balance of specified currency to the recipient + function _sweep(Currency currency, address to) internal { + uint256 balance = currency.balanceOfSelf(); + if (balance > 0) currency.transfer(to, balance); } + /// @dev if there is a subscriber attached to the position, this function will notify the subscriber function _modifyLiquidity( - PositionConfig calldata config, + PositionInfo info, + PoolKey memory poolKey, int256 liquidityChange, bytes32 salt, bytes calldata hookData - ) internal returns (BalanceDelta liquidityDelta) { - (liquidityDelta,) = poolManager.modifyLiquidity( - config.poolKey, + ) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) { + (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams({ - tickLower: config.tickLower, - tickUpper: config.tickUpper, + tickLower: info.tickLower(), + tickUpper: info.tickUpper(), liquidityDelta: liquidityChange, salt: salt }), hookData ); - if (positionConfigs.hasSubscriber(uint256(salt))) { - _notifyModifyLiquidity(uint256(salt), config, liquidityChange); + if (info.hasSubscriber()) { + _notifyModifyLiquidity(uint256(salt), liquidityChange, feesAccrued); } } - /// @notice Sweeps the entire contract balance of specified currency to the recipient - function _sweep(Currency currency, address to) internal { - uint256 balance = currency.balanceOfSelf(); - if (balance > 0) currency.transfer(to, balance); - } - // implementation of abstract function DeltaResolver._pay function _pay(Currency currency, address payer, uint256 amount) internal override { if (payer == address(this)) { - // TODO: currency is guaranteed to not be eth so the native check in transfer is not optimal. currency.transfer(address(poolManager), amount); } else { + // Casting from uint256 to uint160 is safe due to limits on the total supply of a pool permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency)); } } + /// @notice an internal helper used by Notifier + function _setSubscribed(uint256 tokenId) internal override { + positionInfo[tokenId] = positionInfo[tokenId].setSubscribe(); + } + + /// @notice an internal helper used by Notifier + function _setUnsubscribed(uint256 tokenId) internal override { + positionInfo[tokenId] = positionInfo[tokenId].setUnsubscribe(); + } + /// @dev overrides solmate transferFrom in case a notification to subscribers is needed - function transferFrom(address from, address to, uint256 id) public override { + /// @dev will revert if pool manager is locked + function transferFrom(address from, address to, uint256 id) public virtual override onlyIfPoolManagerLocked { super.transferFrom(from, to, id); - if (positionConfigs.hasSubscriber(id)) _notifyTransfer(id, from, to); + if (positionInfo[id].hasSubscriber()) _unsubscribe(id); } - function getPositionLiquidity(uint256 tokenId, PositionConfig calldata config) - public - view - returns (uint128 liquidity) - { - bytes32 positionId = - Position.calculatePositionKey(address(this), config.tickLower, config.tickUpper, bytes32(tokenId)); - liquidity = poolManager.getPositionLiquidity(config.poolKey.toId(), positionId); + /// @inheritdoc IPositionManager + function getPoolAndPositionInfo(uint256 tokenId) public view returns (PoolKey memory poolKey, PositionInfo info) { + info = positionInfo[tokenId]; + poolKey = poolKeys[info.poolId()]; } /// @inheritdoc IPositionManager - function getPositionConfigId(uint256 tokenId) external view returns (bytes32) { - return positionConfigs.getConfigId(tokenId); + function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity) { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + liquidity = _getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper()); } - /// @inheritdoc INotifier - function hasSubscriber(uint256 tokenId) external view returns (bool) { - return positionConfigs.hasSubscriber(tokenId); + function _getLiquidity(uint256 tokenId, PoolKey memory poolKey, int24 tickLower, int24 tickUpper) + internal + view + returns (uint128 liquidity) + { + bytes32 positionId = Position.calculatePositionKey(address(this), tickLower, tickUpper, bytes32(tokenId)); + liquidity = poolManager.getPositionLiquidity(poolKey.toId(), positionId); } } diff --git a/src/UniswapV4DeployerCompetition.sol b/src/UniswapV4DeployerCompetition.sol new file mode 100644 index 000000000..deec4177e --- /dev/null +++ b/src/UniswapV4DeployerCompetition.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {VanityAddressLib} from "./libraries/VanityAddressLib.sol"; +import {IUniswapV4DeployerCompetition} from "./interfaces/IUniswapV4DeployerCompetition.sol"; + +/// @title UniswapV4DeployerCompetition +/// @notice A contract to crowdsource a salt for the best Uniswap V4 address +contract UniswapV4DeployerCompetition is IUniswapV4DeployerCompetition { + using VanityAddressLib for address; + + /// @dev The salt for the best address found so far + bytes32 public bestAddressSalt; + /// @dev The submitter of the best address found so far + address public bestAddressSubmitter; + + /// @dev The deadline for the competition + uint256 public immutable competitionDeadline; + /// @dev The init code hash of the V4 contract + bytes32 public immutable initCodeHash; + + /// @dev The deployer who can initiate the deployment of the v4 PoolManager, until the exclusive deploy deadline. + /// @dev After this deadline anyone can deploy. + address public immutable deployer; + /// @dev The deadline for exclusive deployment by deployer after deadline + uint256 public immutable exclusiveDeployDeadline; + + constructor( + bytes32 _initCodeHash, + uint256 _competitionDeadline, + address _exclusiveDeployer, + uint256 _exclusiveDeployLength + ) { + initCodeHash = _initCodeHash; + competitionDeadline = _competitionDeadline; + exclusiveDeployDeadline = _competitionDeadline + _exclusiveDeployLength; + deployer = _exclusiveDeployer; + } + + /// @inheritdoc IUniswapV4DeployerCompetition + function updateBestAddress(bytes32 salt) external { + if (block.timestamp > competitionDeadline) { + revert CompetitionOver(block.timestamp, competitionDeadline); + } + + address saltSubAddress = address(bytes20(salt)); + if (saltSubAddress != msg.sender && saltSubAddress != address(0)) revert InvalidSender(salt, msg.sender); + + address newAddress = Create2.computeAddress(salt, initCodeHash); + address _bestAddress = bestAddress(); + if (!newAddress.betterThan(_bestAddress)) { + revert WorseAddress(newAddress, _bestAddress, newAddress.score(), _bestAddress.score()); + } + + bestAddressSalt = salt; + bestAddressSubmitter = msg.sender; + + emit NewAddressFound(newAddress, msg.sender, newAddress.score()); + } + + /// @inheritdoc IUniswapV4DeployerCompetition + function deploy(bytes memory bytecode) external { + if (keccak256(bytecode) != initCodeHash) { + revert InvalidBytecode(); + } + + if (block.timestamp <= competitionDeadline) { + revert CompetitionNotOver(block.timestamp, competitionDeadline); + } + + if (msg.sender != deployer && block.timestamp <= exclusiveDeployDeadline) { + // anyone can deploy after the deadline + revert NotAllowedToDeploy(msg.sender, deployer); + } + + // the owner of the contract must be encoded in the bytecode + Create2.deploy(0, bestAddressSalt, bytecode); + } + + /// @dev returns the best address found so far + function bestAddress() public view returns (address) { + return Create2.computeAddress(bestAddressSalt, initCodeHash); + } +} diff --git a/src/V4Router.sol b/src/V4Router.sol index 8f32d51d3..e149da215 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -1,30 +1,29 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; +import {PathKey, PathKeyLibrary} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; -import {BipsLibrary} from "./libraries/BipsLibrary.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; import {Actions} from "./libraries/Actions.sol"; -import {SafeCastTemp} from "./libraries/SafeCast.sol"; +import {ActionConstants} from "./libraries/ActionConstants.sol"; +import {BipsLibrary} from "./libraries/BipsLibrary.sol"; /// @title UniswapV4Router -/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools +/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap v4 pools /// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` /// An inheriting contract should call _executeActions at the point that they wish actions to be executed abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { - using SafeCastTemp for *; using SafeCast for *; - using PathKeyLib for PathKey; + using PathKeyLibrary for PathKey; using CalldataDecoder for bytes; using BipsLibrary for uint256; @@ -36,55 +35,59 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { if (action == Actions.SWAP_EXACT_IN) { IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); _swapExactInput(swapParams); + return; } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { IV4Router.ExactInputSingleParams calldata swapParams = params.decodeSwapExactInSingleParams(); _swapExactInputSingle(swapParams); + return; } else if (action == Actions.SWAP_EXACT_OUT) { IV4Router.ExactOutputParams calldata swapParams = params.decodeSwapExactOutParams(); _swapExactOutput(swapParams); + return; } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { IV4Router.ExactOutputSingleParams calldata swapParams = params.decodeSwapExactOutSingleParams(); _swapExactOutputSingle(swapParams); - } else { - revert UnsupportedAction(action); + return; } } else { - if (action == Actions.SETTLE_TAKE_PAIR) { - (Currency settleCurrency, Currency takeCurrency) = params.decodeCurrencyPair(); - _settle(settleCurrency, msgSender(), _getFullDebt(settleCurrency)); - _take(takeCurrency, msgSender(), _getFullCredit(takeCurrency)); - } else if (action == Actions.SETTLE_ALL) { + if (action == Actions.SETTLE_ALL) { (Currency currency, uint256 maxAmount) = params.decodeCurrencyAndUint256(); uint256 amount = _getFullDebt(currency); - if (amount > maxAmount) revert V4TooMuchRequested(); + if (amount > maxAmount) revert V4TooMuchRequested(maxAmount, amount); _settle(currency, msgSender(), amount); + return; } else if (action == Actions.TAKE_ALL) { (Currency currency, uint256 minAmount) = params.decodeCurrencyAndUint256(); uint256 amount = _getFullCredit(currency); - if (amount < minAmount) revert V4TooLittleReceived(); + if (amount < minAmount) revert V4TooLittleReceived(minAmount, amount); _take(currency, msgSender(), amount); + return; } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + return; } else if (action == Actions.TAKE) { (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + return; } else if (action == Actions.TAKE_PORTION) { (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _getFullCredit(currency).calculatePortion(bips)); - } else { - revert UnsupportedAction(action); + return; } } + revert UnsupportedAction(action); } function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { - uint128 amountIn = - _mapInputAmount(params.amountIn, params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1); - uint128 amountOut = _swap( - params.poolKey, params.zeroForOne, int256(-int128(amountIn)), params.sqrtPriceLimitX96, params.hookData - ).toUint128(); - if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(); + uint128 amountIn = params.amountIn; + if (amountIn == ActionConstants.OPEN_DELTA) { + amountIn = + _getFullCredit(params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1).toUint128(); + } + uint128 amountOut = + _swap(params.poolKey, params.zeroForOne, -int256(uint256(amountIn)), params.hookData).toUint128(); + if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(params.amountOutMinimum, amountOut); } function _swapExactInput(IV4Router.ExactInputParams calldata params) private { @@ -93,34 +96,34 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { uint256 pathLength = params.path.length; uint128 amountOut; Currency currencyIn = params.currencyIn; - uint128 amountIn = _mapInputAmount(params.amountIn, currencyIn); + uint128 amountIn = params.amountIn; + if (amountIn == ActionConstants.OPEN_DELTA) amountIn = _getFullCredit(currencyIn).toUint128(); PathKey calldata pathKey; for (uint256 i = 0; i < pathLength; i++) { pathKey = params.path[i]; (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(currencyIn); // The output delta will always be positive, except for when interacting with certain hook pools - amountOut = _swap(poolKey, zeroForOne, -int256(uint256(amountIn)), 0, pathKey.hookData).toUint128(); + amountOut = _swap(poolKey, zeroForOne, -int256(uint256(amountIn)), pathKey.hookData).toUint128(); amountIn = amountOut; currencyIn = pathKey.intermediateCurrency; } - if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(); + if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(params.amountOutMinimum, amountOut); } } function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams calldata params) private { + uint128 amountOut = params.amountOut; + if (amountOut == ActionConstants.OPEN_DELTA) { + amountOut = + _getFullDebt(params.zeroForOne ? params.poolKey.currency1 : params.poolKey.currency0).toUint128(); + } uint128 amountIn = ( - -_swap( - params.poolKey, - params.zeroForOne, - int256(int128(params.amountOut)), - params.sqrtPriceLimitX96, - params.hookData - ) + uint256(-int256(_swap(params.poolKey, params.zeroForOne, int256(uint256(amountOut)), params.hookData))) ).toUint128(); - if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(); + if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn); } function _swapExactOutput(IV4Router.ExactOutputParams calldata params) private { @@ -132,35 +135,34 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { Currency currencyOut = params.currencyOut; PathKey calldata pathKey; + if (amountOut == ActionConstants.OPEN_DELTA) { + amountOut = _getFullDebt(currencyOut).toUint128(); + } + for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); // The output delta will always be negative, except for when interacting with certain hook pools - amountIn = (-_swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData)).toUint128(); + amountIn = (uint256(-int256(_swap(poolKey, !oneForZero, int256(uint256(amountOut)), pathKey.hookData)))) + .toUint128(); amountOut = amountIn; currencyOut = pathKey.intermediateCurrency; } - if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(); + if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn); } } - function _swap( - PoolKey memory poolKey, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata hookData - ) private returns (int128 reciprocalAmount) { + function _swap(PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, bytes calldata hookData) + private + returns (int128 reciprocalAmount) + { + // for protection of exactOut swaps, sqrtPriceLimit is not exposed as a feature in this contract unchecked { BalanceDelta delta = poolManager.swap( poolKey, IPoolManager.SwapParams( - zeroForOne, - amountSpecified, - sqrtPriceLimitX96 == 0 - ? (zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1) - : sqrtPriceLimitX96 + zeroForOne, amountSpecified, zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 ), hookData ); diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 821fa0e09..8cb30f131 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -1,10 +1,9 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; -import {Actions} from "../libraries/Actions.sol"; import {ActionConstants} from "../libraries/ActionConstants.sol"; /// @notice Abstract contract for performing a combination of actions on Uniswap v4. @@ -32,26 +31,28 @@ abstract contract BaseActionsRouter is SafeCallback { function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { // abi.decode(data, (bytes, bytes[])); (bytes calldata actions, bytes[] calldata params) = data.decodeActionsRouterParams(); + _executeActionsWithoutUnlock(actions, params); + return ""; + } + function _executeActionsWithoutUnlock(bytes calldata actions, bytes[] calldata params) internal { uint256 numActions = actions.length; if (numActions != params.length) revert InputLengthMismatch(); for (uint256 actionIndex = 0; actionIndex < numActions; actionIndex++) { - uint256 action = uint256(uint8(actions[actionIndex])); + uint256 action = uint8(actions[actionIndex]); _handleAction(action, params[actionIndex]); } - - return ""; } /// @notice function to handle the parsing and execution of an action and its parameters function _handleAction(uint256 action, bytes calldata params) internal virtual; - /// @notice function that returns address considered executer of the actions + /// @notice function that returns address considered executor of the actions /// @dev The other context functions, _msgData and _msgValue, are not supported by this contract /// In many contracts this will be the address that calls the initial entry point that calls `_executeActions` - /// `msg.sender` shouldnt be used, as this will be the v4 pool manager contract that calls `unlockCallback` + /// `msg.sender` shouldn't be used, as this will be the v4 pool manager contract that calls `unlockCallback` /// If using ReentrancyLock.sol, this function can return _getLocker() function msgSender() public view virtual returns (address); diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol new file mode 100644 index 000000000..52bacdb3f --- /dev/null +++ b/src/base/BaseV4Quoter.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {QuoterRevert} from "../libraries/QuoterRevert.sol"; +import {SafeCallback} from "../base/SafeCallback.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + +abstract contract BaseV4Quoter is SafeCallback { + using QuoterRevert for *; + using PoolIdLibrary for PoolId; + + error NotEnoughLiquidity(PoolId poolId); + error NotSelf(); + error UnexpectedCallSuccess(); + + constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} + + /// @dev Only this address may call this function. Used to mimic internal functions, using an + /// external call to catch and parse revert reasons + modifier selfOnly() { + if (msg.sender != address(this)) revert NotSelf(); + _; + } + + function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).call(data); + // Every quote path gathers a quote, and then reverts either with QuoteSwap(quoteAmount) or alternative error + if (success) revert UnexpectedCallSuccess(); + // Bubble the revert string, whether a valid quote or an alternative error + returnData.bubbleReason(); + } + + /// @dev Execute a swap and return the balance delta + /// @notice if amountSpecified < 0, the swap is exactInput, otherwise exactOutput + function _swap(PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, bytes calldata hookData) + internal + returns (BalanceDelta swapDelta) + { + swapDelta = poolManager.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 + }), + hookData + ); + + // Check that the pool was not illiquid. + int128 amountSpecifiedActual = (zeroForOne == (amountSpecified < 0)) ? swapDelta.amount0() : swapDelta.amount1(); + if (amountSpecifiedActual != amountSpecified) { + revert NotEnoughLiquidity(poolKey.toId()); + } + } +} diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 4269092eb..bd0d39bf0 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -1,29 +1,31 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ImmutableState} from "./ImmutableState.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {ActionConstants} from "../libraries/ActionConstants.sol"; /// @notice Abstract contract used to sync, send, and settle funds to the pool manager /// @dev Note that sync() is called before any erc-20 transfer in `settle`. abstract contract DeltaResolver is ImmutableState { using TransientStateLibrary for IPoolManager; - using SafeCast for *; /// @notice Emitted trying to settle a positive delta. error DeltaNotPositive(Currency currency); /// @notice Emitted trying to take a negative delta. error DeltaNotNegative(Currency currency); + /// @notice Emitted when the contract does not have enough balance to wrap or unwrap. + error InsufficientBalance(); /// @notice Take an amount of currency out of the PoolManager /// @param currency Currency to take /// @param recipient Address to receive the currency /// @param amount Amount to take + /// @dev Returns early if the amount is 0 function _take(Currency currency, address recipient, uint256 amount) internal { + if (amount == 0) return; poolManager.take(currency, recipient, amount); } @@ -32,11 +34,14 @@ abstract contract DeltaResolver is ImmutableState { /// @param currency Currency to settle /// @param payer Address of the payer /// @param amount Amount to send + /// @dev Returns early if the amount is 0 function _settle(Currency currency, address payer, uint256 amount) internal { - if (currency.isNative()) { + if (amount == 0) return; + + poolManager.sync(currency); + if (currency.isAddressZero()) { poolManager.settle{value: amount}(); } else { - poolManager.sync(currency); _pay(currency, payer, amount); poolManager.settle(); } @@ -54,8 +59,9 @@ abstract contract DeltaResolver is ImmutableState { /// @return amount The amount owed by this contract as a uint256 function _getFullDebt(Currency currency) internal view returns (uint256 amount) { int256 _amount = poolManager.currencyDelta(address(this), currency); - // If the amount is negative, it should be settled not taken. + // If the amount is positive, it should be taken not settled. if (_amount > 0) revert DeltaNotNegative(currency); + // Casting is safe due to limits on the total supply of a pool amount = uint256(-_amount); } @@ -64,7 +70,7 @@ abstract contract DeltaResolver is ImmutableState { /// @return amount The amount owed to this contract as a uint256 function _getFullCredit(Currency currency) internal view returns (uint256 amount) { int256 _amount = poolManager.currencyDelta(address(this), currency); - // If the amount is negative, it should be taken not settled for. + // If the amount is negative, it should be settled not taken. if (_amount < 0) revert DeltaNotPositive(currency); amount = uint256(_amount); } @@ -75,29 +81,43 @@ abstract contract DeltaResolver is ImmutableState { return currency.balanceOfSelf(); } else if (amount == ActionConstants.OPEN_DELTA) { return _getFullDebt(currency); + } else { + return amount; } - return amount; } /// @notice Calculates the amount for a take action function _mapTakeAmount(uint256 amount, Currency currency) internal view returns (uint256) { if (amount == ActionConstants.OPEN_DELTA) { return _getFullCredit(currency); + } else { + return amount; } - return amount; } - /// @notice Calculates the amount for a swap action - /// @dev This is to be used for swaps where the input amount isn't known before the transaction, and - /// isn't possible using a v4 multi-hop command. - /// For example USDC-v2->DAI-v4->USDT. This intermediate DAI amount could be swapped using CONTRACT_BALANCE - /// or settled using CONTRACT_BALANCE then swapped using OPEN_DELTA. - function _mapInputAmount(uint128 amount, Currency currency) internal view returns (uint128) { + /// @notice Calculates the sanitized amount before wrapping/unwrapping. + /// @param inputCurrency The currency, either native or wrapped native, that this contract holds + /// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE, OPEN_DELTA or a specific amount + /// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager + function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency) + internal + view + returns (uint256) + { + // if wrapping, the balance in this contract is in ETH + // if unwrapping, the balance in this contract is in WETH + uint256 balance = inputCurrency.balanceOf(address(this)); if (amount == ActionConstants.CONTRACT_BALANCE) { - return currency.balanceOfSelf().toUint128(); - } else if (amount == ActionConstants.OPEN_DELTA) { - return _getFullCredit(currency).toUint128(); + // return early to avoid unnecessary balance check + return balance; + } + if (amount == ActionConstants.OPEN_DELTA) { + // if wrapping, the open currency on the PoolManager is WETH. + // if unwrapping, the open currency on the PoolManager is ETH. + // note that we use the DEBT amount. Positive deltas can be taken and then wrapped. + amount = _getFullDebt(outputCurrency); } + if (amount > balance) revert InsufficientBalance(); return amount; } } diff --git a/src/base/EIP712_v4.sol b/src/base/EIP712_v4.sol index 48e607bd8..e66261aa5 100644 --- a/src/base/EIP712_v4.sol +++ b/src/base/EIP712_v4.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import {IEIP712_v4} from "../interfaces/IEIP712_v4.sol"; /// @notice Generic EIP712 implementation /// @dev Maintains cross-chain replay protection in the event of a fork /// @dev Should not be delegatecall'd because DOMAIN_SEPARATOR returns the cached hash and does not recompute with the delegatecallers address -/// @dev Reference: https://github.com/Uniswap/permit2/blob/main/src/EIP712.sol -/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol +/// @dev Reference: https://github.com/Uniswap/permit2/blob/3f17e8db813189a03950dc7fc8382524a095c053/src/EIP712.sol +/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7bd2b2aaf68c21277097166a9a51eb72ae239b34/contracts/utils/cryptography/EIP712.sol contract EIP712_v4 is IEIP712_v4 { // Cache the domain separator as an immutable value, but also store the chain id that it // corresponds to, in order to invalidate the cached domain separator if the chain id changes. @@ -15,8 +15,8 @@ contract EIP712_v4 is IEIP712_v4 { uint256 private immutable _CACHED_CHAIN_ID; bytes32 private immutable _HASHED_NAME; - /// @dev equal to keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)") - bytes32 private constant _TYPE_HASH = 0x8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866; + bytes32 private constant _TYPE_HASH = + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); constructor(string memory name) { _HASHED_NAME = keccak256(bytes(name)); @@ -25,9 +25,9 @@ contract EIP712_v4 is IEIP712_v4 { _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(); } - /// @notice Returns the domain separator for the current chain. - /// @dev Uses cached version if chainid is unchanged from construction. - function DOMAIN_SEPARATOR() public view override returns (bytes32) { + /// @inheritdoc IEIP712_v4 + function DOMAIN_SEPARATOR() public view returns (bytes32) { + // uses cached version if chainid is unchanged from construction return block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : _buildDomainSeparator(); } @@ -46,6 +46,11 @@ contract EIP712_v4 is IEIP712_v4 { mstore(add(fmp, 0x02), domainSeparator) mstore(add(fmp, 0x22), dataHash) digest := keccak256(fmp, 0x42) + + // now clean the memory we used + mstore(fmp, 0) // fmp held "\x19\x01", domainSeparator + mstore(add(fmp, 0x20), 0) // fmp+0x20 held domainSeparator, dataHash + mstore(add(fmp, 0x40), 0) // fmp+0x40 held dataHash } } } diff --git a/src/base/ERC721Permit_v4.sol b/src/base/ERC721Permit_v4.sol index 7c35a570b..e6ded04dd 100644 --- a/src/base/ERC721Permit_v4.sol +++ b/src/base/ERC721Permit_v4.sol @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {ERC721} from "solmate/src/tokens/ERC721.sol"; import {EIP712_v4} from "./EIP712_v4.sol"; -import {ERC721PermitHashLibrary} from "../libraries/ERC721PermitHash.sol"; +import {ERC721PermitHash} from "../libraries/ERC721PermitHash.sol"; import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; import {IERC721Permit_v4} from "../interfaces/IERC721Permit_v4.sol"; @@ -17,26 +17,63 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde /// @notice Computes the nameHash and versionHash constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) EIP712_v4(name_) {} + /// @notice Checks if the block's timestamp is before a signature's deadline + modifier checkSignatureDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert SignatureDeadlineExpired(); + _; + } + /// @inheritdoc IERC721Permit_v4 function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) external payable + checkSignatureDeadline(deadline) { - if (block.timestamp > deadline) revert DeadlineExpired(); - - address owner = ownerOf(tokenId); - if (spender == owner) revert NoSelfPermit(); + // the .verify function checks the owner is non-0 + address owner = _ownerOf[tokenId]; - bytes32 hash = ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline); - signature.verify(_hashTypedData(hash), owner); + bytes32 digest = ERC721PermitHash.hashPermit(spender, tokenId, nonce, deadline); + signature.verify(_hashTypedData(digest), owner); _useUnorderedNonce(owner, nonce); _approve(owner, spender, tokenId); } + /// @inheritdoc IERC721Permit_v4 + function permitForAll( + address owner, + address operator, + bool approved, + uint256 deadline, + uint256 nonce, + bytes calldata signature + ) external payable checkSignatureDeadline(deadline) { + bytes32 digest = ERC721PermitHash.hashPermitForAll(operator, approved, nonce, deadline); + signature.verify(_hashTypedData(digest), owner); + + _useUnorderedNonce(owner, nonce); + _approveForAll(owner, operator, approved); + } + + /// @notice Enable or disable approval for a third party ("operator") to manage + /// all of `msg.sender`'s assets + /// @dev Emits the ApprovalForAll event. The contract MUST allow + /// multiple operators per owner. + /// @dev Override Solmate's ERC721 setApprovalForAll so setApprovalForAll() and permit() share the _approveForAll method + /// @param operator Address to add to the set of authorized operators + /// @param approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address operator, bool approved) public override { + _approveForAll(msg.sender, operator, approved); + } + + function _approveForAll(address owner, address operator, bool approved) internal { + isApprovedForAll[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + /// @notice Change or reaffirm the approved address for an NFT /// @dev override Solmate's ERC721 approve so approve() and permit() share the _approve method - /// The zero address indicates there is no approved address + /// Passing a spender address of zero can be used to remove any outstanding approvals /// Throws error unless `msg.sender` is the current NFT owner, /// or an authorized operator of the current owner. /// @param spender The new approved NFT controller diff --git a/src/base/ImmutableState.sol b/src/base/ImmutableState.sol index dab1563cc..708a3d281 100644 --- a/src/base/ImmutableState.sol +++ b/src/base/ImmutableState.sol @@ -1,9 +1,13 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IImmutableState} from "../interfaces/IImmutableState.sol"; -contract ImmutableState { +/// @title Immutable State +/// @notice A collection of immutable state variables, commonly used across multiple contracts +contract ImmutableState is IImmutableState { + /// @inheritdoc IImmutableState IPoolManager public immutable poolManager; constructor(IPoolManager _poolManager) { diff --git a/src/base/Multicall_v4.sol b/src/base/Multicall_v4.sol index b6190a33b..e4fb7dacb 100644 --- a/src/base/Multicall_v4.sol +++ b/src/base/Multicall_v4.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; @@ -7,7 +7,7 @@ import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; /// @notice Enables calling multiple methods in a single call to the contract abstract contract Multicall_v4 is IMulticall_v4 { /// @inheritdoc IMulticall_v4 - function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { (bool success, bytes memory result) = address(this).delegatecall(data[i]); diff --git a/src/base/NativeWrapper.sol b/src/base/NativeWrapper.sol new file mode 100644 index 000000000..eb4eba073 --- /dev/null +++ b/src/base/NativeWrapper.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IWETH9} from "../interfaces/external/IWETH9.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; +import {ImmutableState} from "./ImmutableState.sol"; + +/// @title Native Wrapper +/// @notice Used for wrapping and unwrapping native +abstract contract NativeWrapper is ImmutableState { + /// @notice The address for WETH9 + IWETH9 public immutable WETH9; + + /// @notice Thrown when an unexpected address sends ETH to this contract + error InvalidEthSender(); + + constructor(IWETH9 _weth9) { + WETH9 = _weth9; + } + + /// @dev The amount should already be <= the current balance in this contract. + function _wrap(uint256 amount) internal { + if (amount > 0) WETH9.deposit{value: amount}(); + } + + /// @dev The amount should already be <= the current balance in this contract. + function _unwrap(uint256 amount) internal { + if (amount > 0) WETH9.withdraw(amount); + } + + receive() external payable { + if (msg.sender != address(WETH9) && msg.sender != address(poolManager)) revert InvalidEthSender(); + } +} diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 556ea308d..7e755c660 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -1,59 +1,132 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {ISubscriber} from "../interfaces/ISubscriber.sol"; -import {PositionConfig} from "../libraries/PositionConfig.sol"; -import {BipsLibrary} from "../libraries/BipsLibrary.sol"; - -import "../interfaces/INotifier.sol"; +import {INotifier} from "../interfaces/INotifier.sol"; +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; /// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers abstract contract Notifier is INotifier { - using BipsLibrary for uint256; - - error AlreadySubscribed(address subscriber); - - event Subscribed(uint256 tokenId, address subscriber); - event Unsubscribed(uint256 tokenId, address subscriber); + using CustomRevert for *; ISubscriber private constant NO_SUBSCRIBER = ISubscriber(address(0)); - // a percentage of the block.gaslimit denoted in BPS, used as the gas limit for subscriber calls - // 100 bps is 1% - // at 30M gas, the limit is 300K - uint256 private constant BLOCK_LIMIT_BPS = 100; + /// @inheritdoc INotifier + uint256 public immutable unsubscribeGasLimit; + /// @inheritdoc INotifier mapping(uint256 tokenId => ISubscriber subscriber) public subscriber; - function _subscribe(uint256 tokenId, PositionConfig memory config, address newSubscriber, bytes memory data) - internal + constructor(uint256 _unsubscribeGasLimit) { + unsubscribeGasLimit = _unsubscribeGasLimit; + } + + /// @notice Only allow callers that are approved as spenders or operators of the tokenId + /// @dev to be implemented by the parent contract (PositionManager) + /// @param caller the address of the caller + /// @param tokenId the tokenId of the position + modifier onlyIfApproved(address caller, uint256 tokenId) virtual; + + /// @notice Enforces that the PoolManager is locked. + modifier onlyIfPoolManagerLocked() virtual; + + function _setUnsubscribed(uint256 tokenId) internal virtual; + + function _setSubscribed(uint256 tokenId) internal virtual; + + /// @inheritdoc INotifier + function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) + external + payable + onlyIfPoolManagerLocked + onlyIfApproved(msg.sender, tokenId) { ISubscriber _subscriber = subscriber[tokenId]; - if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(address(_subscriber)); + if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(tokenId, address(_subscriber)); + _setSubscribed(tokenId); + subscriber[tokenId] = ISubscriber(newSubscriber); - ISubscriber(newSubscriber).notifySubscribe(tokenId, config, data); - emit Subscribed(tokenId, address(newSubscriber)); + bool success = _call(newSubscriber, abi.encodeCall(ISubscriber.notifySubscribe, (tokenId, data))); + + if (!success) { + newSubscriber.bubbleUpAndRevertWith(ISubscriber.notifySubscribe.selector, SubscriptionReverted.selector); + } + + emit Subscription(tokenId, newSubscriber); } - /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. - function _unsubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) internal { + /// @inheritdoc INotifier + function unsubscribe(uint256 tokenId) + external + payable + onlyIfPoolManagerLocked + onlyIfApproved(msg.sender, tokenId) + { + _unsubscribe(tokenId); + } + + function _unsubscribe(uint256 tokenId) internal { ISubscriber _subscriber = subscriber[tokenId]; - uint256 subscriberGasLimit = block.gaslimit.calculatePortion(BLOCK_LIMIT_BPS); + if (_subscriber == NO_SUBSCRIBER) revert NotSubscribed(); + _setUnsubscribed(tokenId); - try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config, data) {} catch {} + delete subscriber[tokenId]; + + if (address(_subscriber).code.length > 0) { + // require that the remaining gas is sufficient to notify the subscriber + // otherwise, users can select a gas limit where .notifyUnsubscribe hits OutOfGas yet the + // transaction/unsubscription can still succeed + if (gasleft() < unsubscribeGasLimit) GasLimitTooLow.selector.revertWith(); + try _subscriber.notifyUnsubscribe{gas: unsubscribeGasLimit}(tokenId) {} catch {} + } + emit Unsubscription(tokenId, address(_subscriber)); + } + + /// @dev note this function also deletes the subscriber address from the mapping + function _removeSubscriberAndNotifyBurn( + uint256 tokenId, + address owner, + PositionInfo info, + uint256 liquidity, + BalanceDelta feesAccrued + ) internal { + address _subscriber = address(subscriber[tokenId]); + + // remove the subscriber delete subscriber[tokenId]; - emit Unsubscribed(tokenId, address(_subscriber)); + + bool success = + _call(_subscriber, abi.encodeCall(ISubscriber.notifyBurn, (tokenId, owner, info, liquidity, feesAccrued))); + + if (!success) { + _subscriber.bubbleUpAndRevertWith(ISubscriber.notifyBurn.selector, BurnNotificationReverted.selector); + } } - function _notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) internal { - subscriber[tokenId].notifyModifyLiquidity(tokenId, config, liquidityChange); + function _notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) internal { + address _subscriber = address(subscriber[tokenId]); + + bool success = _call( + _subscriber, abi.encodeCall(ISubscriber.notifyModifyLiquidity, (tokenId, liquidityChange, feesAccrued)) + ); + + if (!success) { + _subscriber.bubbleUpAndRevertWith( + ISubscriber.notifyModifyLiquidity.selector, ModifyLiquidityNotificationReverted.selector + ); + } } - function _notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) internal { - subscriber[tokenId].notifyTransfer(tokenId, previousOwner, newOwner); + function _call(address target, bytes memory encodedCall) internal returns (bool success) { + if (target.code.length == 0) NoCodeSubscriber.selector.revertWith(); + assembly ("memory-safe") { + success := call(gas(), target, 0, add(encodedCall, 0x20), mload(encodedCall), 0, 0) + } } } diff --git a/src/base/Permit2Forwarder.sol b/src/base/Permit2Forwarder.sol index 41525c6e3..d5a749a85 100644 --- a/src/base/Permit2Forwarder.sol +++ b/src/base/Permit2Forwarder.sol @@ -1,11 +1,12 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; /// @notice PermitForwarder allows permitting this contract as a spender on permit2 /// @dev This contract does not enforce the spender to be this contract, but that is the intended use case contract Permit2Forwarder { + /// @notice the Permit2 contract to forward approvals IAllowanceTransfer public immutable permit2; constructor(IAllowanceTransfer _permit2) { @@ -14,19 +15,35 @@ contract Permit2Forwarder { /// @notice allows forwarding a single permit to permit2 /// @dev this function is payable to allow multicall with NATIVE based actions + /// @param owner the owner of the tokens + /// @param permitSingle the permit data + /// @param signature the signature of the permit; abi.encodePacked(r, s, v) function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature) external payable + returns (bytes memory err) { - permit2.permit(owner, permitSingle, signature); + // use try/catch in case an actor front-runs the permit, which would DOS multicalls + try permit2.permit(owner, permitSingle, signature) {} + catch (bytes memory reason) { + err = reason; + } } /// @notice allows forwarding batch permits to permit2 /// @dev this function is payable to allow multicall with NATIVE based actions + /// @param owner the owner of the tokens + /// @param _permitBatch a batch of approvals + /// @param signature the signature of the permit; abi.encodePacked(r, s, v) function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature) external payable + returns (bytes memory err) { - permit2.permit(owner, _permitBatch, signature); + // use try/catch in case an actor front-runs the permit, which would DOS multicalls + try permit2.permit(owner, _permitBatch, signature) {} + catch (bytes memory reason) { + err = reason; + } } } diff --git a/src/base/PoolInitializer.sol b/src/base/PoolInitializer.sol index bb75a3d9b..fea603402 100644 --- a/src/base/PoolInitializer.sol +++ b/src/base/PoolInitializer.sol @@ -1,15 +1,24 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {ImmutableState} from "./ImmutableState.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +/// @title Pool Initializer +/// @notice Initializes a Uniswap v4 Pool +/// @dev Enables create pool + mint liquidity in a single transaction with multicall abstract contract PoolInitializer is ImmutableState { - function initializePool(PoolKey calldata key, uint160 sqrtPriceX96, bytes calldata hookData) - external - returns (int24) - { - return poolManager.initialize(key, sqrtPriceX96, hookData); + /// @notice Initialize a Uniswap v4 Pool + /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max + /// @param key the PoolKey of the pool to initialize + /// @param sqrtPriceX96 the initial sqrtPriceX96 of the pool + /// @return tick The current tick of the pool + function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24) { + try poolManager.initialize(key, sqrtPriceX96) returns (int24 tick) { + return tick; + } catch { + return type(int24).max; + } } } diff --git a/src/base/ReentrancyLock.sol b/src/base/ReentrancyLock.sol index 29fa3d34f..c1abf028f 100644 --- a/src/base/ReentrancyLock.sol +++ b/src/base/ReentrancyLock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Locker} from "../libraries/Locker.sol"; diff --git a/src/base/SafeCallback.sol b/src/base/SafeCallback.sol index 99942a9e6..45e1c6bf9 100644 --- a/src/base/SafeCallback.sol +++ b/src/base/SafeCallback.sol @@ -1,24 +1,30 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ImmutableState} from "./ImmutableState.sol"; +/// @title Safe Callback +/// @notice A contract that only allows the Uniswap v4 PoolManager to call the unlockCallback abstract contract SafeCallback is ImmutableState, IUnlockCallback { + /// @notice Thrown when calling unlockCallback where the caller is not PoolManager error NotPoolManager(); constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} - modifier onlyByPoolManager() { + /// @notice Only allow calls from the PoolManager contract + modifier onlyPoolManager() { if (msg.sender != address(poolManager)) revert NotPoolManager(); _; } - /// @dev We force the onlyByPoolManager modifier by exposing a virtual function after the onlyByPoolManager check. - function unlockCallback(bytes calldata data) external onlyByPoolManager returns (bytes memory) { + /// @inheritdoc IUnlockCallback + /// @dev We force the onlyPoolManager modifier by exposing a virtual function after the onlyPoolManager check. + function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) { return _unlockCallback(data); } + /// @dev to be implemented by the child contract, to safely guarantee the logic is only executed by the PoolManager function _unlockCallback(bytes calldata data) internal virtual returns (bytes memory); } diff --git a/src/base/UnorderedNonce.sol b/src/base/UnorderedNonce.sol index a1efe7698..b08b5d92c 100644 --- a/src/base/UnorderedNonce.sol +++ b/src/base/UnorderedNonce.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; /// @title Unordered Nonce /// @notice Contract state and methods for using unordered nonces in signatures @@ -10,9 +10,9 @@ contract UnorderedNonce { /// @dev word is at most type(uint248).max mapping(address owner => mapping(uint256 word => uint256 bitmap)) public nonces; - /// @notice Consume a nonce, reverting if its already been used + /// @notice Consume a nonce, reverting if it has already been used /// @param owner address, the owner/signer of the nonce - /// @param nonce uint256, the nonce to consume. the top 248 bits are the word, the bottom 8 bits indicate the bit position + /// @param nonce uint256, the nonce to consume. The top 248 bits are the word, the bottom 8 bits indicate the bit position function _useUnorderedNonce(address owner, uint256 nonce) internal { uint256 wordPos = nonce >> 8; uint256 bitPos = uint8(nonce); @@ -21,4 +21,11 @@ contract UnorderedNonce { uint256 flipped = nonces[owner][wordPos] ^= bit; if (flipped & bit == 0) revert NonceAlreadyUsed(); } + + /// @notice Revoke a nonce by spending it, preventing it from being used again + /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce + /// @dev payable so it can be multicalled with native-token related actions + function revokeNonce(uint256 nonce) external payable { + _useUnorderedNonce(msg.sender, nonce); + } } diff --git a/src/base/hooks/BaseHook.sol b/src/base/hooks/BaseHook.sol index 0c983cf6e..635602a63 100644 --- a/src/base/hooks/BaseHook.sol +++ b/src/base/hooks/BaseHook.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; @@ -9,6 +9,8 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; import {SafeCallback} from "../SafeCallback.sol"; +/// @title Base Hook +/// @notice abstract contract for hook implementations abstract contract BaseHook is IHooks, SafeCallback { error NotSelf(); error InvalidPool(); @@ -31,11 +33,14 @@ abstract contract BaseHook is IHooks, SafeCallback { _; } + /// @notice Returns a struct of permissions to signal which hook functions are to be implemented + /// @dev Used at deployment to validate the address correctly represents the expected permissions function getHookPermissions() public pure virtual returns (Hooks.Permissions memory); - // this function is virtual so that we can override it during testing, - // which allows us to deploy an implementation to any address - // and then etch the bytecode into the correct address + /// @notice Validates the deployed hook address agrees with the expected permissions of the hook + /// @dev this function is virtual so that we can override it during testing, + /// which allows us to deploy an implementation to any address + /// and then etch the bytecode into the correct address function validateHookAddress(BaseHook _this) internal pure virtual { Hooks.validateHookPermissions(_this, getHookPermissions()); } @@ -45,24 +50,22 @@ abstract contract BaseHook is IHooks, SafeCallback { if (success) return returnData; if (returnData.length == 0) revert LockFailure(); // if the call failed, bubble up the reason - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(returnData, 32), mload(returnData)) } } - function beforeInitialize(address, PoolKey calldata, uint160, bytes calldata) external virtual returns (bytes4) { + /// @inheritdoc IHooks + function beforeInitialize(address, PoolKey calldata, uint160) external virtual returns (bytes4) { revert HookNotImplemented(); } - function afterInitialize(address, PoolKey calldata, uint160, int24, bytes calldata) - external - virtual - returns (bytes4) - { + /// @inheritdoc IHooks + function afterInitialize(address, PoolKey calldata, uint160, int24) external virtual returns (bytes4) { revert HookNotImplemented(); } + /// @inheritdoc IHooks function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) external virtual @@ -71,6 +74,7 @@ abstract contract BaseHook is IHooks, SafeCallback { revert HookNotImplemented(); } + /// @inheritdoc IHooks function beforeRemoveLiquidity( address, PoolKey calldata, @@ -80,26 +84,31 @@ abstract contract BaseHook is IHooks, SafeCallback { revert HookNotImplemented(); } + /// @inheritdoc IHooks function afterAddLiquidity( address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, + BalanceDelta, bytes calldata ) external virtual returns (bytes4, BalanceDelta) { revert HookNotImplemented(); } + /// @inheritdoc IHooks function afterRemoveLiquidity( address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, + BalanceDelta, bytes calldata ) external virtual returns (bytes4, BalanceDelta) { revert HookNotImplemented(); } + /// @inheritdoc IHooks function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) external virtual @@ -108,6 +117,7 @@ abstract contract BaseHook is IHooks, SafeCallback { revert HookNotImplemented(); } + /// @inheritdoc IHooks function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) external virtual @@ -116,6 +126,7 @@ abstract contract BaseHook is IHooks, SafeCallback { revert HookNotImplemented(); } + /// @inheritdoc IHooks function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) external virtual @@ -124,6 +135,7 @@ abstract contract BaseHook is IHooks, SafeCallback { revert HookNotImplemented(); } + /// @inheritdoc IHooks function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) external virtual diff --git a/src/interfaces/IEIP712_v4.sol b/src/interfaces/IEIP712_v4.sol index 622f7c068..70c4faa5e 100644 --- a/src/interfaces/IEIP712_v4.sol +++ b/src/interfaces/IEIP712_v4.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +/// @notice This interface is used for an EIP712 implementation interface IEIP712_v4 { + /// @notice Returns the domain separator for the current chain. + /// @return bytes32 The domain separator function DOMAIN_SEPARATOR() external view returns (bytes32); } diff --git a/src/interfaces/IERC721Permit_v4.sol b/src/interfaces/IERC721Permit_v4.sol index 3fa2894f7..e15e00bd8 100644 --- a/src/interfaces/IERC721Permit_v4.sol +++ b/src/interfaces/IERC721Permit_v4.sol @@ -1,10 +1,10 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.7.5; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; /// @title ERC721 with permit /// @notice Extension to ERC721 that includes a permit function for signature based approvals interface IERC721Permit_v4 { - error DeadlineExpired(); + error SignatureDeadlineExpired(); error NoSelfPermit(); error Unauthorized(); @@ -12,9 +12,27 @@ interface IERC721Permit_v4 { /// @param spender The account that is being approved /// @param tokenId The ID of the token that is being approved for spending /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v) /// @dev payable so it can be multicalled with NATIVE related actions function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) external payable; + + /// @notice Set an operator with full permission to an owner's tokens via signature + /// @param owner The address that is setting the operator + /// @param operator The address that will be set as an operator for the owner + /// @param approved The permission to set on the operator + /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word + /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v) + /// @dev payable so it can be multicalled with NATIVE related actions + function permitForAll( + address owner, + address operator, + bool approved, + uint256 deadline, + uint256 nonce, + bytes calldata signature + ) external payable; } diff --git a/src/interfaces/IImmutableState.sol b/src/interfaces/IImmutableState.sol new file mode 100644 index 000000000..83f7cf034 --- /dev/null +++ b/src/interfaces/IImmutableState.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +/// @title Interface for ImmutableState +interface IImmutableState { + /// @notice The Uniswap v4 PoolManager contract + function poolManager() external view returns (IPoolManager); +} diff --git a/src/interfaces/IMulticall_v4.sol b/src/interfaces/IMulticall_v4.sol index f1629f129..07c321b32 100644 --- a/src/interfaces/IMulticall_v4.sol +++ b/src/interfaces/IMulticall_v4.sol @@ -1,11 +1,12 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; /// @title Multicall_v4 interface /// @notice Enables calling multiple methods in a single call to the contract interface IMulticall_v4 { /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed - /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @dev The `msg.value` is passed onto all subcalls, even if a previous subcall has consumed the ether. + /// Subcalls can instead use `address(this).value` to see the available ETH, and consume it using {value: x}. /// @param data The encoded function data for each of the calls to make to this contract /// @return results The results from each of the calls passed in via data function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); diff --git a/src/interfaces/INotifier.sol b/src/interfaces/INotifier.sol index 33ec63f8e..a415e27b6 100644 --- a/src/interfaces/INotifier.sol +++ b/src/interfaces/INotifier.sol @@ -1,30 +1,53 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -import {PositionConfig} from "../libraries/PositionConfig.sol"; +import {ISubscriber} from "./ISubscriber.sol"; /// @notice This interface is used to opt in to sending updates to external contracts about position modifications or transfers interface INotifier { + /// @notice Thrown when unsubscribing without a subscriber + error NotSubscribed(); + /// @notice Thrown when a subscriber does not have code + error NoCodeSubscriber(); + /// @notice Thrown when a user specifies a gas limit too low to avoid valid unsubscribe notifications + error GasLimitTooLow(); + /// @notice Wraps the revert message of the subscriber contract on a reverting subscription + error SubscriptionReverted(address subscriber, bytes reason); + /// @notice Wraps the revert message of the subscriber contract on a reverting modify liquidity notification + error ModifyLiquidityNotificationReverted(address subscriber, bytes reason); + /// @notice Wraps the revert message of the subscriber contract on a reverting burn notification + error BurnNotificationReverted(address subscriber, bytes reason); + /// @notice Thrown when a tokenId already has a subscriber + error AlreadySubscribed(uint256 tokenId, address subscriber); + + /// @notice Emitted on a successful call to subscribe + event Subscription(uint256 indexed tokenId, address indexed subscriber); + /// @notice Emitted on a successful call to unsubscribe + event Unsubscription(uint256 indexed tokenId, address indexed subscriber); + + /// @notice Returns the subscriber for a respective position + /// @param tokenId the ERC721 tokenId + /// @return subscriber the subscriber contract + function subscriber(uint256 tokenId) external view returns (ISubscriber subscriber); + /// @notice Enables the subscriber to receive notifications for a respective position /// @param tokenId the ERC721 tokenId - /// @param config the corresponding PositionConfig for the tokenId - /// @param subscriber the address to notify + /// @param newSubscriber the address of the subscriber contract /// @param data caller-provided data that's forwarded to the subscriber contract /// @dev Calling subscribe when a position is already subscribed will revert /// @dev payable so it can be multicalled with NATIVE related actions - function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber, bytes calldata data) - external - payable; + /// @dev will revert if pool manager is locked + function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) external payable; /// @notice Removes the subscriber from receiving notifications for a respective position /// @param tokenId the ERC721 tokenId - /// @param config the corresponding PositionConfig for the tokenId - /// @param data caller-provided data that's forwarded to the subscriber contract + /// @dev Callers must specify a high gas limit (remaining gas should be higher than unsubscriberGasLimit) such that the subscriber can be notified /// @dev payable so it can be multicalled with NATIVE related actions - function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) external payable; + /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. + /// @dev will revert if pool manager is locked + function unsubscribe(uint256 tokenId) external payable; - /// @notice Returns whether a a position should call out to notify a subscribing contract on modification or transfer - /// @param tokenId the ERC721 tokenId - /// @return bool whether or not the position has a subscriber - function hasSubscriber(uint256 tokenId) external view returns (bool); + /// @notice Returns and determines the maximum allowable gas-used for notifying unsubscribe + /// @return uint256 the maximum gas limit when notifying a subscriber's `notifyUnsubscribe` function + function unsubscribeGasLimit() external view returns (uint256); } diff --git a/src/interfaces/IPositionDescriptor.sol b/src/interfaces/IPositionDescriptor.sol new file mode 100644 index 000000000..a1736ceab --- /dev/null +++ b/src/interfaces/IPositionDescriptor.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "./IPositionManager.sol"; + +/// @title Describes position NFT tokens via URI +interface IPositionDescriptor { + /// @notice Produces the URI describing a particular token ID + /// @dev Note this URI may be a data: URI with the JSON contents directly inlined + /// @param positionManager The position manager for which to describe the token + /// @param tokenId The ID of the token for which to produce a description, which may not be valid + /// @return The URI of the ERC721-compliant metadata + function tokenURI(IPositionManager positionManager, uint256 tokenId) external view returns (string memory); +} diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 3c45df7d5..86df7bbf9 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -1,35 +1,46 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {PositionConfig} from "../libraries/PositionConfig.sol"; +import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; import {INotifier} from "./INotifier.sol"; +import {IImmutableState} from "./IImmutableState.sol"; -interface IPositionManager is INotifier { +/// @title IPositionManager +/// @notice Interface for the PositionManager contract +interface IPositionManager is INotifier, IImmutableState { + /// @notice Thrown when the caller is not approved to modify a position error NotApproved(address caller); - error DeadlinePassed(); - error IncorrectPositionConfigForTokenId(uint256 tokenId); + /// @notice Thrown when the block.timestamp exceeds the user-provided deadline + error DeadlinePassed(uint256 deadline); + /// @notice Thrown when calling transfer, subscribe, or unsubscribe when the PoolManager is unlocked. + /// @dev This is to prevent hooks from being able to trigger notifications at the same time the position is being modified. + error PoolManagerMustBeLocked(); - /// @notice Batches many liquidity modification calls to pool manager - /// @param payload is an encoding of actions, and parameters for those actions + /// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity + /// @dev This is the standard entrypoint for the PositionManager + /// @param unlockData is an encoding of actions, and parameters for those actions /// @param deadline is the deadline for the batched actions to be executed - function modifyLiquidities(bytes calldata payload, uint256 deadline) external payable; + function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable; - function nextTokenId() external view returns (uint256); + /// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager + /// @dev This must be called by a contract that has already unlocked the v4 PoolManager + /// @param actions the actions to perform + /// @param params the parameters to provide for the actions + function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable; - /// @param tokenId the ERC721 tokenId - /// @return configId a truncated hash of the position's poolkey, tickLower, and tickUpper - /// @dev truncates the least significant bit of the hash - function getPositionConfigId(uint256 tokenId) external view returns (bytes32 configId); + /// @notice Used to get the ID that will be used for the next minted liquidity position + /// @return uint256 The next token ID + function nextTokenId() external view returns (uint256); /// @param tokenId the ERC721 tokenId - /// @param config the corresponding PositionConfig for the tokenId /// @return liquidity the position's liquidity, as a liquidityAmount /// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library - function getPositionLiquidity(uint256 tokenId, PositionConfig calldata config) - external - view - returns (uint128 liquidity); + function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity); + + /// @param tokenId the ERC721 tokenId + /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper) + /// @return poolKey the pool key of the position + function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo); } diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol deleted file mode 100644 index 3b96bca2d..000000000 --- a/src/interfaces/IQuoter.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; - -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {PathKey} from "../libraries/PathKey.sol"; - -/// @title Quoter Interface -/// @notice Supports quoting the delta amounts from exact input or exact output swaps. -/// @notice For each pool also tells you the number of initialized ticks loaded and the sqrt price of the pool after the swap. -/// @dev These functions are not marked view because they rely on calling non-view functions and reverting -/// to compute the result. They are also not gas efficient and should not be called on-chain. -interface IQuoter { - error InvalidLockCaller(); - error InvalidQuoteBatchParams(); - error InsufficientAmountOut(); - error LockFailure(); - error NotSelf(); - error UnexpectedRevertBytes(bytes revertData); - - struct PoolDeltas { - int128 currency0Delta; - int128 currency1Delta; - } - - struct QuoteExactSingleParams { - PoolKey poolKey; - bool zeroForOne; - uint128 exactAmount; - uint160 sqrtPriceLimitX96; - bytes hookData; - } - - struct QuoteExactParams { - Currency exactCurrency; - PathKey[] path; - uint128 exactAmount; - } - - /// @notice Returns the delta amounts for a given exact input swap of a single pool - /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` - /// poolKey The key for identifying a V4 pool - /// zeroForOne If the swap is from currency0 to currency1 - /// exactAmount The desired input amount - /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap - /// hookData arbitrary hookData to pass into the associated hooks - /// @return deltaAmounts Delta amounts resulted from the swap - /// @return sqrtPriceX96After The sqrt price of the pool after the swap - /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded - function quoteExactInputSingle(QuoteExactSingleParams calldata params) - external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); - - /// @notice Returns the delta amounts along the swap path for a given exact input swap - /// @param params the params for the quote, encoded as 'QuoteExactInputParams' - /// currencyIn The input currency of the swap - /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info - /// exactAmount The desired input amount - /// @return deltaAmounts Delta amounts along the path resulted from the swap - /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path - /// @return initializedTicksLoadedList List of the initialized ticks that the swap loaded for each pool in the path - function quoteExactInput(QuoteExactParams memory params) - external - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ); - - /// @notice Returns the delta amounts for a given exact output swap of a single pool - /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams` - /// poolKey The key for identifying a V4 pool - /// zeroForOne If the swap is from currency0 to currency1 - /// exactAmount The desired input amount - /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap - /// hookData arbitrary hookData to pass into the associated hooks - /// @return deltaAmounts Delta amounts resulted from the swap - /// @return sqrtPriceX96After The sqrt price of the pool after the swap - /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded - function quoteExactOutputSingle(QuoteExactSingleParams calldata params) - external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); - - /// @notice Returns the delta amounts along the swap path for a given exact output swap - /// @param params the params for the quote, encoded as 'QuoteExactOutputParams' - /// currencyOut The output currency of the swap - /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info - /// exactAmount The desired output amount - /// @return deltaAmounts Delta amounts along the path resulted from the swap - /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path - /// @return initializedTicksLoadedList List of the initialized ticks that the swap loaded for each pool in the path - function quoteExactOutput(QuoteExactParams memory params) - external - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ); -} diff --git a/src/interfaces/ISubscriber.sol b/src/interfaces/ISubscriber.sol index 81a68c0df..f2fc94df6 100644 --- a/src/interfaces/ISubscriber.sol +++ b/src/interfaces/ISubscriber.sol @@ -1,24 +1,29 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -import {PositionConfig} from "../libraries/PositionConfig.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; /// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager interface ISubscriber { /// @param tokenId the token ID of the position - /// @param config details about the position /// @param data additional data passed in by the caller - function notifySubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; + function notifySubscribe(uint256 tokenId, bytes memory data) external; + /// @notice Called when a position unsubscribes from the subscriber + /// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment) + /// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft() /// @param tokenId the token ID of the position - /// @param config details about the position - /// @param data additional data passed in by the caller - function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; + function notifyUnsubscribe(uint256 tokenId) external; + /// @notice Called when a position is burned /// @param tokenId the token ID of the position - /// @param config details about the position - /// @param liquidityChange the change in liquidity on the underlying position - function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) external; + /// @param owner the current owner of the tokenId + /// @param info information about the position + /// @param liquidity the amount of liquidity decreased in the position, may be 0 + /// @param feesAccrued the fees accrued by the position if liquidity was decreased + function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) + external; /// @param tokenId the token ID of the position - /// @param previousOwner address of the old owner - /// @param newOwner address of the new owner - function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external; + /// @param liquidityChange the change in liquidity on the underlying position + /// @param feesAccrued the fees to be collected from the position as a result of the modifyLiquidity call + function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external; } diff --git a/src/interfaces/IUniswapV4DeployerCompetition.sol b/src/interfaces/IUniswapV4DeployerCompetition.sol new file mode 100644 index 000000000..5bb1a4be1 --- /dev/null +++ b/src/interfaces/IUniswapV4DeployerCompetition.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/// @title UniswapV4DeployerCompetition +/// @notice A competition to deploy the UniswapV4 contract with the best address +interface IUniswapV4DeployerCompetition { + event NewAddressFound(address indexed bestAddress, address indexed submitter, uint256 score); + + error InvalidBytecode(); + error CompetitionNotOver(uint256 currentTime, uint256 deadline); + error CompetitionOver(uint256 currentTime, uint256 deadline); + error NotAllowedToDeploy(address sender, address deployer); + error WorseAddress(address newAddress, address bestAddress, uint256 newScore, uint256 bestScore); + error InvalidSender(bytes32 salt, address sender); + + /// @notice Updates the best address if the new address has a better vanity score + /// @param salt The salt to use to compute the new address with CREATE2 + /// @dev The first 20 bytes of the salt must be either address(0) or msg.sender + function updateBestAddress(bytes32 salt) external; + + /// @notice deploys the Uniswap v4 PoolManager contract + /// @param bytecode The bytecode of the Uniswap v4 PoolManager contract + /// @dev The bytecode must match the initCodeHash + function deploy(bytes memory bytecode) external; +} diff --git a/src/interfaces/IV4Quoter.sol b/src/interfaces/IV4Quoter.sol new file mode 100644 index 000000000..f502a4f44 --- /dev/null +++ b/src/interfaces/IV4Quoter.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PathKey} from "../libraries/PathKey.sol"; + +/// @title V4 Quoter Interface +/// @notice Supports quoting the delta amounts for exact input or exact output swaps. +/// @notice For each pool also tells you the sqrt price of the pool after the swap. +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +interface IV4Quoter { + struct QuoteExactSingleParams { + PoolKey poolKey; + bool zeroForOne; + uint128 exactAmount; + bytes hookData; + } + + struct QuoteExactParams { + Currency exactCurrency; + PathKey[] path; + uint128 exactAmount; + } + + /// @notice Returns the delta amounts for a given exact input swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// exactAmount The desired input amount + /// hookData arbitrary hookData to pass into the associated hooks + /// @return amountOut The output quote for the exactIn swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactInputSingle(QuoteExactSingleParams memory params) + external + returns (uint256 amountOut, uint256 gasEstimate); + + /// @notice Returns the delta amounts along the swap path for a given exact input swap + /// @param params the params for the quote, encoded as 'QuoteExactParams' + /// currencyIn The input currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// exactAmount The desired input amount + /// @return amountOut The output quote for the exactIn swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactInput(QuoteExactParams memory params) + external + returns (uint256 amountOut, uint256 gasEstimate); + + /// @notice Returns the delta amounts for a given exact output swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// exactAmount The desired output amount + /// hookData arbitrary hookData to pass into the associated hooks + /// @return amountIn The input quote for the exactOut swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactOutputSingle(QuoteExactSingleParams memory params) + external + returns (uint256 amountIn, uint256 gasEstimate); + + /// @notice Returns the delta amounts along the swap path for a given exact output swap + /// @param params the params for the quote, encoded as 'QuoteExactParams' + /// currencyOut The output currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// exactAmount The desired output amount + /// @return amountIn The input quote for the exactOut swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactOutput(QuoteExactParams memory params) + external + returns (uint256 amountIn, uint256 gasEstimate); +} diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 6991d72dc..f42715aa0 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -1,17 +1,18 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PathKey} from "../libraries/PathKey.sol"; +import {IImmutableState} from "./IImmutableState.sol"; /// @title IV4Router /// @notice Interface containing all the structs and errors for different v4 swap types -interface IV4Router { +interface IV4Router is IImmutableState { /// @notice Emitted when an exactInput swap does not receive its minAmountOut - error V4TooLittleReceived(); + error V4TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived); /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn - error V4TooMuchRequested(); + error V4TooMuchRequested(uint256 maxAmountInRequested, uint256 amountRequested); /// @notice Parameters for a single-hop exact-input swap struct ExactInputSingleParams { @@ -19,7 +20,6 @@ interface IV4Router { bool zeroForOne; uint128 amountIn; uint128 amountOutMinimum; - uint160 sqrtPriceLimitX96; bytes hookData; } @@ -37,7 +37,6 @@ interface IV4Router { bool zeroForOne; uint128 amountOut; uint128 amountInMaximum; - uint160 sqrtPriceLimitX96; bytes hookData; } diff --git a/src/interfaces/external/IERC20PermitAllowed.sol b/src/interfaces/external/IERC20PermitAllowed.sol deleted file mode 100644 index 7f2cf6570..000000000 --- a/src/interfaces/external/IERC20PermitAllowed.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.5.0; - -/// @title Interface for permit -/// @notice Interface used by DAI/CHAI for permit -interface IERC20PermitAllowed { - /// @notice Approve the spender to spend some tokens via the holder signature - /// @dev This is the permit interface used by DAI and CHAI - /// @param holder The address of the token holder, the token owner - /// @param spender The address of the token spender - /// @param nonce The holder's nonce, increases at each call to permit - /// @param expiry The timestamp at which the permit is no longer valid - /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function permit( - address holder, - address spender, - uint256 nonce, - uint256 expiry, - bool allowed, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} diff --git a/src/interfaces/external/IWETH9.sol b/src/interfaces/external/IWETH9.sol new file mode 100644 index 000000000..b8e68b363 --- /dev/null +++ b/src/interfaces/external/IWETH9.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title Interface for WETH9 +interface IWETH9 is IERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; +} diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol deleted file mode 100644 index fcf63d0c2..000000000 --- a/src/lens/Quoter.sol +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; - -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {IQuoter} from "../interfaces/IQuoter.sol"; -import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; -import {PathKey, PathKeyLib} from "../libraries/PathKey.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {SafeCallback} from "../base/SafeCallback.sol"; - -contract Quoter is IQuoter, SafeCallback { - using Hooks for IHooks; - using PoolIdLibrary for PoolKey; - using PathKeyLib for PathKey; - using StateLibrary for IPoolManager; - - /// @dev cache used to check a safety condition in exact output swaps. - uint128 private amountOutCached; - - /// @dev min valid reason is 3-words long - /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes - uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 96; - - struct QuoteResult { - int128[] deltaAmounts; - uint160[] sqrtPriceX96AfterList; - uint32[] initializedTicksLoadedList; - } - - struct QuoteCache { - BalanceDelta curDeltas; - uint128 prevAmount; - int128 deltaIn; - int128 deltaOut; - int24 tickBefore; - int24 tickAfter; - Currency prevCurrency; - uint160 sqrtPriceX96After; - } - - /// @dev Only this address may call this function - modifier selfOnly() { - if (msg.sender != address(this)) revert NotSelf(); - _; - } - - constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} - - /// @inheritdoc IQuoter - function quoteExactInputSingle(QuoteExactSingleParams memory params) - public - override - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) - { - try poolManager.unlock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} - catch (bytes memory reason) { - return _handleRevertSingle(reason); - } - } - - /// @inheritdoc IQuoter - function quoteExactInput(QuoteExactParams memory params) - external - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { - try poolManager.unlock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} - catch (bytes memory reason) { - return _handleRevert(reason); - } - } - - /// @inheritdoc IQuoter - function quoteExactOutputSingle(QuoteExactSingleParams memory params) - public - override - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) - { - try poolManager.unlock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} - catch (bytes memory reason) { - if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; - return _handleRevertSingle(reason); - } - } - - /// @inheritdoc IQuoter - function quoteExactOutput(QuoteExactParams memory params) - public - override - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { - try poolManager.unlock(abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} - catch (bytes memory reason) { - return _handleRevert(reason); - } - } - - function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { - (bool success, bytes memory returnData) = address(this).call(data); - if (success) return returnData; - if (returnData.length == 0) revert LockFailure(); - // if the call failed, bubble up the reason - /// @solidity memory-safe-assembly - assembly { - revert(add(returnData, 32), mload(returnData)) - } - } - - /// @dev check revert bytes and pass through if considered valid; otherwise revert with different message - function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { - if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { - revert UnexpectedRevertBytes(reason); - } - return reason; - } - - /// @dev parse revert bytes from a single-pool quote - function _handleRevertSingle(bytes memory reason) - private - pure - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) - { - reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96After, initializedTicksLoaded) = abi.decode(reason, (int128[], uint160, uint32)); - } - - /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, sqrtPriceX96After, and initializedTicksLoaded - function _handleRevert(bytes memory reason) - private - pure - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { - reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList) = - abi.decode(reason, (int128[], uint160[], uint32[])); - } - - /// @dev quote an ExactInput swap along a path of tokens, then revert with the result - function _quoteExactInput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { - uint256 pathLength = params.path.length; - - QuoteResult memory result = QuoteResult({ - deltaAmounts: new int128[](pathLength + 1), - sqrtPriceX96AfterList: new uint160[](pathLength), - initializedTicksLoadedList: new uint32[](pathLength) - }); - QuoteCache memory cache; - - for (uint256 i = 0; i < pathLength; i++) { - (PoolKey memory poolKey, bool zeroForOne) = - params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : cache.prevCurrency); - (, cache.tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - - (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = _swap( - poolKey, - zeroForOne, - -int256(int128(i == 0 ? params.exactAmount : cache.prevAmount)), - 0, - params.path[i].hookData - ); - - (cache.deltaIn, cache.deltaOut) = zeroForOne - ? (-cache.curDeltas.amount0(), -cache.curDeltas.amount1()) - : (-cache.curDeltas.amount1(), -cache.curDeltas.amount0()); - result.deltaAmounts[i] += cache.deltaIn; - result.deltaAmounts[i + 1] += cache.deltaOut; - - cache.prevAmount = zeroForOne ? uint128(cache.curDeltas.amount1()) : uint128(cache.curDeltas.amount0()); - cache.prevCurrency = params.path[i].intermediateCurrency; - result.sqrtPriceX96AfterList[i] = cache.sqrtPriceX96After; - result.initializedTicksLoadedList[i] = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, cache.tickBefore, cache.tickAfter); - } - bytes memory r = - abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList, result.initializedTicksLoadedList); - assembly { - revert(add(0x20, r), mload(r)) - } - } - - /// @dev quote an ExactInput swap on a pool, then revert with the result - function _quoteExactInputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { - (, int24 tickBefore,,) = poolManager.getSlot0(params.poolKey.toId()); - - (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( - params.poolKey, - params.zeroForOne, - -int256(int128(params.exactAmount)), - params.sqrtPriceLimitX96, - params.hookData - ); - - int128[] memory deltaAmounts = new int128[](2); - - deltaAmounts[0] = -deltas.amount0(); - deltaAmounts[1] = -deltas.amount1(); - - uint32 initializedTicksLoaded = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, params.poolKey, tickBefore, tickAfter); - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); - assembly { - revert(add(0x20, result), mload(result)) - } - } - - /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result - function _quoteExactOutput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { - uint256 pathLength = params.path.length; - - QuoteResult memory result = QuoteResult({ - deltaAmounts: new int128[](pathLength + 1), - sqrtPriceX96AfterList: new uint160[](pathLength), - initializedTicksLoadedList: new uint32[](pathLength) - }); - QuoteCache memory cache; - uint128 curAmountOut; - - for (uint256 i = pathLength; i > 0; i--) { - curAmountOut = i == pathLength ? params.exactAmount : cache.prevAmount; - amountOutCached = curAmountOut; - - (PoolKey memory poolKey, bool oneForZero) = PathKeyLib.getPoolAndSwapDirection( - params.path[i - 1], i == pathLength ? params.exactCurrency : cache.prevCurrency - ); - - (, cache.tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - - (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = - _swap(poolKey, !oneForZero, int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); - - // always clear because sqrtPriceLimitX96 is set to 0 always - delete amountOutCached; - (cache.deltaIn, cache.deltaOut) = !oneForZero - ? (-cache.curDeltas.amount0(), -cache.curDeltas.amount1()) - : (-cache.curDeltas.amount1(), -cache.curDeltas.amount0()); - result.deltaAmounts[i - 1] += cache.deltaIn; - result.deltaAmounts[i] += cache.deltaOut; - - cache.prevAmount = !oneForZero ? uint128(-cache.curDeltas.amount0()) : uint128(-cache.curDeltas.amount1()); - cache.prevCurrency = params.path[i - 1].intermediateCurrency; - result.sqrtPriceX96AfterList[i - 1] = cache.sqrtPriceX96After; - result.initializedTicksLoadedList[i - 1] = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, cache.tickBefore, cache.tickAfter); - } - bytes memory r = - abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList, result.initializedTicksLoadedList); - assembly { - revert(add(0x20, r), mload(r)) - } - } - - /// @dev quote an ExactOutput swap on a pool, then revert with the result - function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { - // if no price limit has been specified, cache the output amount for comparison in the swap callback - if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - - (, int24 tickBefore,,) = poolManager.getSlot0(params.poolKey.toId()); - (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( - params.poolKey, - params.zeroForOne, - int256(uint256(params.exactAmount)), - params.sqrtPriceLimitX96, - params.hookData - ); - - if (amountOutCached != 0) delete amountOutCached; - int128[] memory deltaAmounts = new int128[](2); - - deltaAmounts[0] = -deltas.amount0(); - deltaAmounts[1] = -deltas.amount1(); - - uint32 initializedTicksLoaded = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, params.poolKey, tickBefore, tickAfter); - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); - assembly { - revert(add(0x20, result), mload(result)) - } - } - - /// @dev Execute a swap and return the amounts delta, as well as relevant pool state - /// @notice if amountSpecified > 0, the swap is exactInput, otherwise exactOutput - function _swap( - PoolKey memory poolKey, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata hookData - ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - deltas = poolManager.swap( - poolKey, - IPoolManager.SwapParams({ - zeroForOne: zeroForOne, - amountSpecified: amountSpecified, - sqrtPriceLimitX96: _sqrtPriceLimitOrDefault(sqrtPriceLimitX96, zeroForOne) - }), - hookData - ); - // only exactOut case - if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? deltas.amount1() : deltas.amount0())) { - revert InsufficientAmountOut(); - } - (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(poolKey.toId()); - } - - /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction - function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { - return sqrtPriceLimitX96 == 0 - ? zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 - : sqrtPriceLimitX96; - } -} diff --git a/src/lens/StateView.sol b/src/lens/StateView.sol index e3cea895c..6527d2f72 100644 --- a/src/lens/StateView.sol +++ b/src/lens/StateView.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; @@ -102,7 +102,7 @@ contract StateView is ImmutableState { } /** - * @notice Retrieves total the liquidity of a pool. + * @notice Retrieves the total liquidity of a pool. * @dev Corresponds to pools[poolId].liquidity * @param poolId The ID of the pool. * @return liquidity The liquidity of the pool. @@ -161,7 +161,7 @@ contract StateView is ImmutableState { /** * @notice Retrieves the liquidity of a position. - * @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieiving liquidity as compared to getPositionInfo + * @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieving liquidity as compared to getPositionInfo * @param poolId The ID of the pool. * @param positionId The ID of the position. * @return liquidity The liquidity of the position. diff --git a/src/lens/V4Quoter.sol b/src/lens/V4Quoter.sol new file mode 100644 index 000000000..0f6b78648 --- /dev/null +++ b/src/lens/V4Quoter.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {IV4Quoter} from "../interfaces/IV4Quoter.sol"; +import {PathKey, PathKeyLibrary} from "../libraries/PathKey.sol"; +import {QuoterRevert} from "../libraries/QuoterRevert.sol"; +import {BaseV4Quoter} from "../base/BaseV4Quoter.sol"; + +contract V4Quoter is IV4Quoter, BaseV4Quoter { + using PathKeyLibrary for PathKey; + using QuoterRevert for *; + + constructor(IPoolManager _poolManager) BaseV4Quoter(_poolManager) {} + + /// @inheritdoc IV4Quoter + function quoteExactInputSingle(QuoteExactSingleParams memory params) + external + returns (uint256 amountOut, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountOut = reason.parseQuoteAmount(); + } + } + + /// @inheritdoc IV4Quoter + function quoteExactInput(QuoteExactParams memory params) + external + returns (uint256 amountOut, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountOut = reason.parseQuoteAmount(); + } + } + + /// @inheritdoc IV4Quoter + function quoteExactOutputSingle(QuoteExactSingleParams memory params) + external + returns (uint256 amountIn, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountIn = reason.parseQuoteAmount(); + } + } + + /// @inheritdoc IV4Quoter + function quoteExactOutput(QuoteExactParams memory params) + external + returns (uint256 amountIn, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountIn = reason.parseQuoteAmount(); + } + } + + /// @dev external function called within the _unlockCallback, to simulate an exact input swap, then revert with the result + function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { + uint256 pathLength = params.path.length; + BalanceDelta swapDelta; + uint128 amountIn = params.exactAmount; + Currency inputCurrency = params.exactCurrency; + PathKey calldata pathKey; + + for (uint256 i = 0; i < pathLength; i++) { + pathKey = params.path[i]; + (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(inputCurrency); + + swapDelta = _swap(poolKey, zeroForOne, -int256(int128(amountIn)), pathKey.hookData); + + amountIn = zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); + inputCurrency = pathKey.intermediateCurrency; + } + // amountIn after the loop actually holds the amountOut of the trade + amountIn.revertQuote(); + } + + /// @dev external function called within the _unlockCallback, to simulate a single-hop exact input swap, then revert with the result + function _quoteExactInputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { + BalanceDelta swapDelta = + _swap(params.poolKey, params.zeroForOne, -int256(int128(params.exactAmount)), params.hookData); + + // the output delta of a swap is positive + uint256 amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); + amountOut.revertQuote(); + } + + /// @dev external function called within the _unlockCallback, to simulate an exact output swap, then revert with the result + function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { + uint256 pathLength = params.path.length; + BalanceDelta swapDelta; + uint128 amountOut = params.exactAmount; + Currency outputCurrency = params.exactCurrency; + PathKey calldata pathKey; + + for (uint256 i = pathLength; i > 0; i--) { + pathKey = params.path[i - 1]; + (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); + + swapDelta = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), pathKey.hookData); + + amountOut = oneForZero ? uint128(-swapDelta.amount1()) : uint128(-swapDelta.amount0()); + + outputCurrency = pathKey.intermediateCurrency; + } + // amountOut after the loop exits actually holds the amountIn of the trade + amountOut.revertQuote(); + } + + /// @dev external function called within the _unlockCallback, to simulate a single-hop exact output swap, then revert with the result + function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { + BalanceDelta swapDelta = + _swap(params.poolKey, params.zeroForOne, int256(uint256(params.exactAmount)), params.hookData); + + // the input delta of a swap is negative so we must flip it + uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); + amountIn.revertQuote(); + } +} diff --git a/src/libraries/ActionConstants.sol b/src/libraries/ActionConstants.sol index 504d9052c..4c84e11a1 100644 --- a/src/libraries/ActionConstants.sol +++ b/src/libraries/ActionConstants.sol @@ -1,14 +1,20 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +/// @title Action Constants +/// @notice Common constants used in actions +/// @dev Constants are gas efficient alternatives to their literal values library ActionConstants { /// @notice used to signal that an action should use the input value of the open delta on the pool manager /// or of the balance that the contract holds uint128 internal constant OPEN_DELTA = 0; + /// @notice used to signal that an action should use the contract's entire balance of a currency /// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit. uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000; - /// @notice used to signal that the recipient of an action should be the msgSender of address(this) + /// @notice used to signal that the recipient of an action should be the msgSender address internal constant MSG_SENDER = address(1); + + /// @notice used to signal that the recipient of an action should be the address(this) address internal constant ADDRESS_THIS = address(2); } diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 0b2592dd6..f87038476 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -1,8 +1,9 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; /// @notice Library to define different pool actions. /// @dev These are suggested common commands, however additional commands should be defined as required +/// Some of these actions are not supported in the Router contracts or Position Manager contracts, but are left as they may be helpful commands for other peripheral contracts. library Actions { // pool actions // liquidity actions @@ -10,31 +11,39 @@ library Actions { uint256 constant DECREASE_LIQUIDITY = 0x01; uint256 constant MINT_POSITION = 0x02; uint256 constant BURN_POSITION = 0x03; + uint256 constant INCREASE_LIQUIDITY_FROM_DELTAS = 0x04; + uint256 constant MINT_POSITION_FROM_DELTAS = 0x05; + // swapping - uint256 constant SWAP_EXACT_IN_SINGLE = 0x04; - uint256 constant SWAP_EXACT_IN = 0x05; - uint256 constant SWAP_EXACT_OUT_SINGLE = 0x06; - uint256 constant SWAP_EXACT_OUT = 0x07; + uint256 constant SWAP_EXACT_IN_SINGLE = 0x06; + uint256 constant SWAP_EXACT_IN = 0x07; + uint256 constant SWAP_EXACT_OUT_SINGLE = 0x08; + uint256 constant SWAP_EXACT_OUT = 0x09; + // donate - uint256 constant DONATE = 0x08; + // note this is not supported in the position manager or router + uint256 constant DONATE = 0x0a; // closing deltas on the pool manager // settling - uint256 constant SETTLE = 0x09; - uint256 constant SETTLE_ALL = 0x10; - uint256 constant SETTLE_PAIR = 0x11; + uint256 constant SETTLE = 0x0b; + uint256 constant SETTLE_ALL = 0x0c; + uint256 constant SETTLE_PAIR = 0x0d; // taking - uint256 constant TAKE = 0x12; - uint256 constant TAKE_ALL = 0x13; - uint256 constant TAKE_PORTION = 0x14; - uint256 constant TAKE_PAIR = 0x15; + uint256 constant TAKE = 0x0e; + uint256 constant TAKE_ALL = 0x0f; + uint256 constant TAKE_PORTION = 0x10; + uint256 constant TAKE_PAIR = 0x11; + + uint256 constant CLOSE_CURRENCY = 0x12; + uint256 constant CLEAR_OR_TAKE = 0x13; + uint256 constant SWEEP = 0x14; - uint256 constant SETTLE_TAKE_PAIR = 0x16; - uint256 constant CLOSE_CURRENCY = 0x17; - uint256 constant CLEAR_OR_TAKE = 0x18; - uint256 constant SWEEP = 0x19; + uint256 constant WRAP = 0x15; + uint256 constant UNWRAP = 0x16; // minting/burning 6909s to close deltas - uint256 constant MINT_6909 = 0x20; - uint256 constant BURN_6909 = 0x21; + // note this is not supported in the position manager or router + uint256 constant MINT_6909 = 0x17; + uint256 constant BURN_6909 = 0x18; } diff --git a/src/libraries/AddressStringUtil.sol b/src/libraries/AddressStringUtil.sol new file mode 100644 index 000000000..3add136f8 --- /dev/null +++ b/src/libraries/AddressStringUtil.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title AddressStringUtil +/// @notice provides utility functions for converting addresses to strings +/// @dev Reference: https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/AddressStringUtil.sol +library AddressStringUtil { + error InvalidAddressLength(uint256 len); + + /// @notice Converts an address to the uppercase hex string, extracting only len bytes (up to 20, multiple of 2) + /// @param addr the address to convert + /// @param len the number of bytes to extract + /// @return the hex string + function toAsciiString(address addr, uint256 len) internal pure returns (string memory) { + if (!(len % 2 == 0 && len > 0 && len <= 40)) { + revert InvalidAddressLength(len); + } + + bytes memory s = new bytes(len); + uint256 addrNum = uint256(uint160(addr)); + for (uint256 i = 0; i < len / 2; i++) { + // shift right and truncate all but the least significant byte to extract the byte at position 19-i + uint8 b = uint8(addrNum >> (8 * (19 - i))); + // first hex character is the most significant 4 bits + uint8 hi = b >> 4; + // second hex character is the least significant 4 bits + uint8 lo = b - (hi << 4); + s[2 * i] = char(hi); + s[2 * i + 1] = char(lo); + } + return string(s); + } + + /// @notice Converts a value into is corresponding ASCII character for the hex representation + // hi and lo are only 4 bits and between 0 and 16 + // uses upper case for the characters + /// @param b the value to convert + /// @return c the ASCII character + function char(uint8 b) private pure returns (bytes1 c) { + if (b < 10) { + return bytes1(b + 0x30); + } else { + return bytes1(b + 0x37); + } + } +} diff --git a/src/libraries/BipsLibrary.sol b/src/libraries/BipsLibrary.sol index 32bd97fc8..d85486a82 100644 --- a/src/libraries/BipsLibrary.sol +++ b/src/libraries/BipsLibrary.sol @@ -1,8 +1,7 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; /// @title For calculating a percentage of an amount, using bips -// TODO: Post-audit move to core, as v4-core will use something similar. library BipsLibrary { uint256 internal constant BPS_DENOMINATOR = 10_000; diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index 197987d38..496852bdc 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {PositionConfig} from "./PositionConfig.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {IV4Router} from "../interfaces/IV4Router.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; /// @title Library for abi decoding in calldata library CalldataDecoder { @@ -11,68 +11,106 @@ library CalldataDecoder { error SliceOutOfBounds(); - /// @notice equivalent to SliceOutOfBounds.selector - bytes4 constant SLICE_ERROR_SELECTOR = 0x3b99b53d; + /// @notice mask used for offsets and lengths to ensure no overflow + /// @dev no sane abi encoding will pass in an offset or length greater than type(uint32).max + /// (note that this does deviate from standard solidity behavior and offsets/lengths will + /// be interpreted as mod type(uint32).max which will only impact malicious/buggy callers) + uint256 constant OFFSET_OR_LENGTH_MASK = 0xffffffff; + uint256 constant OFFSET_OR_LENGTH_MASK_AND_WORD_ALIGN = 0xffffffe0; - /// @dev equivalent to: abi.decode(params, (uint256[], bytes[])) in calldata + /// @notice equivalent to SliceOutOfBounds.selector, stored in least-significant bits + uint256 constant SLICE_ERROR_SELECTOR = 0x3b99b53d; + + /// @dev equivalent to: abi.decode(params, (bytes, bytes[])) in calldata (requires strict abi encoding) function decodeActionsRouterParams(bytes calldata _bytes) internal pure returns (bytes calldata actions, bytes[] calldata params) { assembly ("memory-safe") { - // The offset of the 0th element is 0, which stores the offset of the length pointer of actions array. - // The offset of the 1st element is 32, which stores the offset of the length pointer of params array. - let actionsPtr := add(_bytes.offset, calldataload(_bytes.offset)) - let paramsPtr := add(_bytes.offset, calldataload(add(_bytes.offset, 0x20))) - - // The length is stored as the first element - actions.length := calldataload(actionsPtr) - params.length := calldataload(paramsPtr) - - // The actual data is stored in the slot after the length - actions.offset := add(actionsPtr, 0x20) - params.offset := add(paramsPtr, 0x20) - - // Calculate how far `params` is into the provided bytes - let relativeOffset := sub(params.offset, _bytes.offset) - // Check that that isnt longer than the bytes themselves, or revert - if lt(_bytes.length, add(params.length, relativeOffset)) { + // Strict encoding requires that the data begin with: + // 0x00: 0x40 (offset to `actions.length`) + // 0x20: 0x60 + actions.length (offset to `params.length`) + // 0x40: `actions.length` + // 0x60: beginning of actions + + // Verify actions offset matches strict encoding + let invalidData := xor(calldataload(_bytes.offset), 0x40) + actions.offset := add(_bytes.offset, 0x60) + actions.length := and(calldataload(add(_bytes.offset, 0x40)), OFFSET_OR_LENGTH_MASK) + + // Round actions length up to be word-aligned, and add 0x60 (for the first 3 words of encoding) + let paramsLengthOffset := add(and(add(actions.length, 0x1f), OFFSET_OR_LENGTH_MASK_AND_WORD_ALIGN), 0x60) + // Verify params offset matches strict encoding + invalidData := or(invalidData, xor(calldataload(add(_bytes.offset, 0x20)), paramsLengthOffset)) + let paramsLengthPointer := add(_bytes.offset, paramsLengthOffset) + params.length := and(calldataload(paramsLengthPointer), OFFSET_OR_LENGTH_MASK) + params.offset := add(paramsLengthPointer, 0x20) + + // Expected offset for `params[0]` is params.length * 32 + // As the first `params.length` slots are pointers to each of the array element lengths + let tailOffset := shl(5, params.length) + let expectedOffset := tailOffset + + for { let offset := 0 } lt(offset, tailOffset) { offset := add(offset, 32) } { + let itemLengthOffset := calldataload(add(params.offset, offset)) + // Verify that the offset matches the expected offset from strict encoding + invalidData := or(invalidData, xor(itemLengthOffset, expectedOffset)) + let itemLengthPointer := add(params.offset, itemLengthOffset) + let length := + add(and(add(calldataload(itemLengthPointer), 0x1f), OFFSET_OR_LENGTH_MASK_AND_WORD_ALIGN), 0x20) + expectedOffset := add(expectedOffset, length) + } + + // if the data encoding was invalid, or the provided bytes string isnt as long as the encoding says, revert + if or(invalidData, lt(add(_bytes.length, _bytes.offset), add(params.offset, expectedOffset))) { mstore(0, SLICE_ERROR_SELECTOR) - revert(0, 0x04) + revert(0x1c, 4) } } } - /// @dev equivalent to: abi.decode(params, (uint256, PositionConfig, uint256, uint128, uint128, bytes)) in calldata + /// @dev equivalent to: abi.decode(params, (uint256, uint256, uint128, uint128, bytes)) in calldata function decodeModifyLiquidityParams(bytes calldata params) internal pure - returns ( - uint256 tokenId, - PositionConfig calldata config, - uint256 liquidity, - uint128 amount0, - uint128 amount1, - bytes calldata hookData - ) + returns (uint256 tokenId, uint256 liquidity, uint128 amount0, uint128 amount1, bytes calldata hookData) { + // no length check performed, as there is a length check in `toBytes` assembly ("memory-safe") { tokenId := calldataload(params.offset) - config := add(params.offset, 0x20) - liquidity := calldataload(add(params.offset, 0x100)) - amount0 := calldataload(add(params.offset, 0x120)) - amount1 := calldataload(add(params.offset, 0x140)) + liquidity := calldataload(add(params.offset, 0x20)) + amount0 := calldataload(add(params.offset, 0x40)) + amount1 := calldataload(add(params.offset, 0x60)) } - hookData = params.toBytes(11); + + hookData = params.toBytes(4); } - /// @dev equivalent to: abi.decode(params, (PositionConfig, uint256, uint128, uint128, address, bytes)) in calldata + /// @dev equivalent to: abi.decode(params, (uint256, uint128, uint128, bytes)) in calldata + function decodeIncreaseLiquidityFromDeltasParams(bytes calldata params) + internal + pure + returns (uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + tokenId := calldataload(params.offset) + amount0Max := calldataload(add(params.offset, 0x20)) + amount1Max := calldataload(add(params.offset, 0x40)) + } + + hookData = params.toBytes(3); + } + + /// @dev equivalent to: abi.decode(params, (PoolKey, int24, int24, uint256, uint128, uint128, address, bytes)) in calldata function decodeMintParams(bytes calldata params) internal pure returns ( - PositionConfig calldata config, + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, @@ -80,8 +118,11 @@ library CalldataDecoder { bytes calldata hookData ) { + // no length check performed, as there is a length check in `toBytes` assembly ("memory-safe") { - config := params.offset + poolKey := params.offset + tickLower := calldataload(add(params.offset, 0xa0)) + tickUpper := calldataload(add(params.offset, 0xc0)) liquidity := calldataload(add(params.offset, 0xe0)) amount0Max := calldataload(add(params.offset, 0x100)) amount1Max := calldataload(add(params.offset, 0x120)) @@ -90,27 +131,49 @@ library CalldataDecoder { hookData = params.toBytes(11); } - /// @dev equivalent to: abi.decode(params, (uint256, PositionConfig, uint128, uint128, bytes)) in calldata - function decodeBurnParams(bytes calldata params) + /// @dev equivalent to: abi.decode(params, (PoolKey, int24, int24, uint128, uint128, address, bytes)) in calldata + function decodeMintFromDeltasParams(bytes calldata params) internal pure returns ( - uint256 tokenId, - PositionConfig calldata config, - uint128 amount0Min, - uint128 amount1Min, + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint128 amount0Max, + uint128 amount1Max, + address owner, bytes calldata hookData ) { + // no length check performed, as there is a length check in `toBytes` assembly ("memory-safe") { - tokenId := calldataload(params.offset) - config := add(params.offset, 0x20) - amount0Min := calldataload(add(params.offset, 0x100)) - amount1Min := calldataload(add(params.offset, 0x120)) + poolKey := params.offset + tickLower := calldataload(add(params.offset, 0xa0)) + tickUpper := calldataload(add(params.offset, 0xc0)) + amount0Max := calldataload(add(params.offset, 0xe0)) + amount1Max := calldataload(add(params.offset, 0x100)) + owner := calldataload(add(params.offset, 0x120)) } + hookData = params.toBytes(10); } + /// @dev equivalent to: abi.decode(params, (uint256, uint128, uint128, bytes)) in calldata + function decodeBurnParams(bytes calldata params) + internal + pure + returns (uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + tokenId := calldataload(params.offset) + amount0Min := calldataload(add(params.offset, 0x20)) + amount1Min := calldataload(add(params.offset, 0x40)) + } + + hookData = params.toBytes(3); + } + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputParams)) function decodeSwapExactInParams(bytes calldata params) internal @@ -119,6 +182,12 @@ library CalldataDecoder { { // ExactInputParams is a variable length struct so we just have to look up its location assembly ("memory-safe") { + // only safety checks for the minimum length, where path is empty + // 0xa0 = 5 * 0x20 -> 3 elements, path offset, and path length 0 + if lt(params.length, 0xa0) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } swapParams := add(params.offset, calldataload(params.offset)) } } @@ -131,6 +200,12 @@ library CalldataDecoder { { // ExactInputSingleParams is a variable length struct so we just have to look up its location assembly ("memory-safe") { + // only safety checks for the minimum length, where hookData is empty + // 0x140 = 10 * 0x20 -> 8 elements, bytes offset, and bytes length 0 + if lt(params.length, 0x140) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } swapParams := add(params.offset, calldataload(params.offset)) } } @@ -143,11 +218,17 @@ library CalldataDecoder { { // ExactOutputParams is a variable length struct so we just have to look up its location assembly ("memory-safe") { + // only safety checks for the minimum length, where path is empty + // 0xa0 = 5 * 0x20 -> 3 elements, path offset, and path length 0 + if lt(params.length, 0xa0) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } swapParams := add(params.offset, calldataload(params.offset)) } } - /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputSingleParams)) + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactOutputSingleParams)) function decodeSwapExactOutSingleParams(bytes calldata params) internal pure @@ -155,6 +236,12 @@ library CalldataDecoder { { // ExactOutputSingleParams is a variable length struct so we just have to look up its location assembly ("memory-safe") { + // only safety checks for the minimum length, where hookData is empty + // 0x140 = 10 * 0x20 -> 8 elements, bytes offset, and bytes length 0 + if lt(params.length, 0x140) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } swapParams := add(params.offset, calldataload(params.offset)) } } @@ -162,6 +249,10 @@ library CalldataDecoder { /// @dev equivalent to: abi.decode(params, (Currency)) in calldata function decodeCurrency(bytes calldata params) internal pure returns (Currency currency) { assembly ("memory-safe") { + if lt(params.length, 0x20) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency := calldataload(params.offset) } } @@ -169,6 +260,10 @@ library CalldataDecoder { /// @dev equivalent to: abi.decode(params, (Currency, Currency)) in calldata function decodeCurrencyPair(bytes calldata params) internal pure returns (Currency currency0, Currency currency1) { assembly ("memory-safe") { + if lt(params.length, 0x40) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency0 := calldataload(params.offset) currency1 := calldataload(add(params.offset, 0x20)) } @@ -181,6 +276,10 @@ library CalldataDecoder { returns (Currency currency0, Currency currency1, address _address) { assembly ("memory-safe") { + if lt(params.length, 0x60) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency0 := calldataload(params.offset) currency1 := calldataload(add(params.offset, 0x20)) _address := calldataload(add(params.offset, 0x40)) @@ -194,6 +293,10 @@ library CalldataDecoder { returns (Currency currency, address _address) { assembly ("memory-safe") { + if lt(params.length, 0x40) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency := calldataload(params.offset) _address := calldataload(add(params.offset, 0x20)) } @@ -206,6 +309,10 @@ library CalldataDecoder { returns (Currency currency, address _address, uint256 amount) { assembly ("memory-safe") { + if lt(params.length, 0x60) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency := calldataload(params.offset) _address := calldataload(add(params.offset, 0x20)) amount := calldataload(add(params.offset, 0x40)) @@ -219,11 +326,26 @@ library CalldataDecoder { returns (Currency currency, uint256 amount) { assembly ("memory-safe") { + if lt(params.length, 0x40) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency := calldataload(params.offset) amount := calldataload(add(params.offset, 0x20)) } } + /// @dev equivalent to: abi.decode(params, (uint256)) in calldata + function decodeUint256(bytes calldata params) internal pure returns (uint256 amount) { + assembly ("memory-safe") { + if lt(params.length, 0x20) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + amount := calldataload(params.offset) + } + } + /// @dev equivalent to: abi.decode(params, (Currency, uint256, bool)) in calldata function decodeCurrencyUint256AndBool(bytes calldata params) internal @@ -231,44 +353,39 @@ library CalldataDecoder { returns (Currency currency, uint256 amount, bool boolean) { assembly ("memory-safe") { + if lt(params.length, 0x60) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } currency := calldataload(params.offset) amount := calldataload(add(params.offset, 0x20)) boolean := calldataload(add(params.offset, 0x40)) } } - /// @notice Decode the `_arg`-th element in `_bytes` as a dynamic array - /// @dev The decoding of `length` and `offset` is universal, - /// whereas the type declaration of `res` instructs the compiler how to read it. - /// @param _bytes The input bytes string to slice - /// @param _arg The index of the argument to extract - /// @return length Length of the array - /// @return offset Pointer to the data part of the array - function toLengthOffset(bytes calldata _bytes, uint256 _arg) - internal - pure - returns (uint256 length, uint256 offset) - { - uint256 relativeOffset; - assembly ("memory-safe") { - // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. - // shl(5, x) is equivalent to mul(32, x) - let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg)))) - length := calldataload(lengthPtr) - offset := add(lengthPtr, 0x20) - relativeOffset := sub(offset, _bytes.offset) - } - if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds(); - } - /// @notice Decode the `_arg`-th element in `_bytes` as `bytes` /// @param _bytes The input bytes string to extract a bytes string from /// @param _arg The index of the argument to extract function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) { - (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); + uint256 length; assembly ("memory-safe") { + // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. + // shl(5, x) is equivalent to mul(32, x) + let lengthPtr := + add(_bytes.offset, and(calldataload(add(_bytes.offset, shl(5, _arg))), OFFSET_OR_LENGTH_MASK)) + // the number of bytes in the bytes string + length := and(calldataload(lengthPtr), OFFSET_OR_LENGTH_MASK) + // the offset where the bytes string begins + let offset := add(lengthPtr, 0x20) + // assign the return parameters res.length := length res.offset := offset + + // if the provided bytes string isnt as long as the encoding says, revert + if lt(add(_bytes.length, _bytes.offset), add(length, offset)) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } } } } diff --git a/src/libraries/CurrencyRatioSortOrder.sol b/src/libraries/CurrencyRatioSortOrder.sol new file mode 100644 index 000000000..c02d1084f --- /dev/null +++ b/src/libraries/CurrencyRatioSortOrder.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title CurrencyRatioSortOrder +/// @notice Provides constants for sorting currencies when displaying price ratios +/// Currencies given larger values will be in the numerator of the price ratio +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/TokenRatioSortOrder.sol +library CurrencyRatioSortOrder { + int256 constant NUMERATOR_MOST = 300; + int256 constant NUMERATOR_MORE = 200; + int256 constant NUMERATOR = 100; + + int256 constant DENOMINATOR_MOST = -300; + int256 constant DENOMINATOR_MORE = -200; + int256 constant DENOMINATOR = -100; +} diff --git a/src/libraries/Descriptor.sol b/src/libraries/Descriptor.sol new file mode 100644 index 000000000..327937c8d --- /dev/null +++ b/src/libraries/Descriptor.sol @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; +import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol"; +import {SVG} from "./SVG.sol"; +import {HexStrings} from "./HexStrings.sol"; + +/// @title Descriptor +/// @notice Describes NFT token positions +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/NFTDescriptor.sol +library Descriptor { + using TickMath for int24; + using Strings for uint256; + using HexStrings for uint256; + using LPFeeLibrary for uint24; + + uint256 constant sqrt10X128 = 1076067327063303206878105757264492625226; + + struct ConstructTokenURIParams { + uint256 tokenId; + address quoteCurrency; + address baseCurrency; + string quoteCurrencySymbol; + string baseCurrencySymbol; + uint8 quoteCurrencyDecimals; + uint8 baseCurrencyDecimals; + bool flipRatio; + int24 tickLower; + int24 tickUpper; + int24 tickCurrent; + int24 tickSpacing; + uint24 fee; + address poolManager; + address hooks; + } + + /// @notice Constructs the token URI for a Uniswap v4 NFT + /// @param params Parameters needed to construct the token URI + /// @return The token URI as a string + function constructTokenURI(ConstructTokenURIParams memory params) internal pure returns (string memory) { + string memory name = generateName(params, feeToPercentString(params.fee)); + string memory descriptionPartOne = generateDescriptionPartOne( + escapeSpecialCharacters(params.quoteCurrencySymbol), + escapeSpecialCharacters(params.baseCurrencySymbol), + addressToString(params.poolManager) + ); + string memory descriptionPartTwo = generateDescriptionPartTwo( + params.tokenId.toString(), + escapeSpecialCharacters(params.baseCurrencySymbol), + params.quoteCurrency == address(0) ? "Native" : addressToString(params.quoteCurrency), + params.baseCurrency == address(0) ? "Native" : addressToString(params.baseCurrency), + params.hooks == address(0) ? "No Hook" : addressToString(params.hooks), + feeToPercentString(params.fee) + ); + string memory image = Base64.encode(bytes(generateSVGImage(params))); + + return string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( + abi.encodePacked( + '{"name":"', + name, + '", "description":"', + descriptionPartOne, + descriptionPartTwo, + '", "image": "', + "data:image/svg+xml;base64,", + image, + '"}' + ) + ) + ) + ) + ); + } + + /// @notice Escapes special characters in a string if they are present + function escapeSpecialCharacters(string memory symbol) internal pure returns (string memory) { + bytes memory symbolBytes = bytes(symbol); + uint8 specialCharCount = 0; + // count the amount of double quotes, form feeds, new lines, carriage returns, or tabs in the symbol + for (uint8 i = 0; i < symbolBytes.length; i++) { + if (isSpecialCharacter(symbolBytes[i])) { + specialCharCount++; + } + } + if (specialCharCount > 0) { + // create a new bytes array with enough space to hold the original bytes plus space for the backslashes to escape the special characters + bytes memory escapedBytes = new bytes(symbolBytes.length + specialCharCount); + uint256 index; + for (uint8 i = 0; i < symbolBytes.length; i++) { + // add a '\' before any double quotes, form feeds, new lines, carriage returns, or tabs + if (isSpecialCharacter(symbolBytes[i])) { + escapedBytes[index++] = "\\"; + } + // copy each byte from original string to the new array + escapedBytes[index++] = symbolBytes[i]; + } + return string(escapedBytes); + } + return symbol; + } + + /// @notice Generates the first part of the description for a Uniswap v4 NFT + /// @param quoteCurrencySymbol The symbol of the quote currency + /// @param baseCurrencySymbol The symbol of the base currency + /// @param poolManager The address of the pool manager + /// @return The first part of the description + function generateDescriptionPartOne( + string memory quoteCurrencySymbol, + string memory baseCurrencySymbol, + string memory poolManager + ) private pure returns (string memory) { + // displays quote currency first, then base currency + return string( + abi.encodePacked( + "This NFT represents a liquidity position in a Uniswap v4 ", + quoteCurrencySymbol, + "-", + baseCurrencySymbol, + " pool. ", + "The owner of this NFT can modify or redeem the position.\\n", + "\\nPool Manager Address: ", + poolManager, + "\\n", + quoteCurrencySymbol + ) + ); + } + + /// @notice Generates the second part of the description for a Uniswap v4 NFTs + /// @param tokenId The token ID + /// @param baseCurrencySymbol The symbol of the base currency + /// @param quoteCurrency The address of the quote currency + /// @param baseCurrency The address of the base currency + /// @param hooks The address of the hooks contract + /// @param feeTier The fee tier of the pool + /// @return The second part of the description + function generateDescriptionPartTwo( + string memory tokenId, + string memory baseCurrencySymbol, + string memory quoteCurrency, + string memory baseCurrency, + string memory hooks, + string memory feeTier + ) private pure returns (string memory) { + return string( + abi.encodePacked( + " Address: ", + quoteCurrency, + "\\n", + baseCurrencySymbol, + " Address: ", + baseCurrency, + "\\nHook Address: ", + hooks, + "\\nFee Tier: ", + feeTier, + "\\nToken ID: ", + tokenId, + "\\n\\n", + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ) + ); + } + + /// @notice Generates the name for a Uniswap v4 NFT + /// @param params Parameters needed to generate the name + /// @param feeTier The fee tier of the pool + /// @return The name of the NFT + function generateName(ConstructTokenURIParams memory params, string memory feeTier) + private + pure + returns (string memory) + { + // image shows in terms of price, ie quoteCurrency/baseCurrency + return string( + abi.encodePacked( + "Uniswap - ", + feeTier, + " - ", + escapeSpecialCharacters(params.quoteCurrencySymbol), + "/", + escapeSpecialCharacters(params.baseCurrencySymbol), + " - ", + tickToDecimalString( + !params.flipRatio ? params.tickLower : params.tickUpper, + params.tickSpacing, + params.baseCurrencyDecimals, + params.quoteCurrencyDecimals, + params.flipRatio + ), + "<>", + tickToDecimalString( + !params.flipRatio ? params.tickUpper : params.tickLower, + params.tickSpacing, + params.baseCurrencyDecimals, + params.quoteCurrencyDecimals, + params.flipRatio + ) + ) + ); + } + + struct DecimalStringParams { + // significant figures of decimal + uint256 sigfigs; + // length of decimal string + uint8 bufferLength; + // ending index for significant figures (funtion works backwards when copying sigfigs) + uint8 sigfigIndex; + // index of decimal place (0 if no decimal) + uint8 decimalIndex; + // start index for trailing/leading 0's for very small/large numbers + uint8 zerosStartIndex; + // end index for trailing/leading 0's for very small/large numbers + uint8 zerosEndIndex; + // true if decimal number is less than one + bool isLessThanOne; + // true if string should include "%" + bool isPercent; + } + + function generateDecimalString(DecimalStringParams memory params) private pure returns (string memory) { + bytes memory buffer = new bytes(params.bufferLength); + if (params.isPercent) { + buffer[buffer.length - 1] = "%"; + } + if (params.isLessThanOne) { + buffer[0] = "0"; + buffer[1] = "."; + } + + // add leading/trailing 0's + for (uint256 zerosCursor = params.zerosStartIndex; zerosCursor < params.zerosEndIndex + 1; zerosCursor++) { + // converts the ASCII code for 0 (which is 48) into a bytes1 to store in the buffer + buffer[zerosCursor] = bytes1(uint8(48)); + } + // add sigfigs + while (params.sigfigs > 0) { + if (params.decimalIndex > 0 && params.sigfigIndex == params.decimalIndex) { + buffer[params.sigfigIndex--] = "."; + } + buffer[params.sigfigIndex] = bytes1(uint8(48 + (params.sigfigs % 10))); + // can overflow when sigfigIndex = 0 + unchecked { + params.sigfigIndex--; + } + params.sigfigs /= 10; + } + return string(buffer); + } + + /// @notice Gets the price (quote/base) at a specific tick in decimal form + /// MIN or MAX are returned if tick is at the bottom or top of the price curve + /// @param tick The tick (either tickLower or tickUpper) + /// @param tickSpacing The tick spacing of the pool + /// @param baseCurrencyDecimals The decimals of the base currency + /// @param quoteCurrencyDecimals The decimals of the quote currency + /// @param flipRatio True if the ratio was flipped + /// @return The ratio value as a string + function tickToDecimalString( + int24 tick, + int24 tickSpacing, + uint8 baseCurrencyDecimals, + uint8 quoteCurrencyDecimals, + bool flipRatio + ) internal pure returns (string memory) { + if (tick == (TickMath.MIN_TICK / tickSpacing) * tickSpacing) { + return !flipRatio ? "MIN" : "MAX"; + } else if (tick == (TickMath.MAX_TICK / tickSpacing) * tickSpacing) { + return !flipRatio ? "MAX" : "MIN"; + } else { + uint160 sqrtRatioX96 = TickMath.getSqrtPriceAtTick(tick); + if (flipRatio) { + sqrtRatioX96 = uint160(uint256(1 << 192) / sqrtRatioX96); + } + return fixedPointToDecimalString(sqrtRatioX96, baseCurrencyDecimals, quoteCurrencyDecimals); + } + } + + function sigfigsRounded(uint256 value, uint8 digits) private pure returns (uint256, bool) { + bool extraDigit; + if (digits > 5) { + value = value / (10 ** (digits - 5)); + } + bool roundUp = value % 10 > 4; + value = value / 10; + if (roundUp) { + value = value + 1; + } + // 99999 -> 100000 gives an extra sigfig + if (value == 100000) { + value /= 10; + extraDigit = true; + } + return (value, extraDigit); + } + + /// @notice Adjusts the sqrt price for different currencies with different decimals + /// @param sqrtRatioX96 The sqrt price at a specific tick + /// @param baseCurrencyDecimals The decimals of the base currency + /// @param quoteCurrencyDecimals The decimals of the quote currency + /// @return adjustedSqrtRatioX96 The adjusted sqrt price + function adjustForDecimalPrecision(uint160 sqrtRatioX96, uint8 baseCurrencyDecimals, uint8 quoteCurrencyDecimals) + private + pure + returns (uint256 adjustedSqrtRatioX96) + { + uint256 difference = abs(int256(uint256(baseCurrencyDecimals)) - (int256(uint256(quoteCurrencyDecimals)))); + if (difference > 0 && difference <= 18) { + if (baseCurrencyDecimals > quoteCurrencyDecimals) { + adjustedSqrtRatioX96 = sqrtRatioX96 * (10 ** (difference / 2)); + if (difference % 2 == 1) { + adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, sqrt10X128, 1 << 128); + } + } else { + adjustedSqrtRatioX96 = sqrtRatioX96 / (10 ** (difference / 2)); + if (difference % 2 == 1) { + adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, 1 << 128, sqrt10X128); + } + } + } else { + adjustedSqrtRatioX96 = uint256(sqrtRatioX96); + } + } + + /// @notice Absolute value of a signed integer + /// @param x The signed integer + /// @return The absolute value of x + function abs(int256 x) private pure returns (uint256) { + return uint256(x >= 0 ? x : -x); + } + + function fixedPointToDecimalString(uint160 sqrtRatioX96, uint8 baseCurrencyDecimals, uint8 quoteCurrencyDecimals) + internal + pure + returns (string memory) + { + uint256 adjustedSqrtRatioX96 = + adjustForDecimalPrecision(sqrtRatioX96, baseCurrencyDecimals, quoteCurrencyDecimals); + uint256 value = FullMath.mulDiv(adjustedSqrtRatioX96, adjustedSqrtRatioX96, 1 << 64); + + bool priceBelow1 = adjustedSqrtRatioX96 < 2 ** 96; + if (priceBelow1) { + // 10 ** 43 is precision needed to retreive 5 sigfigs of smallest possible price + 1 for rounding + value = FullMath.mulDiv(value, 10 ** 44, 1 << 128); + } else { + // leave precision for 4 decimal places + 1 place for rounding + value = FullMath.mulDiv(value, 10 ** 5, 1 << 128); + } + + // get digit count + uint256 temp = value; + uint8 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + // don't count extra digit kept for rounding + digits = digits - 1; + + // address rounding + (uint256 sigfigs, bool extraDigit) = sigfigsRounded(value, digits); + if (extraDigit) { + digits++; + } + + DecimalStringParams memory params; + if (priceBelow1) { + // 7 bytes ( "0." and 5 sigfigs) + leading 0's bytes + params.bufferLength = uint8(uint8(7) + (uint8(43) - digits)); + params.zerosStartIndex = 2; + params.zerosEndIndex = uint8(uint256(43) - digits + 1); + params.sigfigIndex = uint8(params.bufferLength - 1); + } else if (digits >= 9) { + // no decimal in price string + params.bufferLength = uint8(digits - 4); + params.zerosStartIndex = 5; + params.zerosEndIndex = uint8(params.bufferLength - 1); + params.sigfigIndex = 4; + } else { + // 5 sigfigs surround decimal + params.bufferLength = 6; + params.sigfigIndex = 5; + params.decimalIndex = uint8(digits - 5 + 1); + } + params.sigfigs = sigfigs; + params.isLessThanOne = priceBelow1; + params.isPercent = false; + + return generateDecimalString(params); + } + + /// @notice Converts fee amount in pips to decimal string with percent sign + /// @param fee fee amount + /// @return fee as a decimal string with percent sign + function feeToPercentString(uint24 fee) internal pure returns (string memory) { + if (fee.isDynamicFee()) { + return "Dynamic"; + } + if (fee == 0) { + return "0%"; + } + uint24 temp = fee; + uint256 digits; + uint8 numSigfigs; + // iterates over each digit of fee by dividing temp by 10 in each iteration until temp becomes 0 + // calculates number of digits and number of significant figures (non-zero digits) + while (temp != 0) { + if (numSigfigs > 0) { + // count all digits preceding least significant figure + numSigfigs++; + } else if (temp % 10 != 0) { + numSigfigs++; + } + digits++; + temp /= 10; + } + + DecimalStringParams memory params; + uint256 nZeros; + if (digits >= 5) { + // represents fee greater than or equal to 1% + // if decimal > 1 (5th digit is the ones place) + uint256 decimalPlace = digits - numSigfigs >= 4 ? 0 : 1; + nZeros = digits - 5 < numSigfigs - 1 ? 0 : digits - 5 - (numSigfigs - 1); + params.zerosStartIndex = numSigfigs; + params.zerosEndIndex = uint8(params.zerosStartIndex + nZeros - 1); + params.sigfigIndex = uint8(params.zerosStartIndex - 1 + decimalPlace); + params.bufferLength = uint8(nZeros + numSigfigs + 1 + decimalPlace); + } else { + // represents fee less than 1% + // else if decimal < 1 + nZeros = 5 - digits; // number of zeros, inlcuding the zero before decimal + params.zerosStartIndex = 2; // leading zeros will start after the decimal point + params.zerosEndIndex = uint8(nZeros + params.zerosStartIndex - 1); // end index for leading zeros + params.bufferLength = uint8(nZeros + numSigfigs + 2); // total length of string buffer, including "0." and "%" + params.sigfigIndex = uint8(params.bufferLength - 2); // index of starting signficant figure + params.isLessThanOne = true; + } + params.sigfigs = uint256(fee) / (10 ** (digits - numSigfigs)); // the signficant figures of the fee + params.isPercent = true; + params.decimalIndex = digits > 4 ? uint8(digits - 4) : 0; // based on total number of digits in the fee + + return generateDecimalString(params); + } + + function addressToString(address addr) internal pure returns (string memory) { + return (uint256(uint160(addr))).toHexString(20); + } + + /// @notice Generates the SVG image for a Uniswap v4 NFT + /// @param params Parameters needed to generate the SVG image + /// @return svg The SVG image as a string + function generateSVGImage(ConstructTokenURIParams memory params) internal pure returns (string memory svg) { + SVG.SVGParams memory svgParams = SVG.SVGParams({ + quoteCurrency: addressToString(params.quoteCurrency), + baseCurrency: addressToString(params.baseCurrency), + hooks: params.hooks, + quoteCurrencySymbol: params.quoteCurrencySymbol, + baseCurrencySymbol: params.baseCurrencySymbol, + feeTier: feeToPercentString(params.fee), + tickLower: params.tickLower, + tickUpper: params.tickUpper, + tickSpacing: params.tickSpacing, + overRange: overRange(params.tickLower, params.tickUpper, params.tickCurrent), + tokenId: params.tokenId, + color0: currencyToColorHex(uint256(uint160(params.quoteCurrency)), 136), + color1: currencyToColorHex(uint256(uint160(params.baseCurrency)), 136), + color2: currencyToColorHex(uint256(uint160(params.quoteCurrency)), 0), + color3: currencyToColorHex(uint256(uint160(params.baseCurrency)), 0), + x1: scale(getCircleCoord(uint256(uint160(params.quoteCurrency)), 16, params.tokenId), 0, 255, 16, 274), + y1: scale(getCircleCoord(uint256(uint160(params.baseCurrency)), 16, params.tokenId), 0, 255, 100, 484), + x2: scale(getCircleCoord(uint256(uint160(params.quoteCurrency)), 32, params.tokenId), 0, 255, 16, 274), + y2: scale(getCircleCoord(uint256(uint160(params.baseCurrency)), 32, params.tokenId), 0, 255, 100, 484), + x3: scale(getCircleCoord(uint256(uint160(params.quoteCurrency)), 48, params.tokenId), 0, 255, 16, 274), + y3: scale(getCircleCoord(uint256(uint160(params.baseCurrency)), 48, params.tokenId), 0, 255, 100, 484) + }); + + return SVG.generateSVG(svgParams); + } + + /// @notice Checks if the current price is within your position range, above, or below + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @param tickCurrent The current tick + /// @return 0 if the current price is within the position range, -1 if below, 1 if above + function overRange(int24 tickLower, int24 tickUpper, int24 tickCurrent) private pure returns (int8) { + if (tickCurrent < tickLower) { + return -1; + } else if (tickCurrent > tickUpper) { + return 1; + } else { + return 0; + } + } + + function isSpecialCharacter(bytes1 b) private pure returns (bool) { + return b == '"' || b == "\u000c" || b == "\n" || b == "\r" || b == "\t"; + } + + function scale(uint256 n, uint256 inMn, uint256 inMx, uint256 outMn, uint256 outMx) + private + pure + returns (string memory) + { + return ((n - inMn) * (outMx - outMn) / (inMx - inMn) + outMn).toString(); + } + + function currencyToColorHex(uint256 currency, uint256 offset) internal pure returns (string memory str) { + return string((currency >> offset).toHexStringNoPrefix(3)); + } + + function getCircleCoord(uint256 currency, uint256 offset, uint256 tokenId) internal pure returns (uint256) { + return (sliceCurrencyHex(currency, offset) * tokenId) % 255; + } + + function sliceCurrencyHex(uint256 currency, uint256 offset) internal pure returns (uint256) { + return uint256(uint8(currency >> offset)); + } +} diff --git a/src/libraries/ERC721PermitHash.sol b/src/libraries/ERC721PermitHash.sol index 9a36d6de8..3ead157a2 100644 --- a/src/libraries/ERC721PermitHash.sol +++ b/src/libraries/ERC721PermitHash.sol @@ -1,11 +1,20 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -library ERC721PermitHashLibrary { +library ERC721PermitHash { /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); bytes32 constant PERMIT_TYPEHASH = 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; - function hash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + /// @dev Value is equal to keccak256("PermitForAll(address operator,bool approved,uint256 nonce,uint256 deadline)"); + bytes32 constant PERMIT_FOR_ALL_TYPEHASH = 0x6673cb397ee2a50b6b8401653d3638b4ac8b3db9c28aa6870ffceb7574ec2f76; + + /// @notice Hashes the data that will be signed for IERC721Permit_v4.permit() + /// @param spender The address which may spend the tokenId + /// @param tokenId The tokenId of the owner, which may be spent by spender + /// @param nonce A unique non-ordered value for each signature to prevent replay attacks + /// @param deadline The time at which the signature expires + /// @return digest The hash of the data to be signed; the equivalent to keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + function hashPermit(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) internal pure returns (bytes32 digest) @@ -14,11 +23,48 @@ library ERC721PermitHashLibrary { assembly ("memory-safe") { let fmp := mload(0x40) mstore(fmp, PERMIT_TYPEHASH) - mstore(add(fmp, 0x20), spender) + mstore(add(fmp, 0x20), and(spender, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(fmp, 0x40), tokenId) mstore(add(fmp, 0x60), nonce) mstore(add(fmp, 0x80), deadline) digest := keccak256(fmp, 0xa0) + + // now clean the memory we used + mstore(fmp, 0) // fmp held PERMIT_TYPEHASH + mstore(add(fmp, 0x20), 0) // fmp+0x20 held spender + mstore(add(fmp, 0x40), 0) // fmp+0x40 held tokenId + mstore(add(fmp, 0x60), 0) // fmp+0x60 held nonce + mstore(add(fmp, 0x80), 0) // fmp+0x80 held deadline + } + } + + /// @notice Hashes the data that will be signed for IERC721Permit_v4.permit() + /// @param operator The address which may spend any of the owner's tokenIds + /// @param approved true if the operator is to have full permission over the owner's tokenIds; false otherwise + /// @param nonce A unique non-ordered value for each signature to prevent replay attacks + /// @param deadline The time at which the signature expires + /// @return digest The hash of the data to be signed; the equivalent to keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + function hashPermitForAll(address operator, bool approved, uint256 nonce, uint256 deadline) + internal + pure + returns (bytes32 digest) + { + // equivalent to: keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, PERMIT_FOR_ALL_TYPEHASH) + mstore(add(fmp, 0x20), and(operator, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(fmp, 0x40), and(approved, 0x1)) + mstore(add(fmp, 0x60), nonce) + mstore(add(fmp, 0x80), deadline) + digest := keccak256(fmp, 0xa0) + + // now clean the memory we used + mstore(fmp, 0) // fmp held PERMIT_FOR_ALL_TYPEHASH + mstore(add(fmp, 0x20), 0) // fmp+0x20 held operator + mstore(add(fmp, 0x40), 0) // fmp+0x40 held approved + mstore(add(fmp, 0x60), 0) // fmp+0x60 held nonce + mstore(add(fmp, 0x80), 0) // fmp+0x80 held deadline } } } diff --git a/src/libraries/HexStrings.sol b/src/libraries/HexStrings.sol new file mode 100644 index 000000000..714a9c8ef --- /dev/null +++ b/src/libraries/HexStrings.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title HexStrings +/// @notice Provides function for converting numbers to hexadecimal strings +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/HexStrings.sol +library HexStrings { + bytes16 internal constant ALPHABET = "0123456789abcdef"; + + /// @notice Convert a number to a hex string without the '0x' prefix with a fixed length + /// @param value The number to convert + /// @param length The length of the output string, starting from the last character of the string + /// @return The hex string + function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length); + for (uint256 i = buffer.length; i > 0; i--) { + buffer[i - 1] = ALPHABET[value & 0xf]; + value >>= 4; + } + return string(buffer); + } +} diff --git a/src/libraries/LiquidityAmounts.sol b/src/libraries/LiquidityAmounts.sol new file mode 100644 index 000000000..d6d2dd96d --- /dev/null +++ b/src/libraries/LiquidityAmounts.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; + +/// @notice Provides functions for computing liquidity amounts from token amounts and prices +library LiquidityAmounts { + using SafeCast for uint256; + + /// @notice Computes the amount of liquidity received for a given amount of token0 and price range + /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)) + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount0 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0) + internal + pure + returns (uint128 liquidity) + { + unchecked { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96); + return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128(); + } + } + + /// @notice Computes the amount of liquidity received for a given amount of token1 and price range + /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary + /// @param amount1 The amount1 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1) + internal + pure + returns (uint128 liquidity) + { + unchecked { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128(); + } + } + + /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current + /// pool prices and the prices at the tick boundaries + /// @param sqrtPriceX96 A sqrt price representing the current pool prices + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount of token0 being sent in + /// @param amount1 The amount of token1 being sent in + /// @return liquidity The maximum amount of liquidity received + function getLiquidityForAmounts( + uint160 sqrtPriceX96, + uint160 sqrtPriceAX96, + uint160 sqrtPriceBX96, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + + if (sqrtPriceX96 <= sqrtPriceAX96) { + liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0); + } else if (sqrtPriceX96 < sqrtPriceBX96) { + uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0); + uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1); + + liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; + } else { + liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1); + } + } +} diff --git a/src/libraries/Locker.sol b/src/libraries/Locker.sol index 713779f16..246b10a96 100644 --- a/src/libraries/Locker.sol +++ b/src/libraries/Locker.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /// @notice This is a temporary library that allows us to use transient storage (tstore/tload) diff --git a/src/libraries/PathKey.sol b/src/libraries/PathKey.sol index a286076d3..daa2fdd07 100644 --- a/src/libraries/PathKey.sol +++ b/src/libraries/PathKey.sol @@ -1,5 +1,5 @@ -//SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; @@ -13,7 +13,14 @@ struct PathKey { bytes hookData; } -library PathKeyLib { +/// @title PathKey Library +/// @notice Functions for working with PathKeys +library PathKeyLibrary { + /// @notice Get the pool and swap direction for a given PathKey + /// @param params the given PathKey + /// @param currencyIn the input currency + /// @return poolKey the pool key of the swap + /// @return zeroForOne the direction of the swap, true if currency0 is being swapped for currency1 function getPoolAndSwapDirection(PathKey calldata params, Currency currencyIn) internal pure diff --git a/src/libraries/PoolTicksCounter.sol b/src/libraries/PoolTicksCounter.sol deleted file mode 100644 index 7420ffd5d..000000000 --- a/src/libraries/PoolTicksCounter.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.8.20; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -library PoolTicksCounter { - using PoolIdLibrary for PoolKey; - using StateLibrary for IPoolManager; - - struct TickCache { - int16 wordPosLower; - int16 wordPosHigher; - uint8 bitPosLower; - uint8 bitPosHigher; - bool tickBeforeInitialized; - bool tickAfterInitialized; - } - - /// @dev This function counts the number of initialized ticks that would incur a gas cost between tickBefore and tickAfter. - /// When tickBefore and/or tickAfter themselves are initialized, the logic over whether we should count them depends on the - /// direction of the swap. If we are swapping upwards (tickAfter > tickBefore) we don't want to count tickBefore but we do - /// want to count tickAfter. The opposite is true if we are swapping downwards. - function countInitializedTicksLoaded(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) - internal - view - returns (uint32 initializedTicksLoaded) - { - TickCache memory cache; - - { - // Get the key and offset in the tick bitmap of the active tick before and after the swap. - int16 wordPos = int16((tickBefore / key.tickSpacing) >> 8); - uint8 bitPos = uint8(uint24((tickBefore / key.tickSpacing) % 256)); - - int16 wordPosAfter = int16((tickAfter / key.tickSpacing) >> 8); - uint8 bitPosAfter = uint8(uint24((tickAfter / key.tickSpacing) % 256)); - - // In the case where tickAfter is initialized, we only want to count it if we are swapping downwards. - // If the initializable tick after the swap is initialized, our original tickAfter is a - // multiple of tick spacing, and we are swapping downwards we know that tickAfter is initialized - // and we shouldn't count it. - uint256 bmAfter = self.getTickBitmap(key.toId(), wordPosAfter); - cache.tickAfterInitialized = - ((bmAfter & (1 << bitPosAfter)) > 0) && ((tickAfter % key.tickSpacing) == 0) && (tickBefore > tickAfter); - - // In the case where tickBefore is initialized, we only want to count it if we are swapping upwards. - // Use the same logic as above to decide whether we should count tickBefore or not. - uint256 bmBefore = self.getTickBitmap(key.toId(), wordPos); - cache.tickBeforeInitialized = - ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); - - if (wordPos < wordPosAfter || (wordPos == wordPosAfter && bitPos <= bitPosAfter)) { - cache.wordPosLower = wordPos; - cache.bitPosLower = bitPos; - cache.wordPosHigher = wordPosAfter; - cache.bitPosHigher = bitPosAfter; - } else { - cache.wordPosLower = wordPosAfter; - cache.bitPosLower = bitPosAfter; - cache.wordPosHigher = wordPos; - cache.bitPosHigher = bitPos; - } - } - - // Count the number of initialized ticks crossed by iterating through the tick bitmap. - // Our first mask should include the lower tick and everything to its left. - uint256 mask = type(uint256).max << cache.bitPosLower; - while (cache.wordPosLower <= cache.wordPosHigher) { - // If we're on the final tick bitmap page, ensure we only count up to our - // ending tick. - if (cache.wordPosLower == cache.wordPosHigher) { - mask = mask & (type(uint256).max >> (255 - cache.bitPosHigher)); - } - - uint256 bmLower = self.getTickBitmap(key.toId(), cache.wordPosLower); - uint256 masked = bmLower & mask; - initializedTicksLoaded += countOneBits(masked); - cache.wordPosLower++; - // Reset our mask so we consider all bits on the next iteration. - mask = type(uint256).max; - } - - if (cache.tickAfterInitialized) { - initializedTicksLoaded -= 1; - } - - if (cache.tickBeforeInitialized) { - initializedTicksLoaded -= 1; - } - - return initializedTicksLoaded; - } - - function countOneBits(uint256 x) private pure returns (uint16) { - uint16 bits = 0; - while (x != 0) { - bits++; - x &= (x - 1); - } - return bits; - } -} diff --git a/src/libraries/PositionConfig.sol b/src/libraries/PositionConfig.sol index 7c5d632ac..2fd6cd0d6 100644 --- a/src/libraries/PositionConfig.sol +++ b/src/libraries/PositionConfig.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; @@ -10,49 +10,8 @@ struct PositionConfig { int24 tickUpper; } -/// @notice Library to get and set the PositionConfigId and subscriber status for a given tokenId +/// @notice Library to calculate the PositionConfigId from the PositionConfig struct library PositionConfigLibrary { - using PositionConfigLibrary for PositionConfig; - - bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; - - /// @notice returns the truncated hash of the PositionConfig for a given tokenId - function getConfigId(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) - internal - view - returns (bytes32 configId) - { - configId = positionConfigs[tokenId] & MASK_UPPER_BIT; - } - - function setConfigId( - mapping(uint256 => bytes32) storage positionConfigs, - uint256 tokenId, - PositionConfig calldata config - ) internal { - positionConfigs[tokenId] = config.toId(); - } - - function setSubscribe(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) internal { - positionConfigs[tokenId] |= DIRTY_UPPER_BIT; - } - - function setUnsubscribe(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) internal { - positionConfigs[tokenId] &= MASK_UPPER_BIT; - } - - function hasSubscriber(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) - internal - view - returns (bool subscribed) - { - bytes32 _config = positionConfigs[tokenId]; - assembly ("memory-safe") { - subscribed := shr(255, _config) - } - } - function toId(PositionConfig calldata config) internal pure returns (bytes32 id) { // id = keccak256(abi.encodePacked(currency0, currency1, fee, tickSpacing, hooks, tickLower, tickUpper))) >> 1 assembly ("memory-safe") { diff --git a/src/libraries/PositionConfigId.sol b/src/libraries/PositionConfigId.sol new file mode 100644 index 000000000..e6bdf84d6 --- /dev/null +++ b/src/libraries/PositionConfigId.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice A configId is set per tokenId +/// The lower 255 bits are used to store the truncated hash of the corresponding PositionConfig +/// The upper bit is used to signal if the tokenId has a subscriber +struct PositionConfigId { + bytes32 id; +} + +library PositionConfigIdLibrary { + bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; + + /// @notice returns the truncated hash of the PositionConfig for a given tokenId + function getConfigId(PositionConfigId storage _configId) internal view returns (bytes32 configId) { + configId = _configId.id & MASK_UPPER_BIT; + } + + /// @dev We only set the config on mint, guaranteeing that the most significant bit is unset, so we can just assign the entire 32 bytes to the id. + function setConfigId(PositionConfigId storage _configId, bytes32 configId) internal { + _configId.id = configId; + } + + function setSubscribe(PositionConfigId storage configId) internal { + configId.id |= DIRTY_UPPER_BIT; + } + + function setUnsubscribe(PositionConfigId storage configId) internal { + configId.id &= MASK_UPPER_BIT; + } + + function hasSubscriber(PositionConfigId storage configId) internal view returns (bool subscribed) { + bytes32 _id = configId.id; + assembly ("memory-safe") { + subscribed := shr(255, _id) + } + } +} diff --git a/src/libraries/PositionInfoLibrary.sol b/src/libraries/PositionInfoLibrary.sol new file mode 100644 index 000000000..2baba26a1 --- /dev/null +++ b/src/libraries/PositionInfoLibrary.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; + +/** + * @dev PositionInfo is a packed version of solidity structure. + * Using the packaged version saves gas and memory by not storing the structure fields in memory slots. + * + * Layout: + * 200 bits poolId | 24 bits tickUpper | 24 bits tickLower | 8 bits hasSubscriber + * + * Fields in the direction from the least significant bit: + * + * A flag to know if the tokenId is subscribed to an address + * uint8 hasSubscriber; + * + * The tickUpper of the position + * int24 tickUpper; + * + * The tickLower of the position + * int24 tickLower; + * + * The truncated poolId. Truncates a bytes32 value so the most signifcant (highest) 200 bits are used. + * bytes25 poolId; + * + * Note: If more bits are needed, hasSubscriber can be a single bit. + * + */ +type PositionInfo is uint256; + +library PositionInfoLibrary { + using PoolIdLibrary for PoolKey; + + PositionInfo internal constant EMPTY_POSITION_INFO = PositionInfo.wrap(0); + + uint256 internal constant MASK_UPPER_200_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000; + uint256 internal constant MASK_8_BITS = 0xFF; + uint24 internal constant MASK_24_BITS = 0xFFFFFF; + uint256 internal constant SET_UNSUBSCRIBE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00; + uint256 internal constant SET_SUBSCRIBE = 0x01; + uint8 internal constant TICK_LOWER_OFFSET = 8; + uint8 internal constant TICK_UPPER_OFFSET = 32; + + /// @dev This poolId is NOT compatible with the poolId used in UniswapV4 core. It is truncated to 25 bytes, and just used to lookup PoolKey in the poolKeys mapping. + function poolId(PositionInfo info) internal pure returns (bytes25 _poolId) { + assembly ("memory-safe") { + _poolId := and(MASK_UPPER_200_BITS, info) + } + } + + function tickLower(PositionInfo info) internal pure returns (int24 _tickLower) { + assembly ("memory-safe") { + _tickLower := signextend(2, shr(TICK_LOWER_OFFSET, info)) + } + } + + function tickUpper(PositionInfo info) internal pure returns (int24 _tickUpper) { + assembly ("memory-safe") { + _tickUpper := signextend(2, shr(TICK_UPPER_OFFSET, info)) + } + } + + function hasSubscriber(PositionInfo info) internal pure returns (bool _hasSubscriber) { + assembly ("memory-safe") { + _hasSubscriber := and(MASK_8_BITS, info) + } + } + + /// @dev this does not actually set any storage + function setSubscribe(PositionInfo info) internal pure returns (PositionInfo _info) { + assembly ("memory-safe") { + _info := or(info, SET_SUBSCRIBE) + } + } + + /// @dev this does not actually set any storage + function setUnsubscribe(PositionInfo info) internal pure returns (PositionInfo _info) { + assembly ("memory-safe") { + _info := and(info, SET_UNSUBSCRIBE) + } + } + + /// @notice Creates the default PositionInfo struct + /// @dev Called when minting a new position + /// @param _poolKey the pool key of the position + /// @param _tickLower the lower tick of the position + /// @param _tickUpper the upper tick of the position + /// @return info packed position info, with the truncated poolId and the hasSubscriber flag set to false + function initialize(PoolKey memory _poolKey, int24 _tickLower, int24 _tickUpper) + internal + pure + returns (PositionInfo info) + { + bytes25 _poolId = bytes25(PoolId.unwrap(_poolKey.toId())); + assembly { + info := + or( + or(and(MASK_UPPER_200_BITS, _poolId), shl(TICK_UPPER_OFFSET, and(MASK_24_BITS, _tickUpper))), + shl(TICK_LOWER_OFFSET, and(MASK_24_BITS, _tickLower)) + ) + } + } +} diff --git a/src/libraries/QuoterRevert.sol b/src/libraries/QuoterRevert.sol new file mode 100644 index 000000000..d53ec844d --- /dev/null +++ b/src/libraries/QuoterRevert.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ParseBytes} from "@uniswap/v4-core/src/libraries/ParseBytes.sol"; + +library QuoterRevert { + using QuoterRevert for bytes; + using ParseBytes for bytes; + + /// @notice error thrown when invalid revert bytes are thrown by the quote + error UnexpectedRevertBytes(bytes revertData); + + /// @notice error thrown containing the quote as the data, to be caught and parsed later + error QuoteSwap(uint256 amount); + + /// @notice reverts, where the revert data is the provided bytes + /// @dev called when quoting, to record the quote amount in an error + /// @dev QuoteSwap is used to differentiate this error from other errors thrown when simulating the swap + function revertQuote(uint256 quoteAmount) internal pure { + revert QuoteSwap(quoteAmount); + } + + /// @notice reverts using the revertData as the reason + /// @dev to bubble up both the valid QuoteSwap(amount) error, or an alternative error thrown during simulation + function bubbleReason(bytes memory revertData) internal pure { + // mload(revertData): the length of the revert data + // add(revertData, 0x20): a pointer to the start of the revert data + assembly ("memory-safe") { + revert(add(revertData, 0x20), mload(revertData)) + } + } + + /// @notice validates whether a revert reason is a valid swap quote or not + /// if valid, it decodes the quote to return. Otherwise it reverts. + function parseQuoteAmount(bytes memory reason) internal pure returns (uint256 quoteAmount) { + // If the error doesnt start with QuoteSwap, we know this isnt a valid quote to parse + // Instead it is another revert that was triggered somewhere in the simulation + if (reason.parseSelector() != QuoteSwap.selector) { + revert UnexpectedRevertBytes(reason); + } + + // reason -> reason+0x1f is the length of the reason string + // reason+0x20 -> reason+0x23 is the selector of QuoteSwap + // reason+0x24 -> reason+0x43 is the quoteAmount + assembly ("memory-safe") { + quoteAmount := mload(add(reason, 0x24)) + } + } +} diff --git a/src/libraries/SVG.sol b/src/libraries/SVG.sol new file mode 100644 index 000000000..03c4236cb --- /dev/null +++ b/src/libraries/SVG.sol @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; +import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol"; + +/// @title SVG +/// @notice Provides a function for generating an SVG associated with a Uniswap NFT +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/NFTSVG.sol +library SVG { + using Strings for uint256; + + // SVG path commands for the curve that represent the steepness of the position + // defined using the Cubic Bezier Curve syntax + // curve1 is the smallest (linear) curve, curve8 is the largest curve + string constant curve1 = "M1 1C41 41 105 105 145 145"; + string constant curve2 = "M1 1C33 49 97 113 145 145"; + string constant curve3 = "M1 1C33 57 89 113 145 145"; + string constant curve4 = "M1 1C25 65 81 121 145 145"; + string constant curve5 = "M1 1C17 73 73 129 145 145"; + string constant curve6 = "M1 1C9 81 65 137 145 145"; + string constant curve7 = "M1 1C1 89 57.5 145 145 145"; + string constant curve8 = "M1 1C1 97 49 145 145 145"; + + struct SVGParams { + string quoteCurrency; + string baseCurrency; + address hooks; + string quoteCurrencySymbol; + string baseCurrencySymbol; + string feeTier; + int24 tickLower; + int24 tickUpper; + int24 tickSpacing; + int8 overRange; + uint256 tokenId; + string color0; + string color1; + string color2; + string color3; + string x1; + string y1; + string x2; + string y2; + string x3; + string y3; + } + + /// @notice Generate the SVG associated with a Uniswap v4 NFT + /// @param params The SVGParams struct containing the parameters for the SVG + /// @return svg The SVG string associated with the NFT + function generateSVG(SVGParams memory params) internal pure returns (string memory svg) { + return string( + abi.encodePacked( + generateSVGDefs(params), + generateSVGBorderText( + params.quoteCurrency, params.baseCurrency, params.quoteCurrencySymbol, params.baseCurrencySymbol + ), + generateSVGCardMantle(params.quoteCurrencySymbol, params.baseCurrencySymbol, params.feeTier), + generageSvgCurve(params.tickLower, params.tickUpper, params.tickSpacing, params.overRange), + generateSVGPositionDataAndLocationCurve( + params.tokenId.toString(), params.hooks, params.tickLower, params.tickUpper + ), + generateSVGRareSparkle(params.tokenId, params.hooks), + "" + ) + ); + } + + /// @notice Generate the SVG defs that create the color scheme for the SVG + /// @param params The SVGParams struct containing the parameters to generate the SVG defs + /// @return svg The SVG defs string + function generateSVGDefs(SVGParams memory params) private pure returns (string memory svg) { + svg = string( + abi.encodePacked( + '", + "", + '" + ) + ) + ), + '"/>" + ) + ) + ), + '"/>" + ) + ) + ), + '" />', + '" + ) + ) + ), + '" /> ', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ' ', + '', + '', + '' + ) + ); + } + + /// @notice Generate the SVG for the moving border text displaying the quote and base currency addresses with their symbols + /// @param quoteCurrency The quote currency + /// @param baseCurrency The base currency + /// @param quoteCurrencySymbol The quote currency symbol + /// @param baseCurrencySymbol The base currency symbol + /// @return svg The SVG for the border NFT's border text + function generateSVGBorderText( + string memory quoteCurrency, + string memory baseCurrency, + string memory quoteCurrencySymbol, + string memory baseCurrencySymbol + ) private pure returns (string memory svg) { + svg = string( + abi.encodePacked( + '', + '', + baseCurrency, + unicode" • ", + baseCurrencySymbol, + ' ', + ' ', + baseCurrency, + unicode" • ", + baseCurrencySymbol, + ' ', + '', + quoteCurrency, + unicode" • ", + quoteCurrencySymbol, + ' ', + quoteCurrency, + unicode" • ", + quoteCurrencySymbol, + ' ' + ) + ); + } + + /// @notice Generate the SVG for the card mantle displaying the quote and base currency symbols and fee tier + /// @param quoteCurrencySymbol The quote currency symbol + /// @param baseCurrencySymbol The base currency symbol + /// @param feeTier The fee tier + /// @return svg The SVG for the card mantle + function generateSVGCardMantle( + string memory quoteCurrencySymbol, + string memory baseCurrencySymbol, + string memory feeTier + ) private pure returns (string memory svg) { + svg = string( + abi.encodePacked( + ' ', + quoteCurrencySymbol, + "/", + baseCurrencySymbol, + '', + feeTier, + "", + '' + ) + ); + } + + /// @notice Generate the SVG for the curve that represents the position. Fade up (top is faded) if current price is above your position range, fade down (bottom is faded) if current price is below your position range + /// Circles are generated at the ends of the curve if the position is in range, or at one end of the curve it is on if not in range + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @param tickSpacing The tick spacing + /// @param overRange Whether the current tick is in range, over range, or under range + /// @return svg The SVG for the curve + function generageSvgCurve(int24 tickLower, int24 tickUpper, int24 tickSpacing, int8 overRange) + private + pure + returns (string memory svg) + { + string memory fade = overRange == 1 ? "#fade-up" : overRange == -1 ? "#fade-down" : "#none"; + string memory curve = getCurve(tickLower, tickUpper, tickSpacing); + svg = string( + abi.encodePacked( + '' + '' '', + '', + '', + '', + generateSVGCurveCircle(overRange) + ) + ); + } + + /// @notice Get the curve based on the tick range + /// The smaller the tick range, the smaller/more linear the curve + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @param tickSpacing The tick spacing + /// @return curve The curve path + function getCurve(int24 tickLower, int24 tickUpper, int24 tickSpacing) + internal + pure + returns (string memory curve) + { + int24 tickRange = (tickUpper - tickLower) / tickSpacing; + if (tickRange <= 4) { + curve = curve1; + } else if (tickRange <= 8) { + curve = curve2; + } else if (tickRange <= 16) { + curve = curve3; + } else if (tickRange <= 32) { + curve = curve4; + } else if (tickRange <= 64) { + curve = curve5; + } else if (tickRange <= 128) { + curve = curve6; + } else if (tickRange <= 256) { + curve = curve7; + } else { + curve = curve8; + } + } + + /// @notice Generate the SVG for the circles on the curve + /// @param overRange 0 if the current tick is in range, 1 if the current tick is over range, -1 if the current tick is under range + /// @return svg The SVG for the circles + function generateSVGCurveCircle(int8 overRange) internal pure returns (string memory svg) { + string memory curvex1 = "73"; + string memory curvey1 = "190"; + string memory curvex2 = "217"; + string memory curvey2 = "334"; + /// If the position is over or under range, generate one circle at the end of the curve on the side of the range it is on with a larger circle around it + if (overRange == 1 || overRange == -1) { + svg = string( + abi.encodePacked( + '' + ) + ); + } else { + /// If the position is in range, generate two circles at the ends of the curve + svg = string( + abi.encodePacked( + '', + '' + ) + ); + } + } + + /// @notice Generate the SVG for the position data (token ID, hooks address, min tick, max tick) and the location curve (where your position falls on the curve) + /// @param tokenId The token ID + /// @param hook The hooks address + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @return svg The SVG for the position data and location curve + function generateSVGPositionDataAndLocationCurve( + string memory tokenId, + address hook, + int24 tickLower, + int24 tickUpper + ) private pure returns (string memory svg) { + string memory hookStr = (uint256(uint160(hook))).toHexString(20); + string memory tickLowerStr = tickToString(tickLower); + string memory tickUpperStr = tickToString(tickUpper); + uint256 str1length = bytes(tokenId).length + 4; + string memory hookSlice = hook == address(0) + ? "No Hook" + : string(abi.encodePacked(substring(hookStr, 0, 5), "...", substring(hookStr, 39, 42))); + uint256 str2length = bytes(hookSlice).length + 5; + uint256 str3length = bytes(tickLowerStr).length + 10; + uint256 str4length = bytes(tickUpperStr).length + 10; + (string memory xCoord, string memory yCoord) = rangeLocation(tickLower, tickUpper); + svg = string( + abi.encodePacked( + ' ', + '', + 'ID: ', + tokenId, + "", + ' ', + '', + 'Hook: ', + hookSlice, + "", + ' ', + '', + 'Min Tick: ', + tickLowerStr, + "", + ' ', + '', + 'Max Tick: ', + tickUpperStr, + "" '', + '', + '', + '' + ) + ); + } + + function substring(string memory str, uint256 startIndex, uint256 endIndex) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory result = new bytes(endIndex - startIndex); + for (uint256 i = startIndex; i < endIndex; i++) { + result[i - startIndex] = strBytes[i]; + } + return string(result); + } + + function tickToString(int24 tick) private pure returns (string memory) { + string memory sign = ""; + if (tick < 0) { + tick = tick * -1; + sign = "-"; + } + return string(abi.encodePacked(sign, uint256(uint24(tick)).toString())); + } + + /// @notice Get the location of where your position falls on the curve + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @return The x and y coordinates of the location of the liquidity + function rangeLocation(int24 tickLower, int24 tickUpper) internal pure returns (string memory, string memory) { + int24 midPoint = (tickLower + tickUpper) / 2; + if (midPoint < -125_000) { + return ("8", "7"); + } else if (midPoint < -75_000) { + return ("8", "10.5"); + } else if (midPoint < -25_000) { + return ("8", "14.25"); + } else if (midPoint < -5_000) { + return ("10", "18"); + } else if (midPoint < 0) { + return ("11", "21"); + } else if (midPoint < 5_000) { + return ("13", "23"); + } else if (midPoint < 25_000) { + return ("15", "25"); + } else if (midPoint < 75_000) { + return ("18", "26"); + } else if (midPoint < 125_000) { + return ("21", "27"); + } else { + return ("24", "27"); + } + } + + /// @notice Generates the SVG for a rare sparkle if the NFT is rare. Else, returns an empty string + /// @param tokenId The token ID + /// @param hooks The hooks address + /// @return svg The SVG for the rare sparkle + function generateSVGRareSparkle(uint256 tokenId, address hooks) private pure returns (string memory svg) { + if (isRare(tokenId, hooks)) { + svg = string( + abi.encodePacked( + '', + '', + '' + ) + ); + } else { + svg = ""; + } + } + + /// @notice Determines if an NFT is rare based on the token ID and hooks address + /// @param tokenId The token ID + /// @param hooks The hooks address + /// @return Whether the NFT is rare or not + function isRare(uint256 tokenId, address hooks) internal pure returns (bool) { + bytes32 h = keccak256(abi.encodePacked(tokenId, hooks)); + return uint256(h) < type(uint256).max / (1 + BitMath.mostSignificantBit(tokenId) * 2); + } +} diff --git a/src/libraries/SafeCast.sol b/src/libraries/SafeCast.sol deleted file mode 100644 index ae16b2d18..000000000 --- a/src/libraries/SafeCast.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; - -/// @title Safe casting methods -/// @notice Contains methods for safely casting between types -/// TODO after audits move this function to core's SafeCast.sol! -library SafeCastTemp { - using CustomRevert for bytes4; - - error SafeCastOverflow(); - - /// @notice Cast a int128 to a uint128, revert on overflow or underflow - /// @param x The int128 to be casted - /// @return y The casted integer, now type uint128 - function toUint128(int128 x) internal pure returns (uint128 y) { - if (x < 0) SafeCastOverflow.selector.revertWith(); - y = uint128(x); - } -} diff --git a/src/libraries/SafeCurrencyMetadata.sol b/src/libraries/SafeCurrencyMetadata.sol new file mode 100644 index 000000000..e1a4c059c --- /dev/null +++ b/src/libraries/SafeCurrencyMetadata.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {AddressStringUtil} from "./AddressStringUtil.sol"; + +/// @title SafeCurrencyMetadata +/// @notice can produce symbols and decimals from inconsistent or absent ERC20 implementations +/// @dev Reference: https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/SafeERC20Namer.sol +library SafeCurrencyMetadata { + uint8 constant MAX_SYMBOL_LENGTH = 12; + + /// @notice attempts to extract the currency symbol. if it does not implement symbol, returns a symbol derived from the address + /// @param currency The currency address + /// @param nativeLabel The native label + /// @return the currency symbol + function currencySymbol(address currency, string memory nativeLabel) internal view returns (string memory) { + if (currency == address(0)) { + return nativeLabel; + } + string memory symbol = callAndParseStringReturn(currency, IERC20Metadata.symbol.selector); + if (bytes(symbol).length == 0) { + // fallback to 6 uppercase hex of address + return addressToSymbol(currency); + } + if (bytes(symbol).length > MAX_SYMBOL_LENGTH) { + return truncateSymbol(symbol); + } + return symbol; + } + + /// @notice attempts to extract the token decimals, returns 0 if not implemented or not a uint8 + /// @param currency The currency address + /// @return the currency decimals + function currencyDecimals(address currency) internal view returns (uint8) { + if (currency == address(0)) { + return 18; + } + (bool success, bytes memory data) = currency.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + if (!success) { + return 0; + } + if (data.length == 32) { + uint256 decimals = abi.decode(data, (uint256)); + if (decimals <= type(uint8).max) { + return uint8(decimals); + } + } + return 0; + } + + function bytes32ToString(bytes32 x) private pure returns (string memory) { + bytes memory bytesString = new bytes(32); + uint256 charCount = 0; + for (uint256 j = 0; j < 32; j++) { + bytes1 char = x[j]; + if (char != 0) { + bytesString[charCount] = char; + charCount++; + } + } + bytes memory bytesStringTrimmed = new bytes(charCount); + for (uint256 j = 0; j < charCount; j++) { + bytesStringTrimmed[j] = bytesString[j]; + } + return string(bytesStringTrimmed); + } + + /// @notice produces a symbol from the address - the first 6 hex of the address string in upper case + /// @param currencyAddress the address of the currency + /// @return the symbol + function addressToSymbol(address currencyAddress) private pure returns (string memory) { + return AddressStringUtil.toAsciiString(currencyAddress, 6); + } + + /// @notice calls an external view contract method that returns a symbol, and parses the output into a string + /// @param currencyAddress the address of the currency + /// @param selector the selector of the symbol method + /// @return the symbol + function callAndParseStringReturn(address currencyAddress, bytes4 selector) private view returns (string memory) { + (bool success, bytes memory data) = currencyAddress.staticcall(abi.encodeWithSelector(selector)); + // if not implemented, return empty string + if (!success) { + return ""; + } + // bytes32 data always has length 32 + if (data.length == 32) { + bytes32 decoded = abi.decode(data, (bytes32)); + return bytes32ToString(decoded); + } else if (data.length > 64) { + return abi.decode(data, (string)); + } + return ""; + } + + /// @notice truncates the symbol to the MAX_SYMBOL_LENGTH + /// @dev assumes the string is already longer than MAX_SYMBOL_LENGTH (or the same) + /// @param str the symbol + /// @return the truncated symbol + function truncateSymbol(string memory str) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory truncatedBytes = new bytes(MAX_SYMBOL_LENGTH); + for (uint256 i = 0; i < MAX_SYMBOL_LENGTH; i++) { + truncatedBytes[i] = strBytes[i]; + } + return string(truncatedBytes); + } +} diff --git a/src/libraries/SlippageCheck.sol b/src/libraries/SlippageCheck.sol index efeafca95..e4c7e960e 100644 --- a/src/libraries/SlippageCheck.sol +++ b/src/libraries/SlippageCheck.sol @@ -1,37 +1,53 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; /// @title Slippage Check Library /// @notice a library for checking if a delta exceeds a maximum ceiling or fails to meet a minimum floor -library SlippageCheckLibrary { - error MaximumAmountExceeded(); - error MinimumAmountInsufficient(); +library SlippageCheck { + using SafeCast for int128; + + error MaximumAmountExceeded(uint128 maximumAmount, uint128 amountRequested); + error MinimumAmountInsufficient(uint128 minimumAmount, uint128 amountReceived); /// @notice Revert if one or both deltas does not meet a minimum output - /// @dev to be used when removing liquidity to guarantee a minimum output + /// @param delta The principal amount of tokens to be removed, does not include any fees accrued + /// @param amount0Min The minimum amount of token0 to receive + /// @param amount1Min The minimum amount of token1 to receive + /// @dev This should be called when removing liquidity (burn or decrease) function validateMinOut(BalanceDelta delta, uint128 amount0Min, uint128 amount1Min) internal pure { - if (uint128(delta.amount0()) < amount0Min || uint128(delta.amount1()) < amount1Min) { - revert MinimumAmountInsufficient(); + // Called on burn or decrease, where we expect the returned delta to be positive. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be negative. + // Because we use SafeCast, this will revert in those cases when the delta is negative. + // This means this contract will NOT support pools where the hook returns a negative delta on burn/decrease. + if (delta.amount0().toUint128() < amount0Min) { + revert MinimumAmountInsufficient(amount0Min, delta.amount0().toUint128()); + } + if (delta.amount1().toUint128() < amount1Min) { + revert MinimumAmountInsufficient(amount1Min, delta.amount1().toUint128()); } } /// @notice Revert if one or both deltas exceeds a maximum input - /// @dev to be used when minting liquidity to guarantee a maximum input + /// @param delta The principal amount of tokens to be added, does not include any fees accrued (which is possible on increase) + /// @param amount0Max The maximum amount of token0 to spend + /// @param amount1Max The maximum amount of token1 to spend + /// @dev This should be called when adding liquidity (mint or increase) function validateMaxIn(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { - if (uint128(-delta.amount0()) > amount0Max || uint128(-delta.amount1()) > amount1Max) { - revert MaximumAmountExceeded(); + // Called on mint or increase, where we expect the returned delta to be negative. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be positive (even after discounting fees accrued). + // Thus, we only cast the delta if it is guaranteed to be negative. + // And we do NOT revert in the positive delta case. Since a positive delta means the hook is crediting tokens to the user for minting/increasing liquidity, we do not check slippage. + // This means this contract will NOT support _positive_ slippage checks (minAmountOut checks) on pools where the hook returns a positive delta on mint/increase. + int256 amount0 = delta.amount0(); + int256 amount1 = delta.amount1(); + if (amount0 < 0 && amount0Max < uint128(uint256(-amount0))) { + revert MaximumAmountExceeded(amount0Max, uint128(uint256(-amount0))); + } + if (amount1 < 0 && amount1Max < uint128(uint256(-amount1))) { + revert MaximumAmountExceeded(amount1Max, uint128(uint256(-amount1))); } - } - - /// @notice Revert if one or both deltas exceeds a maximum input - /// @dev When increasing liquidity, delta can be positive when excess fees need to be collected - /// in those cases, slippage checks are not required - function validateMaxInNegative(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { - if ( - delta.amount0() < 0 && amount0Max < uint128(-delta.amount0()) - || delta.amount1() < 0 && amount1Max < uint128(-delta.amount1()) - ) revert MaximumAmountExceeded(); } } diff --git a/src/libraries/VanityAddressLib.sol b/src/libraries/VanityAddressLib.sol new file mode 100644 index 000000000..0139aa54d --- /dev/null +++ b/src/libraries/VanityAddressLib.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title VanityAddressLib +/// @notice A library to score addresses based on their vanity +library VanityAddressLib { + /// @notice Compares two addresses and returns true if the first address has a better vanity score + /// @param first The first address to compare + /// @param second The second address to compare + /// @return better True if the first address has a better vanity score + function betterThan(address first, address second) internal pure returns (bool better) { + return score(first) > score(second); + } + + /// @notice Scores an address based on its vanity + /// @dev Scoring rules: + /// Requirement: The first nonzero nibble must be 4 + /// 10 points for every leading 0 nibble + /// 40 points if the first 4 is followed by 3 more 4s + /// 20 points if the first nibble after the 4 4s is NOT a 4 + /// 20 points if the last 4 nibbles are 4s + /// 1 point for every 4 + /// @param addr The address to score + /// @return calculatedScore The vanity score of the address + function score(address addr) internal pure returns (uint256 calculatedScore) { + // convert the address to bytes for easier parsing + bytes20 addrBytes = bytes20(addr); + + unchecked { + // 10 points per leading zero nibble + uint256 leadingZeroCount = getLeadingNibbleCount(addrBytes, 0, 0); + calculatedScore += (leadingZeroCount * 10); + + // special handling for 4s immediately after leading 0s + uint256 leadingFourCount = getLeadingNibbleCount(addrBytes, leadingZeroCount, 4); + // If the first nonzero nibble is not 4, return 0 + if (leadingFourCount == 0) { + return 0; + } else if (leadingFourCount == 4) { + // 60 points if exactly 4 4s + calculatedScore += 60; + } else if (leadingFourCount > 4) { + // 40 points if more than 4 4s + calculatedScore += 40; + } + + // handling for remaining nibbles + for (uint256 i = 0; i < addrBytes.length * 2; i++) { + uint8 currentNibble = getNibble(addrBytes, i); + + // 1 extra point for any 4 nibbles + if (currentNibble == 4) { + calculatedScore += 1; + } + } + + // If the last 4 nibbles are 4s, add 20 points + if (addrBytes[18] == 0x44 && addrBytes[19] == 0x44) { + calculatedScore += 20; + } + } + } + + /// @notice Returns the number of leading nibbles in an address that match a given value + /// @param addrBytes The address to count the leading zero nibbles in + function getLeadingNibbleCount(bytes20 addrBytes, uint256 startIndex, uint8 comparison) + internal + pure + returns (uint256 count) + { + if (startIndex >= addrBytes.length * 2) { + return count; + } + + for (uint256 i = startIndex; i < addrBytes.length * 2; i++) { + uint8 currentNibble = getNibble(addrBytes, i); + if (currentNibble != comparison) { + return count; + } + count += 1; + } + } + + /// @notice Returns the nibble at a given index in an address + /// @param input The address to get the nibble from + /// @param nibbleIndex The index of the nibble to get + function getNibble(bytes20 input, uint256 nibbleIndex) internal pure returns (uint8 currentNibble) { + uint8 currByte = uint8(input[nibbleIndex / 2]); + if (nibbleIndex % 2 == 0) { + // Get the higher nibble of the byte + currentNibble = currByte >> 4; + } else { + // Get the lower nibble of the byte + currentNibble = currByte & 0x0F; + } + } +} diff --git a/test/BaseActionsRouter.t.sol b/test/BaseActionsRouter.t.sol index 57ca984fa..0a6bbc76f 100644 --- a/test/BaseActionsRouter.t.sol +++ b/test/BaseActionsRouter.t.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: UNLICENSED +//SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {MockBaseActionsRouter} from "./mocks/MockBaseActionsRouter.sol"; diff --git a/test/DeltaResolver.t.sol b/test/DeltaResolver.t.sol index 0e791e73b..212939b6f 100644 --- a/test/DeltaResolver.t.sol +++ b/test/DeltaResolver.t.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: UNLICENSED +//SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; @@ -23,7 +23,7 @@ contract DeltaResolverTest is Test, Deployers, GasSnapshot { function test_settle_native_succeeds(uint256 amount) public { amount = bound(amount, 1, address(manager).balance); - resolver.executeTest(CurrencyLibrary.NATIVE, amount); + resolver.executeTest(CurrencyLibrary.ADDRESS_ZERO, amount); // check `pay` was not called assertEq(resolver.payCallCount(), 0); diff --git a/test/Multicall.t.sol b/test/Multicall.t.sol index 8591c2712..83503c005 100644 --- a/test/Multicall.t.sol +++ b/test/Multicall.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; diff --git a/test/PositionDescriptor.t.sol b/test/PositionDescriptor.t.sol new file mode 100644 index 000000000..4b326814c --- /dev/null +++ b/test/PositionDescriptor.t.sol @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {PositionDescriptor} from "../src/PositionDescriptor.sol"; +import {CurrencyRatioSortOrder} from "../src/libraries/CurrencyRatioSortOrder.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PositionConfig} from "./shared/PositionConfig.sol"; +import {PosmTestSetup} from "./shared/PosmTestSetup.sol"; +import {ActionConstants} from "../src/libraries/ActionConstants.sol"; +import {Base64} from "./base64.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {SafeCurrencyMetadata} from "../src/libraries/SafeCurrencyMetadata.sol"; +import {AddressStringUtil} from "../src/libraries/AddressStringUtil.sol"; +import {Descriptor} from "../src/libraries/Descriptor.sol"; + +contract PositionDescriptorTest is Test, PosmTestSetup, GasSnapshot { + using Base64 for string; + using CurrencyLibrary for Currency; + + address public WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address public USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + address public TBTC = 0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa; + address public WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + string public nativeCurrencyLabel = "ETH"; + + struct Token { + string description; + string image; + string name; + } + + function setUp() public { + deployFreshManager(); + (currency0, currency1) = deployAndMint2Currencies(); + (key,) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); + deployAndApprovePosm(manager); + } + + function test_bytecodeSize_positionDescriptor() public { + snapSize("positionDescriptor bytecode size", address(positionDescriptor)); + } + + function test_setup_succeeds() public view { + assertEq(address(positionDescriptor.poolManager()), address(manager)); + assertEq(positionDescriptor.wrappedNative(), WETH9); + assertEq(positionDescriptor.nativeCurrencyLabel(), nativeCurrencyLabel); + } + + function test_currencyRatioPriority_mainnet_succeeds() public { + vm.chainId(1); + assertEq(positionDescriptor.currencyRatioPriority(WETH9), CurrencyRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.currencyRatioPriority(address(0)), CurrencyRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.currencyRatioPriority(USDC), CurrencyRatioSortOrder.NUMERATOR_MOST); + assertEq(positionDescriptor.currencyRatioPriority(USDT), CurrencyRatioSortOrder.NUMERATOR_MORE); + assertEq(positionDescriptor.currencyRatioPriority(DAI), CurrencyRatioSortOrder.NUMERATOR); + assertEq(positionDescriptor.currencyRatioPriority(TBTC), CurrencyRatioSortOrder.DENOMINATOR_MORE); + assertEq(positionDescriptor.currencyRatioPriority(WBTC), CurrencyRatioSortOrder.DENOMINATOR_MOST); + assertEq(positionDescriptor.currencyRatioPriority(makeAddr("ALICE")), 0); + } + + function test_currencyRatioPriority_notMainnet_succeeds() public { + assertEq(positionDescriptor.currencyRatioPriority(WETH9), CurrencyRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.currencyRatioPriority(address(0)), CurrencyRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.currencyRatioPriority(USDC), 0); + assertEq(positionDescriptor.currencyRatioPriority(USDT), 0); + assertEq(positionDescriptor.currencyRatioPriority(DAI), 0); + assertEq(positionDescriptor.currencyRatioPriority(TBTC), 0); + assertEq(positionDescriptor.currencyRatioPriority(WBTC), 0); + assertEq(positionDescriptor.currencyRatioPriority(makeAddr("ALICE")), 0); + } + + function test_flipRatio_succeeds() public { + vm.chainId(1); + // bc price = token1/token0 + assertTrue(positionDescriptor.flipRatio(USDC, WETH9)); + assertFalse(positionDescriptor.flipRatio(DAI, USDC)); + assertFalse(positionDescriptor.flipRatio(WBTC, WETH9)); + assertFalse(positionDescriptor.flipRatio(WBTC, USDC)); + assertFalse(positionDescriptor.flipRatio(WBTC, DAI)); + } + + function test_tokenURI_succeeds() public { + int24 tickLower = int24(key.tickSpacing); + int24 tickUpper = int24(key.tickSpacing * 2); + uint256 amount0Desired = 100e18; + uint256 amount1Desired = 100e18; + uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + amount0Desired, + amount1Desired + ); + + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper}); + uint256 tokenId = lpm.nextTokenId(); + mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); + + // The prefix length is calculated by converting the string to bytes and finding its length + uint256 prefixLength = bytes("data:application/json;base64,").length; + + string memory uri = positionDescriptor.tokenURI(lpm, tokenId); + // Convert the uri to bytes + bytes memory uriBytes = bytes(uri); + + // Slice the uri to get only the base64-encoded part + bytes memory base64Part = new bytes(uriBytes.length - prefixLength); + + for (uint256 i = 0; i < base64Part.length; i++) { + base64Part[i] = uriBytes[i + prefixLength]; + } + + // Decode the base64-encoded part + bytes memory decoded = Base64.decode(string(base64Part)); + string memory json = string(decoded); + + // decode json + bytes memory data = vm.parseJson(json); + Token memory token = abi.decode(data, (Token)); + + // quote is currency1, base is currency0 + assertFalse(positionDescriptor.flipRatio(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1))); + + string memory symbol0 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(currency0), nativeCurrencyLabel); + string memory symbol1 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(currency1), nativeCurrencyLabel); + string memory managerAddress = toHexString(address(manager)); + string memory currency0Address = toHexString(Currency.unwrap(currency0)); + string memory currency1Address = toHexString(Currency.unwrap(currency1)); + string memory id = uintToString(tokenId); + string memory hookAddress = address(key.hooks) == address(0) + ? "No Hook" + : string(abi.encodePacked("0x", toHexString(address(key.hooks)))); + string memory fee = Descriptor.feeToPercentString(key.fee); + string memory tickToDecimal0 = Descriptor.tickToDecimalString( + tickLower, + key.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + string memory tickToDecimal1 = Descriptor.tickToDecimalString( + tickUpper, + key.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + + assertEq( + token.name, + string( + abi.encodePacked( + "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1 + ) + ) + ); + assertEq( + token.description, + string( + abi.encodePacked( + unicode"This NFT represents a liquidity position in a Uniswap v4 ", + symbol1, + "-", + symbol0, + " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ", + managerAddress, + "\n", + symbol1, + " Address: ", + currency1Address, + "\n", + symbol0, + " Address: ", + currency0Address, + "\nHook Address: ", + hookAddress, + "\nFee Tier: ", + fee, + "\nToken ID: ", + id, + "\n\n", + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ) + ) + ); + } + + function test_native_tokenURI_succeeds() public { + (nativeKey,) = initPool(CurrencyLibrary.ADDRESS_ZERO, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); + int24 tickLower = int24(nativeKey.tickSpacing); + int24 tickUpper = int24(nativeKey.tickSpacing * 2); + uint256 amount0Desired = 100e18; + uint256 amount1Desired = 100e18; + uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + amount0Desired, + amount1Desired + ); + + PositionConfig memory config = PositionConfig({poolKey: nativeKey, tickLower: tickLower, tickUpper: tickUpper}); + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); + + // The prefix length is calculated by converting the string to bytes and finding its length + uint256 prefixLength = bytes("data:application/json;base64,").length; + + string memory uri = positionDescriptor.tokenURI(lpm, tokenId); + // Convert the uri to bytes + bytes memory uriBytes = bytes(uri); + + // Slice the uri to get only the base64-encoded part + bytes memory base64Part = new bytes(uriBytes.length - prefixLength); + + for (uint256 i = 0; i < base64Part.length; i++) { + base64Part[i] = uriBytes[i + prefixLength]; + } + + // Decode the base64-encoded part + bytes memory decoded = Base64.decode(string(base64Part)); + string memory json = string(decoded); + + // decode json + bytes memory data = vm.parseJson(json); + Token memory token = abi.decode(data, (Token)); + + // quote is currency1, base is currency0 + assertFalse( + positionDescriptor.flipRatio(Currency.unwrap(nativeKey.currency0), Currency.unwrap(nativeKey.currency1)) + ); + + string memory symbol0 = + SafeCurrencyMetadata.currencySymbol(Currency.unwrap(nativeKey.currency0), nativeCurrencyLabel); + string memory symbol1 = + SafeCurrencyMetadata.currencySymbol(Currency.unwrap(nativeKey.currency1), nativeCurrencyLabel); + string memory managerAddress = toHexString(address(manager)); + string memory currency0Address = Currency.unwrap(nativeKey.currency0) == address(0) + ? "Native" + : toHexString(Currency.unwrap(nativeKey.currency0)); + string memory currency1Address = Currency.unwrap(nativeKey.currency1) == address(0) + ? "Native" + : toHexString(Currency.unwrap(nativeKey.currency1)); + string memory id = uintToString(tokenId); + string memory hookAddress = address(nativeKey.hooks) == address(0) + ? "No Hook" + : string(abi.encodePacked("0x", toHexString(address(nativeKey.hooks)))); + string memory fee = Descriptor.feeToPercentString(nativeKey.fee); + string memory tickToDecimal0 = Descriptor.tickToDecimalString( + tickLower, + nativeKey.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + string memory tickToDecimal1 = Descriptor.tickToDecimalString( + tickUpper, + nativeKey.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + + assertEq( + token.name, + string( + abi.encodePacked( + "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1 + ) + ) + ); + assertEq( + token.description, + string( + abi.encodePacked( + unicode"This NFT represents a liquidity position in a Uniswap v4 ", + symbol1, + "-", + symbol0, + " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ", + managerAddress, + "\n", + symbol1, + " Address: ", + currency1Address, + "\n", + symbol0, + " Address: ", + currency0Address, + "\nHook Address: ", + hookAddress, + "\nFee Tier: ", + fee, + "\nToken ID: ", + id, + "\n\n", + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ) + ) + ); + } + + function test_tokenURI_revertsWithInvalidTokenId() public { + int24 tickLower = int24(key.tickSpacing); + int24 tickUpper = int24(key.tickSpacing * 2); + uint256 amount0Desired = 100e18; + uint256 amount1Desired = 100e18; + uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + amount0Desired, + amount1Desired + ); + + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper}); + uint256 tokenId = lpm.nextTokenId(); + mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); + + vm.expectRevert(abi.encodeWithSelector(PositionDescriptor.InvalidTokenId.selector, tokenId + 1)); + + positionDescriptor.tokenURI(lpm, tokenId + 1); + } + + // Helper functions for testing purposes + function toHexString(address account) internal pure returns (string memory) { + return toHexString(uint256(uint160(account)), 20); + } + + // different from AddressStringUtil.toHexString. this one is all lowercase hex and includes the 0x prefix + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + uint8 digit = uint8(value & 0xf); + buffer[i] = digit < 10 ? bytes1(digit + 48) : bytes1(digit + 87); // Lowercase hex (0x61 is 'a' in ASCII) + value >>= 4; + } + require(value == 0, "Hex length insufficient"); + return string(buffer); + } + + function uintToString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } +} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol deleted file mode 100644 index b01d9c881..000000000 --- a/test/Quoter.t.sol +++ /dev/null @@ -1,667 +0,0 @@ -//SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {PathKey} from "../src/libraries/PathKey.sol"; -import {IQuoter} from "../src/interfaces/IQuoter.sol"; -import {Quoter} from "../src/lens/Quoter.sol"; - -// v4-core -import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -// solmate -import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; - -contract QuoterTest is Test, Deployers { - using SafeCast for *; - using PoolIdLibrary for PoolKey; - using StateLibrary for IPoolManager; - - // Min tick for full range with tick spacing of 60 - int24 internal constant MIN_TICK = -887220; - // Max tick for full range with tick spacing of 60 - int24 internal constant MAX_TICK = -MIN_TICK; - - uint160 internal constant SQRT_PRICE_100_102 = 78447570448055484695608110440; - uint160 internal constant SQRT_PRICE_102_100 = 80016521857016594389520272648; - - uint256 internal constant CONTROLLER_GAS_LIMIT = 500000; - - Quoter quoter; - - PoolModifyLiquidityTest positionManager; - - MockERC20 token0; - MockERC20 token1; - MockERC20 token2; - - PoolKey key01; - PoolKey key02; - PoolKey key12; - - MockERC20[] tokenPath; - - function setUp() public { - deployFreshManagerAndRouters(); - quoter = new Quoter(IPoolManager(manager)); - positionManager = new PoolModifyLiquidityTest(manager); - - // salts are chosen so that address(token0) < address(token1) && address(token1) < address(token2) - token0 = new MockERC20("Test0", "0", 18); - vm.etch(address(0x1111), address(token0).code); - token0 = MockERC20(address(0x1111)); - token0.mint(address(this), 2 ** 128); - - vm.etch(address(0x2222), address(token0).code); - token1 = MockERC20(address(0x2222)); - token1.mint(address(this), 2 ** 128); - - vm.etch(address(0x3333), address(token0).code); - token2 = MockERC20(address(0x3333)); - token2.mint(address(this), 2 ** 128); - - key01 = createPoolKey(token0, token1, address(0)); - key02 = createPoolKey(token0, token2, address(0)); - key12 = createPoolKey(token1, token2, address(0)); - setupPool(key01); - setupPool(key12); - setupPoolMultiplePositions(key02); - } - - function testQuoter_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { - uint256 amountIn = 10000; - uint256 expectedAmountOut = 9871; - uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactInputSingle( - IQuoter.QuoteExactSingleParams({ - poolKey: key02, - zeroForOne: true, - exactAmount: uint128(amountIn), - sqrtPriceLimitX96: 0, - hookData: ZERO_BYTES - }) - ); - - assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); - assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); - assertEq(initializedTicksLoaded, 2); - } - - function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { - uint256 amountIn = 10000; - uint256 expectedAmountOut = 9871; - uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactInputSingle( - IQuoter.QuoteExactSingleParams({ - poolKey: key02, - zeroForOne: false, - exactAmount: uint128(amountIn), - sqrtPriceLimitX96: 0, - hookData: ZERO_BYTES - }) - ); - - assertEq(uint128(-deltaAmounts[0]), expectedAmountOut); - assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); - assertEq(initializedTicksLoaded, 2); - } - - // nested self-call into unlockCallback reverts - function testQuoter_callUnlockCallback_reverts() public { - vm.expectRevert(IQuoter.LockFailure.selector); - vm.prank(address(manager)); - quoter.unlockCallback(abi.encodeWithSelector(quoter.unlockCallback.selector, address(this), "0x")); - } - - function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { - tokenPath.push(token0); - tokenPath.push(token2); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[1]), 9871); - assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); - assertEq(initializedTicksLoadedList[0], 2); - } - - function testQuoter_quoteExactInput_0to2_2TicksLoaded_initialiedAfter() public { - tokenPath.push(token0); - tokenPath.push(token2); - - // The swap amount is set such that the active tick after the swap is -120. - // -120 is an initialized tick for this pool. We check that we don't count it. - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[1]), 6143); - assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactInput_0to2_1TickLoaded() public { - tokenPath.push(token0); - tokenPath.push(token2); - - // The swap amount is set such that the active tick after the swap is -60. - // -60 is an initialized tick for this pool. We check that we don't count it. - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[1]), 3971); - assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { - tokenPath.push(token0); - tokenPath.push(token2); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[1]), 8); - assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); - assertEq(initializedTicksLoadedList[0], 0); - } - - function testQuoter_quoteExactInput_0to2_0TickLoaded_startingInitialized() public { - setupPoolWithZeroTickInitialized(key02); - tokenPath.push(token0); - tokenPath.push(token2); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[1]), 8); - assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { - tokenPath.push(token2); - tokenPath.push(token0); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(-deltaAmounts[1], 9871); - assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); - assertEq(initializedTicksLoadedList[0], 2); - } - - function testQuoter_quoteExactInput_2to0_2TicksLoaded_initialiedAfter() public { - tokenPath.push(token2); - tokenPath.push(token0); - - // The swap amount is set such that the active tick after the swap is 120. - // 120 is an initialized tick for this pool. We check that we don't count it. - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(-deltaAmounts[1], 6190); - assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); - assertEq(initializedTicksLoadedList[0], 2); - } - - function testQuoter_quoteExactInput_2to0_0TickLoaded_startingInitialized() public { - setupPoolWithZeroTickInitialized(key02); - tokenPath.push(token2); - tokenPath.push(token0); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); - - // Tick 0 initialized. Tick after = 1 - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(-deltaAmounts[1], 198); - assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); - assertEq(initializedTicksLoadedList[0], 0); - } - - // 2->0 starting not initialized - function testQuoter_quoteExactInput_2to0_0TickLoaded_startingNotInitialized() public { - tokenPath.push(token2); - tokenPath.push(token0); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(-deltaAmounts[1], 101); - assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); - assertEq(initializedTicksLoadedList[0], 0); - } - - function testQuoter_quoteExactInput_2to1() public { - tokenPath.push(token2); - tokenPath.push(token1); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - assertEq(-deltaAmounts[1], 9871); - assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); - assertEq(initializedTicksLoadedList[0], 0); - } - - function testQuoter_quoteExactInput_0to2to1() public { - tokenPath.push(token0); - tokenPath.push(token2); - tokenPath.push(token1); - IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); - - assertEq(-deltaAmounts[2], 9745); - assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); - assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); - assertEq(initializedTicksLoadedList[0], 2); - assertEq(initializedTicksLoadedList[1], 0); - } - - function testQuoter_quoteExactOutputSingle_0to1() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactOutputSingle( - IQuoter.QuoteExactSingleParams({ - poolKey: key01, - zeroForOne: true, - exactAmount: type(uint128).max, - sqrtPriceLimitX96: SQRT_PRICE_100_102, - hookData: ZERO_BYTES - }) - ); - - assertEq(deltaAmounts[0], 9981); - assertEq(sqrtPriceX96After, SQRT_PRICE_100_102); - assertEq(initializedTicksLoaded, 0); - } - - function testQuoter_quoteExactOutputSingle_1to0() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactOutputSingle( - IQuoter.QuoteExactSingleParams({ - poolKey: key01, - zeroForOne: false, - exactAmount: type(uint128).max, - sqrtPriceLimitX96: SQRT_PRICE_102_100, - hookData: ZERO_BYTES - }) - ); - - assertEq(deltaAmounts[1], 9981); - assertEq(sqrtPriceX96After, SQRT_PRICE_102_100); - assertEq(initializedTicksLoaded, 0); - } - - function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { - tokenPath.push(token0); - tokenPath.push(token2); - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 15273); - assertEq(sqrtPriceX96AfterList[0], 78055527257643669242286029831); - assertEq(initializedTicksLoadedList[0], 2); - } - - function testQuoter_quoteExactOutput_0to2_1TickLoaded_initialiedAfter() public { - tokenPath.push(token0); - tokenPath.push(token2); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 6200); - assertEq(sqrtPriceX96AfterList[0], 78757225449310403327341205211); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactOutput_0to2_1TickLoaded() public { - tokenPath.push(token0); - tokenPath.push(token2); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 4029); - assertEq(sqrtPriceX96AfterList[0], 78924219757724709840818372098); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingInitialized() public { - setupPoolWithZeroTickInitialized(key02); - tokenPath.push(token0); - tokenPath.push(token2); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); - - // Tick 0 initialized. Tick after = 1 - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 102); - assertEq(sqrtPriceX96AfterList[0], 79224329176051641448521403903); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingNotInitialized() public { - tokenPath.push(token0); - tokenPath.push(token2); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 12); - assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); - assertEq(initializedTicksLoadedList[0], 0); - } - - function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { - tokenPath.push(token2); - tokenPath.push(token0); - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 15273); - assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 2); - } - - function testQuoter_quoteExactOutput_2to0_2TicksLoaded_initialiedAfter() public { - tokenPath.push(token2); - tokenPath.push(token0); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 6283); - assertEq(sqrtPriceX96AfterList[0], 79708304437530892332449657932); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 2); - } - - function testQuoter_quoteExactOutput_2to0_1TickLoaded() public { - tokenPath.push(token2); - tokenPath.push(token0); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 6055); - assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 1); - } - - function testQuoter_quoteExactOutput_2to1() public { - tokenPath.push(token2); - tokenPath.push(token1); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 10000); - assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 0); - } - - function testQuoter_quoteExactOutput_0to2to1() public { - tokenPath.push(token0); - tokenPath.push(token2); - tokenPath.push(token1); - - IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); - - assertEq(deltaAmounts[0], 10000); - assertEq(deltaAmounts[1], 0); - assertEq(deltaAmounts[2], -9745); - assertEq(sqrtPriceX96AfterList[0], 78461888503179331029803316753); - assertEq(sqrtPriceX96AfterList[1], 80007838904387594703933785072); - assertEq(initializedTicksLoadedList.length, 2); - assertEq(initializedTicksLoadedList[0], 2); - assertEq(initializedTicksLoadedList[1], 0); - } - - function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) - internal - pure - returns (PoolKey memory) - { - if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA); - return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, 60, IHooks(hookAddr)); - } - - function setupPool(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_PRICE_1_1, ZERO_BYTES); - MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); - MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - MIN_TICK, - MAX_TICK, - calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), - 0 - ), - ZERO_BYTES - ); - } - - function setupPoolMultiplePositions(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_PRICE_1_1, ZERO_BYTES); - MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); - MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - MIN_TICK, - MAX_TICK, - calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), - 0 - ), - ZERO_BYTES - ); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - -60, 60, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -60, 60, 100, 100).toInt256(), 0 - ), - ZERO_BYTES - ); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - -120, 120, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -120, 120, 100, 100).toInt256(), 0 - ), - ZERO_BYTES - ); - } - - function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { - PoolId poolId = poolKey.toId(); - (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); - if (sqrtPriceX96 == 0) { - manager.initialize(poolKey, SQRT_PRICE_1_1, ZERO_BYTES); - } - - MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); - MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - MIN_TICK, - MAX_TICK, - calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), - 0 - ), - ZERO_BYTES - ); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - 0, 60, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, 0, 60, 100, 100).toInt256(), 0 - ), - ZERO_BYTES - ); - positionManager.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams( - -120, 0, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -120, 0, 100, 100).toInt256(), 0 - ), - ZERO_BYTES - ); - } - - function calculateLiquidityFromAmounts( - uint160 sqrtRatioX96, - int24 tickLower, - int24 tickUpper, - uint256 amount0, - uint256 amount1 - ) internal pure returns (uint128 liquidity) { - uint160 sqrtRatioAX96 = TickMath.getSqrtPriceAtTick(tickLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtPriceAtTick(tickUpper); - liquidity = - LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1); - } - - function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) - internal - pure - returns (IQuoter.QuoteExactParams memory params) - { - PathKey[] memory path = new PathKey[](_tokenPath.length - 1); - for (uint256 i = 0; i < _tokenPath.length - 1; i++) { - path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); - } - - params.exactCurrency = Currency.wrap(address(_tokenPath[0])); - params.path = path; - params.exactAmount = uint128(amountIn); - } - - function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) - internal - pure - returns (IQuoter.QuoteExactParams memory params) - { - PathKey[] memory path = new PathKey[](_tokenPath.length - 1); - for (uint256 i = _tokenPath.length - 1; i > 0; i--) { - path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); - } - - params.exactCurrency = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); - params.path = path; - params.exactAmount = uint128(amountOut); - } -} diff --git a/test/SafeCallback.t.sol b/test/SafeCallback.t.sol index fd5157ab3..1e7673227 100644 --- a/test/SafeCallback.t.sol +++ b/test/SafeCallback.t.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: UNLICENSED +//SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "forge-std/Test.sol"; diff --git a/test/StateViewTest.t.sol b/test/StateViewTest.t.sol index 392d70484..c243f12fa 100644 --- a/test/StateViewTest.t.sol +++ b/test/StateViewTest.t.sol @@ -38,7 +38,7 @@ contract StateViewTest is Test, Deployers, Fuzzers, GasSnapshot { // Create the pool key = PoolKey(currency0, currency1, 3000, 60, IHooks(address(0x0))); poolId = key.toId(); - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1); state = new StateView(manager); } diff --git a/test/UniswapV4DeployerCompetition.t.sol b/test/UniswapV4DeployerCompetition.t.sol new file mode 100644 index 000000000..265d3b6fe --- /dev/null +++ b/test/UniswapV4DeployerCompetition.t.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Owned} from "solmate/src/auth/Owned.sol"; +import {Test} from "forge-std/Test.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {UniswapV4DeployerCompetition} from "../src/UniswapV4DeployerCompetition.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {VanityAddressLib} from "../src/libraries/VanityAddressLib.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {IUniswapV4DeployerCompetition} from "../src/interfaces/IUniswapV4DeployerCompetition.sol"; + +contract UniswapV4DeployerCompetitionTest is Test { + using VanityAddressLib for address; + + UniswapV4DeployerCompetition competition; + bytes32 initCodeHash; + address deployer; + address v4Owner; + address winner; + address defaultAddress; + uint256 competitionDeadline; + uint256 exclusiveDeployLength = 1 days; + + bytes32 mask20bytes = bytes32(uint256(type(uint96).max)); + + function setUp() public { + competitionDeadline = block.timestamp + 7 days; + v4Owner = makeAddr("V4Owner"); + winner = makeAddr("Winner"); + deployer = makeAddr("Deployer"); + vm.prank(deployer); + initCodeHash = keccak256(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + competition = + new UniswapV4DeployerCompetition(initCodeHash, competitionDeadline, deployer, exclusiveDeployLength); + defaultAddress = Create2.computeAddress(bytes32(0), initCodeHash, address(competition)); + } + + function test_defaultSalt_deploy_succeeds() public { + assertEq(competition.bestAddressSubmitter(), address(0)); + assertEq(competition.bestAddressSalt(), bytes32(0)); + assertEq(competition.bestAddress(), defaultAddress); + + assertEq(defaultAddress.code.length, 0); + vm.warp(competition.competitionDeadline() + 1); + vm.prank(deployer); + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + assertFalse(defaultAddress.code.length == 0); + assertEq(Owned(defaultAddress).owner(), v4Owner); + } + + function test_updateBestAddress_succeeds(bytes32 salt) public { + salt = (salt & mask20bytes) | bytes32(bytes20(winner)); + + assertEq(competition.bestAddressSubmitter(), address(0)); + assertEq(competition.bestAddressSalt(), bytes32(0)); + assertEq(competition.bestAddress(), defaultAddress); + + address newAddress = Create2.computeAddress(salt, initCodeHash, address(competition)); + vm.assume(newAddress.betterThan(defaultAddress)); + + vm.prank(winner); + vm.expectEmit(true, true, true, false, address(competition)); + emit IUniswapV4DeployerCompetition.NewAddressFound(newAddress, winner, VanityAddressLib.score(newAddress)); + competition.updateBestAddress(salt); + assertFalse(competition.bestAddress() == address(0), "best address not set"); + assertEq(competition.bestAddress(), newAddress, "wrong address set"); + assertEq(competition.bestAddressSubmitter(), winner, "wrong submitter set"); + assertEq(competition.bestAddressSalt(), salt, "incorrect salt set"); + address v4Core = competition.bestAddress(); + + assertEq(v4Core.code.length, 0); + vm.warp(competition.competitionDeadline() + 1); + vm.prank(deployer); + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + assertFalse(v4Core.code.length == 0); + assertEq(Owned(v4Core).owner(), v4Owner); + assertEq(address(competition).balance, 0 ether); + } + + function test_updateBestAddress_reverts_CompetitionOver(bytes32 salt) public { + vm.warp(competition.competitionDeadline() + 1); + vm.expectRevert( + abi.encodeWithSelector( + IUniswapV4DeployerCompetition.CompetitionOver.selector, + block.timestamp, + competition.competitionDeadline() + ) + ); + competition.updateBestAddress(salt); + } + + function test_updateBestAddress_reverts_InvalidSigner(bytes32 salt) public { + vm.assume(bytes20(salt) != bytes20(0)); + vm.assume(bytes20(salt) != bytes20(winner)); + + vm.expectRevert(abi.encodeWithSelector(IUniswapV4DeployerCompetition.InvalidSender.selector, salt, winner)); + vm.prank(winner); + competition.updateBestAddress(salt); + } + + function test_updateBestAddress_reverts_WorseAddress(bytes32 salt) public { + vm.assume(salt != bytes32(0)); + salt = (salt & mask20bytes) | bytes32(bytes20(winner)); + + address newAddr = Create2.computeAddress(salt, initCodeHash, address(competition)); + if (!newAddr.betterThan(defaultAddress)) { + vm.expectRevert( + abi.encodeWithSelector( + IUniswapV4DeployerCompetition.WorseAddress.selector, + newAddr, + competition.bestAddress(), + newAddr.score(), + competition.bestAddress().score() + ) + ); + vm.prank(winner); + competition.updateBestAddress(salt); + } else { + vm.prank(winner); + competition.updateBestAddress(salt); + assertEq(competition.bestAddressSubmitter(), winner); + assertEq(competition.bestAddressSalt(), salt); + assertEq(competition.bestAddress(), newAddr); + } + } + + function test_deploy_succeeds(bytes32 salt) public { + salt = (salt & mask20bytes) | bytes32(bytes20(winner)); + + address newAddress = Create2.computeAddress(salt, initCodeHash, address(competition)); + vm.assume(newAddress.betterThan(defaultAddress)); + + vm.prank(winner); + competition.updateBestAddress(salt); + address v4Core = competition.bestAddress(); + + vm.warp(competition.competitionDeadline() + 1); + vm.prank(deployer); + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + assertFalse(v4Core.code.length == 0); + assertEq(Owned(v4Core).owner(), v4Owner); + assertEq(TickMath.MAX_TICK_SPACING, type(int16).max); + } + + function test_deploy_reverts_CompetitionNotOver(uint256 timestamp) public { + vm.assume(timestamp < competition.competitionDeadline()); + vm.warp(timestamp); + vm.expectRevert( + abi.encodeWithSelector( + IUniswapV4DeployerCompetition.CompetitionNotOver.selector, timestamp, competition.competitionDeadline() + ) + ); + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + } + + function test_deploy_reverts_InvalidBytecode() public { + vm.expectRevert(IUniswapV4DeployerCompetition.InvalidBytecode.selector); + vm.prank(deployer); + // set the owner as the winner not the correct owner + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(winner)))); + } + + function test_deploy_reverts_NotAllowedToDeploy() public { + vm.warp(competition.competitionDeadline() + 1); + vm.prank(address(1)); + vm.expectRevert( + abi.encodeWithSelector(IUniswapV4DeployerCompetition.NotAllowedToDeploy.selector, address(1), deployer) + ); + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + } + + function test_deploy_succeeds_afterExcusiveDeployDeadline() public { + vm.warp(competition.exclusiveDeployDeadline() + 1); + vm.prank(address(1)); + competition.deploy(abi.encodePacked(type(PoolManager).creationCode, uint256(uint160(v4Owner)))); + } +} diff --git a/test/UnorderedNonce.t.sol b/test/UnorderedNonce.t.sol index 9683c763c..f7b46b2da 100644 --- a/test/UnorderedNonce.t.sol +++ b/test/UnorderedNonce.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; @@ -107,4 +107,16 @@ contract UnorderedNonceTest is Test { unorderedNonce.spendNonce(address(this), second); } } + + function test_fuzz_revokeNonce(uint256 nonce) public { + unorderedNonce.revokeNonce(nonce); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.revokeNonce(nonce); + } + + function test_fuzz_revokeNonce_twoNonces(uint256 first, uint256 second) public { + unorderedNonce.revokeNonce(first); + if (first == second) vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.revokeNonce(second); + } } diff --git a/test/V4Quoter.t.sol b/test/V4Quoter.t.sol new file mode 100644 index 000000000..fc91ce47a --- /dev/null +++ b/test/V4Quoter.t.sol @@ -0,0 +1,577 @@ +//SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {PathKey} from "../src/libraries/PathKey.sol"; +import {IV4Quoter} from "../src/interfaces/IV4Quoter.sol"; +import {V4Quoter} from "../src/lens/V4Quoter.sol"; +import {BaseV4Quoter} from "../src/base/BaseV4Quoter.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; + +// v4-core +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; + +// solmate +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; + +contract QuoterTest is Test, Deployers, GasSnapshot { + using SafeCast for *; + using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; + + // Min tick for full range with tick spacing of 60 + int24 internal constant MIN_TICK = -887220; + // Max tick for full range with tick spacing of 60 + int24 internal constant MAX_TICK = -MIN_TICK; + + uint160 internal constant SQRT_PRICE_100_102 = 78447570448055484695608110440; + uint160 internal constant SQRT_PRICE_102_100 = 80016521857016594389520272648; + + uint256 internal constant CONTROLLER_GAS_LIMIT = 500000; + + V4Quoter quoter; + + PoolModifyLiquidityTest positionManager; + + MockERC20 token0; + MockERC20 token1; + MockERC20 token2; + + PoolKey key01; + PoolKey key02; + PoolKey key12; + + MockERC20[] tokenPath; + + function setUp() public { + deployFreshManagerAndRouters(); + quoter = new V4Quoter(IPoolManager(manager)); + positionManager = new PoolModifyLiquidityTest(manager); + + // salts are chosen so that address(token0) < address(token1) && address(token1) < address(token2) + token0 = new MockERC20("Test0", "0", 18); + vm.etch(address(0x1111), address(token0).code); + token0 = MockERC20(address(0x1111)); + token0.mint(address(this), 2 ** 128); + + vm.etch(address(0x2222), address(token0).code); + token1 = MockERC20(address(0x2222)); + token1.mint(address(this), 2 ** 128); + + vm.etch(address(0x3333), address(token0).code); + token2 = MockERC20(address(0x3333)); + token2.mint(address(this), 2 ** 128); + + key01 = createPoolKey(token0, token1, address(0)); + key02 = createPoolKey(token0, token2, address(0)); + key12 = createPoolKey(token1, token2, address(0)); + setupPool(key01); + setupPool(key12); + setupPoolMultiplePositions(key02); + } + + function testQuoter_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { + uint256 amountIn = 10000; + uint256 expectedAmountOut = 9871; + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInputSingle( + IV4Quoter.QuoteExactSingleParams({ + poolKey: key02, + zeroForOne: true, + exactAmount: uint128(amountIn), + hookData: ZERO_BYTES + }) + ); + snapLastCall("Quoter_exactInputSingle_zeroForOne_multiplePositions"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, expectedAmountOut); + } + + function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { + uint256 amountIn = 10000; + uint256 expectedAmountOut = 9871; + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInputSingle( + IV4Quoter.QuoteExactSingleParams({ + poolKey: key02, + zeroForOne: false, + exactAmount: uint128(amountIn), + hookData: ZERO_BYTES + }) + ); + snapLastCall("Quoter_exactInputSingle_oneForZero_multiplePositions"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, expectedAmountOut); + } + + function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 9871); + } + + function testQuoter_quoteExactInput_0to2_2TicksLoaded_initializedAfter() public { + tokenPath.push(token0); + tokenPath.push(token2); + + // The swap amount is set such that the active tick after the swap is -120. + // -120 is an initialized tick for this pool. We check that we don't count it. + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 6143); + } + + function testQuoter_quoteExactInput_0to2_1TickLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + + // The swap amount is set such that the active tick after the swap is -60. + // -60 is an initialized tick for this pool. We check that we don't count it. + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + snapLastCall("Quoter_quoteExactInput_oneHop_1TickLoaded"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 3971); + } + + function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token0); + tokenPath.push(token2); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 8); + } + + function testQuoter_quoteExactInput_0to2_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token0); + tokenPath.push(token2); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 8); + } + + function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 9871); + } + + function testQuoter_quoteExactInput_2to0_2TicksLoaded_initializedAfter() public { + tokenPath.push(token2); + tokenPath.push(token0); + + // The swap amount is set such that the active tick after the swap is 120. + // 120 is an initialized tick for this pool. We check that we don't count it. + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + snapLastCall("Quoter_quoteExactInput_oneHop_initializedAfter"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 6190); + } + + function testQuoter_quoteExactInput_2to0_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token2); + tokenPath.push(token0); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); + + // Tick 0 initialized. Tick after = 1 + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + snapLastCall("Quoter_quoteExactInput_oneHop_startingInitialized"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 198); + } + + // 2->0 starting not initialized + function testQuoter_quoteExactInput_2to0_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token2); + tokenPath.push(token0); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 101); + } + + function testQuoter_quoteExactInput_2to1() public { + tokenPath.push(token2); + tokenPath.push(token1); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 9871); + } + + function testQuoter_quoteExactInput_0to2to1() public { + tokenPath.push(token0); + tokenPath.push(token2); + tokenPath.push(token1); + IV4Quoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + + snapLastCall("Quoter_quoteExactInput_twoHops"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountOut, 9745); + } + + function testQuoter_quoteExactOutputSingle_0to1() public { + uint256 amountOut = 10000; + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutputSingle( + IV4Quoter.QuoteExactSingleParams({ + poolKey: key01, + zeroForOne: true, + exactAmount: uint128(amountOut), + hookData: ZERO_BYTES + }) + ); + snapLastCall("Quoter_exactOutputSingle_zeroForOne"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 10133); + } + + function testQuoter_quoteExactOutputSingle_1to0() public { + uint256 amountOut = 10000; + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutputSingle( + IV4Quoter.QuoteExactSingleParams({ + poolKey: key01, + zeroForOne: false, + exactAmount: uint128(amountOut), + hookData: ZERO_BYTES + }) + ); + snapLastCall("Quoter_exactOutputSingle_oneForZero"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 10133); + } + + function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + snapLastCall("Quoter_quoteExactOutput_oneHop_2TicksLoaded"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 15273); + } + + function testQuoter_quoteExactOutput_0to2_1TickLoaded_initializedAfter() public { + tokenPath.push(token0); + tokenPath.push(token2); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + snapLastCall("Quoter_quoteExactOutput_oneHop_initializedAfter"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 6200); + } + + function testQuoter_quoteExactOutput_0to2_1TickLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + snapLastCall("Quoter_quoteExactOutput_oneHop_1TickLoaded"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 4029); + } + + function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token0); + tokenPath.push(token2); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); + + // Tick 0 initialized. Tick after = 1 + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + snapLastCall("Quoter_quoteExactOutput_oneHop_startingInitialized"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 102); + } + + function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token0); + tokenPath.push(token2); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 12); + } + + function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 15273); + } + + function testQuoter_quoteExactOutput_2to0_2TicksLoaded_initializedAfter() public { + tokenPath.push(token2); + tokenPath.push(token0); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 6283); + } + + function testQuoter_quoteExactOutput_2to0_1TickLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 6055); + } + + function testQuoter_quoteExactOutput_2to1() public { + tokenPath.push(token2); + tokenPath.push(token1); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 10000); + } + + function testQuoter_quoteExactOutput_0to2to1() public { + tokenPath.push(token0); + tokenPath.push(token2); + tokenPath.push(token1); + + IV4Quoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); + + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + + snapLastCall("Quoter_quoteExactOutput_twoHops"); + + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); + assertEq(amountIn, 10000); + } + + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) + internal + pure + returns (PoolKey memory) + { + if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, 60, IHooks(hookAddr)); + } + + function setupPool(PoolKey memory poolKey) internal { + manager.initialize(poolKey, SQRT_PRICE_1_1); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), + 0 + ), + ZERO_BYTES + ); + } + + function setupPoolMultiplePositions(PoolKey memory poolKey) internal { + manager.initialize(poolKey, SQRT_PRICE_1_1); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), + 0 + ), + ZERO_BYTES + ); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + -60, 60, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -60, 60, 100, 100).toInt256(), 0 + ), + ZERO_BYTES + ); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + -120, 120, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -120, 120, 100, 100).toInt256(), 0 + ), + ZERO_BYTES + ); + } + + function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { + PoolId poolId = poolKey.toId(); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); + if (sqrtPriceX96 == 0) { + manager.initialize(poolKey, SQRT_PRICE_1_1); + } + + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), + 0 + ), + ZERO_BYTES + ); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + 0, 60, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, 0, 60, 100, 100).toInt256(), 0 + ), + ZERO_BYTES + ); + positionManager.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams( + -120, 0, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -120, 0, 100, 100).toInt256(), 0 + ), + ZERO_BYTES + ); + } + + function calculateLiquidityFromAmounts( + uint160 sqrtRatioX96, + int24 tickLower, + int24 tickUpper, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + uint160 sqrtRatioAX96 = TickMath.getSqrtPriceAtTick(tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtPriceAtTick(tickUpper); + liquidity = + LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1); + } + + function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) + internal + pure + returns (IV4Quoter.QuoteExactParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = 0; i < _tokenPath.length - 1; i++) { + path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); + } + + params.exactCurrency = Currency.wrap(address(_tokenPath[0])); + params.path = path; + params.exactAmount = uint128(amountIn); + } + + function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) + internal + pure + returns (IV4Quoter.QuoteExactParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = _tokenPath.length - 1; i > 0; i--) { + path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); + } + + params.exactCurrency = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); + params.path = path; + params.exactAmount = uint128(amountOut); + } +} diff --git a/test/base64.sol b/test/base64.sol new file mode 100644 index 000000000..811a8f09b --- /dev/null +++ b/test/base64.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @title Base64 +/// @author Brecht Devos - +/// @notice Provides functions for decoding base64 +library Base64 { + bytes internal constant TABLE_DECODE = hex"0000000000000000000000000000000000000000000000000000000000000000" + hex"00000000000000000000003e0000003f3435363738393a3b3c3d000000000000" + hex"00000102030405060708090a0b0c0d0e0f101112131415161718190000000000" + hex"001a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132330000000000"; + + function decode(string memory _data) internal pure returns (bytes memory) { + bytes memory data = bytes(_data); + + if (data.length == 0) return new bytes(0); + require(data.length % 4 == 0, "invalid base64 decoder input"); + + // load the table into memory + bytes memory table = TABLE_DECODE; + + // every 4 characters represent 3 bytes + uint256 decodedLen = (data.length / 4) * 3; + + // add some extra buffer at the end required for the writing + bytes memory result = new bytes(decodedLen + 32); + + assembly { + // padding with '=' + let lastBytes := mload(add(data, mload(data))) + if eq(and(lastBytes, 0xFF), 0x3d) { + decodedLen := sub(decodedLen, 1) + if eq(and(lastBytes, 0xFFFF), 0x3d3d) { decodedLen := sub(decodedLen, 1) } + } + + // set the actual output length + mstore(result, decodedLen) + + // prepare the lookup table + let tablePtr := add(table, 1) + + // input ptr + let dataPtr := data + let endPtr := add(dataPtr, mload(data)) + + // result ptr, jump over length + let resultPtr := add(result, 32) + + // run over the input, 4 characters at a time + for {} lt(dataPtr, endPtr) {} { + // read 4 characters + dataPtr := add(dataPtr, 4) + let input := mload(dataPtr) + + // write 3 bytes + let output := + add( + add( + shl(18, and(mload(add(tablePtr, and(shr(24, input), 0xFF))), 0xFF)), + shl(12, and(mload(add(tablePtr, and(shr(16, input), 0xFF))), 0xFF)) + ), + add( + shl(6, and(mload(add(tablePtr, and(shr(8, input), 0xFF))), 0xFF)), + and(mload(add(tablePtr, and(input, 0xFF))), 0xFF) + ) + ) + mstore(resultPtr, shl(232, output)) + resultPtr := add(resultPtr, 3) + } + } + + return result; + } +} diff --git a/test/ERC721Permit.t.sol b/test/erc721Permit/ERC721Permit.permit.t.sol similarity index 87% rename from test/ERC721Permit.t.sol rename to test/erc721Permit/ERC721Permit.permit.t.sol index 0dc633d3a..0a741381e 100644 --- a/test/ERC721Permit.t.sol +++ b/test/erc721Permit/ERC721Permit.permit.t.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; -import {ERC721PermitHashLibrary} from "../src/libraries/ERC721PermitHash.sol"; -import {MockERC721Permit} from "./mocks/MockERC721Permit.sol"; -import {IERC721Permit_v4} from "../src/interfaces/IERC721Permit_v4.sol"; +import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; +import {MockERC721Permit} from "../mocks/MockERC721Permit.sol"; +import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; import {IERC721} from "forge-std/interfaces/IERC721.sol"; -import {UnorderedNonce} from "../src/base/UnorderedNonce.sol"; +import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; contract ERC721PermitTest is Test { MockERC721Permit erc721Permit; @@ -61,15 +61,15 @@ contract ERC721PermitTest is Test { // --- Test the signature-based approvals (permit) --- function test_permitTypeHash() public pure { assertEq( - ERC721PermitHashLibrary.PERMIT_TYPEHASH, + ERC721PermitHash.PERMIT_TYPEHASH, keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)") ); } function test_fuzz_permitHash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) public pure { bytes32 expectedHash = - keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); - assertEq(expectedHash, ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline)); + keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + assertEq(expectedHash, ERC721PermitHash.hashPermit(spender, tokenId, nonce, deadline)); } function test_domainSeparator() public view { @@ -93,7 +93,7 @@ contract ERC721PermitTest is Test { uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - bytes32 digest = _getDigest(spender, tokenId, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(spender, tokenId, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -127,7 +127,7 @@ contract ERC721PermitTest is Test { uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - bytes32 digest = _getDigest(spender, tokenId, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(spender, tokenId, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -163,7 +163,7 @@ contract ERC721PermitTest is Test { _permit(alicePK, tokenIdAlice, bob, nonce); // alice cannot reuse the nonce - bytes32 digest = _getDigest(bob, tokenIdAlice, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(bob, tokenIdAlice, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -186,7 +186,7 @@ contract ERC721PermitTest is Test { _permit(alicePK, tokenIdAlice, bob, nonce); // alice cannot reuse the nonce for the second token - bytes32 digest = _getDigest(bob, tokenIdAlice2, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(bob, tokenIdAlice2, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -202,7 +202,7 @@ contract ERC721PermitTest is Test { uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - bytes32 digest = _getDigest(bob, tokenId, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(bob, tokenId, nonce, block.timestamp); // bob attempts signing an approval for himself (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPK, digest); @@ -229,13 +229,13 @@ contract ERC721PermitTest is Test { assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); } - function test_fuzz_erc721Permit_deadlineExpired(address spender) public { + function test_fuzz_erc721Permit_SignatureDeadlineExpired(address spender) public { vm.prank(alice); uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - uint256 deadline = block.timestamp; - bytes32 digest = _getDigest(spender, tokenId, nonce, deadline); + uint256 deadline = vm.getBlockTimestamp(); + bytes32 digest = _getPermitDigest(spender, tokenId, nonce, deadline); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -252,7 +252,7 @@ contract ERC721PermitTest is Test { // -- Permit but deadline expired -- // vm.startPrank(spender); - vm.expectRevert(IERC721Permit_v4.DeadlineExpired.selector); + vm.expectRevert(IERC721Permit_v4.SignatureDeadlineExpired.selector); erc721Permit.permit(spender, tokenId, deadline, nonce, signature); vm.stopPrank(); @@ -266,7 +266,7 @@ contract ERC721PermitTest is Test { // Helpers related to permit function _permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { - bytes32 digest = _getDigest(operator, tokenId, 1, block.timestamp); + bytes32 digest = _getPermitDigest(operator, tokenId, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -275,7 +275,7 @@ contract ERC721PermitTest is Test { erc721Permit.permit(operator, tokenId, block.timestamp, nonce, signature); } - function _getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + function _getPermitDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) internal view returns (bytes32 digest) @@ -284,7 +284,7 @@ contract ERC721PermitTest is Test { abi.encodePacked( "\x19\x01", erc721Permit.DOMAIN_SEPARATOR(), - keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) ) ); } diff --git a/test/erc721Permit/ERC721Permit.permitForAll.t.sol b/test/erc721Permit/ERC721Permit.permitForAll.t.sol new file mode 100644 index 000000000..c8150895f --- /dev/null +++ b/test/erc721Permit/ERC721Permit.permitForAll.t.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; + +import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; +import {MockERC721Permit} from "../mocks/MockERC721Permit.sol"; +import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; +import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; + +contract ERC721PermitForAllTest is Test { + MockERC721Permit erc721Permit; + address alice; + uint256 alicePK; + address bob; + uint256 bobPK; + + string constant name = "Mock ERC721Permit_v4"; + string constant symbol = "MOCK721"; + + function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob, bobPK) = makeAddrAndKey("BOB"); + + erc721Permit = new MockERC721Permit(name, symbol); + } + + // --- Test the overriden setApprovalForAll --- + function test_fuzz_setApprovalForAll(address operator) public { + assertEq(erc721Permit.isApprovedForAll(address(this), operator), false); + + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(address(this), operator, true); + erc721Permit.setApprovalForAll(operator, true); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), true); + } + + function test_fuzz_setApprovalForAll_revoke(address operator) public { + assertEq(erc721Permit.isApprovedForAll(address(this), operator), false); + erc721Permit.setApprovalForAll(operator, true); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), true); + + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(address(this), operator, false); + erc721Permit.setApprovalForAll(operator, false); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), false); + } + + // --- Test the signature-based approvals (permitForAll) --- + function test_permitForAllTypeHash() public pure { + assertEq( + ERC721PermitHash.PERMIT_FOR_ALL_TYPEHASH, + keccak256("PermitForAll(address operator,bool approved,uint256 nonce,uint256 deadline)") + ); + } + + function test_fuzz_permitForAllHash(address operator, bool approved, uint256 nonce, uint256 deadline) public pure { + bytes32 expectedHash = + keccak256(abi.encode(ERC721PermitHash.PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + assertEq(expectedHash, ERC721PermitHash.hashPermitForAll(operator, approved, nonce, deadline)); + } + + /// @dev operator uses alice's signature to approve itself + function test_fuzz_erc721permitForAll_operator(address operator) public { + vm.assume(operator != alice); + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, block.timestamp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // -- PermitForAll -- // + vm.startPrank(operator); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(alice, operator, true); + erc721Permit.permitForAll(alice, operator, true, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals set + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), true); + + // nonce was spent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010 + } + + /// @dev a third party caller uses alice's signature to give `operator` the approval + function test_fuzz_erc721permitForAll_caller(address caller, address operator) public { + vm.assume(operator != alice); + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, block.timestamp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // -- PermitForAll -- // + vm.startPrank(caller); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(alice, operator, true); + erc721Permit.permitForAll(alice, operator, true, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals set + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), true); + + // nonce was spent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010 + } + + function test_fuzz_erc721permitForAll_nonceAlreadyUsed(uint256 nonce) public { + // alice gives bob operator permissions + _permitForAll(alicePK, alice, bob, true, nonce); + + // alice cannot reuse the nonce + bytes32 digest = _getPermitForAllDigest(bob, true, nonce, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.startPrank(alice); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permitForAll(alice, bob, true, block.timestamp, nonce, signature); + vm.stopPrank(); + } + + function test_fuzz_erc721permitForAll_invalidSigner(uint256 nonce) public { + bytes32 digest = _getPermitForAllDigest(bob, true, nonce, block.timestamp); + + // bob attempts signing an approval for himself + (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permitForAll(alice, bob, true, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + function test_fuzz_erc721permitForAll_SignatureDeadlineExpired(address operator) public { + uint256 nonce = 1; + uint256 deadline = vm.getBlockTimestamp(); + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // fast forward to exceed deadline + skip(1); + + // -- PermitForAll but deadline expired -- // + vm.startPrank(operator); + vm.expectRevert(IERC721Permit_v4.SignatureDeadlineExpired.selector); + erc721Permit.permitForAll(alice, operator, true, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + /// @dev a signature for permit() cannot be used for permitForAll() + function test_fuzz_erc721Permit_invalidSignatureForAll(address operator) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitDigest(operator, tokenId, nonce, deadline); + + // alice signs a permit for operator + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // signature does not work with permitForAll + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + /// @dev a signature for permitForAll() cannot be used for permit() + function test_fuzz_erc721PermitForAll_invalidSignatureForPermit(address operator) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, deadline); + + // alice signs a permit for operator + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // signature does not work with permit + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permit(bob, tokenId, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + /// @dev a nonce used in permit is unusable for permitForAll + function test_fuzz_erc721PermitForAll_permitNonceUsed(uint256 nonce) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitDigest(bob, tokenId, nonce, deadline); + // alice signs a permit for bob + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // bob gives himself approval + vm.prank(bob); + erc721Permit.permit(bob, tokenId, deadline, nonce, signature); + assertEq(erc721Permit.getApproved(tokenId), bob); + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // alice tries re-using the nonce for permitForAll + digest = _getPermitForAllDigest(bob, true, nonce, deadline); + (v, r, s) = vm.sign(alicePK, digest); + signature = abi.encodePacked(r, s, v); + + // Nonce does not work with permitForAll + vm.startPrank(bob); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); + vm.stopPrank(); + } + + /// @notice revoking a nonce prevents it from being used in permitForAll() + function test_fuzz_erc721PermitForAll_revokedNonceUsed(uint256 nonce) public { + // alice revokes the nonce + vm.prank(alice); + erc721Permit.revokeNonce(nonce); + + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitForAllDigest(bob, true, nonce, deadline); + // alice signs a permit for bob + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // Nonce does not work with permitForAll + vm.startPrank(bob); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); + vm.stopPrank(); + } + + // Helpers related to permitForAll + function _permitForAll(uint256 privateKey, address owner, address operator, bool approved, uint256 nonce) + internal + { + bytes32 digest = _getPermitForAllDigest(operator, approved, nonce, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(operator); + erc721Permit.permitForAll(owner, operator, approved, block.timestamp, nonce, signature); + } + + function _getPermitForAllDigest(address operator, bool approved, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + erc721Permit.DOMAIN_SEPARATOR(), + keccak256(abi.encode(ERC721PermitHash.PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)) + ) + ); + } + + function _getPermitDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + erc721Permit.DOMAIN_SEPARATOR(), + keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + ) + ); + } + + // copied the private function from UnorderedNonce.sol + function _getBitmapFromNonce(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) { + wordPos = uint248(nonce >> 8); + bitPos = uint8(nonce); + } +} diff --git a/test/libraries/BipsLibrary.t.sol b/test/libraries/BipsLibrary.t.sol index f895cf4d5..02cc67d71 100644 --- a/test/libraries/BipsLibrary.t.sol +++ b/test/libraries/BipsLibrary.t.sol @@ -2,12 +2,18 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import "forge-std/StdError.sol"; import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; -contract PositionConfigTest is Test { +contract BipsLibraryTest is Test { using BipsLibrary for uint256; + // The block gas limit set in foundry config is 300_000_000 (300M) for testing purposes + uint256 BLOCK_GAS_LIMIT; + + function setUp() public { + BLOCK_GAS_LIMIT = block.gaslimit; + } + function test_fuzz_calculatePortion(uint256 amount, uint256 bips) public { amount = bound(amount, 0, uint256(type(uint128).max)); if (bips > BipsLibrary.BPS_DENOMINATOR) { @@ -18,12 +24,12 @@ contract PositionConfigTest is Test { } } - function test_fuzz_gasLimitt(uint256 bips) public { + function test_fuzz_gasLimit(uint256 bips) public { if (bips > BipsLibrary.BPS_DENOMINATOR) { vm.expectRevert(BipsLibrary.InvalidBips.selector); block.gaslimit.calculatePortion(bips); } else { - assertEq(block.gaslimit.calculatePortion(bips), block.gaslimit * bips / BipsLibrary.BPS_DENOMINATOR); + assertEq(block.gaslimit.calculatePortion(bips), BLOCK_GAS_LIMIT * bips / BipsLibrary.BPS_DENOMINATOR); } } @@ -32,13 +38,14 @@ contract PositionConfigTest is Test { } function test_gasLimit_1_percent() public view { - /// 100 bps = 1% - // 1% of 3_000_000_000 is 30_000_000 - assertEq(30_000_000, block.gaslimit.calculatePortion(100)); + // 100 bps = 1% + // 1% of 30M is 300K + assertEq(BLOCK_GAS_LIMIT / 100, block.gaslimit.calculatePortion(100)); } function test_gasLimit_1BP() public view { - /// 1bp is 0.01% - assertEq(300_000, block.gaslimit.calculatePortion(1)); + // 1bp is 0.01% + // 0.01% of 30M is 300 + assertEq(BLOCK_GAS_LIMIT / 10000, block.gaslimit.calculatePortion(1)); } } diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index 293986974..b2aa2d78b 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -6,9 +6,10 @@ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {MockCalldataDecoder} from "../mocks/MockCalldataDecoder.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {PathKey} from "../../src/libraries/PathKey.sol"; +import {CalldataDecoder} from "../../src/libraries/CalldataDecoder.sol"; contract CalldataDecoderTest is Test { MockCalldataDecoder decoder; @@ -19,44 +20,34 @@ contract CalldataDecoderTest is Test { function test_fuzz_decodeModifyLiquidityParams( uint256 _tokenId, - PositionConfig calldata _config, uint256 _liquidity, uint128 _amount0, uint128 _amount1, bytes calldata _hookData ) public view { - bytes memory params = abi.encode(_tokenId, _config, _liquidity, _amount0, _amount1, _hookData); - ( - uint256 tokenId, - PositionConfig memory config, - uint256 liquidity, - uint128 amount0, - uint128 amount1, - bytes memory hookData - ) = decoder.decodeModifyLiquidityParams(params); + bytes memory params = abi.encode(_tokenId, _liquidity, _amount0, _amount1, _hookData); + (uint256 tokenId, uint256 liquidity, uint128 amount0, uint128 amount1, bytes memory hookData) = + decoder.decodeModifyLiquidityParams(params); assertEq(tokenId, _tokenId); assertEq(liquidity, _liquidity); assertEq(amount0, _amount0); assertEq(amount1, _amount1); assertEq(hookData, _hookData); - _assertEq(_config, config); } function test_fuzz_decodeBurnParams( uint256 _tokenId, - PositionConfig calldata _config, uint128 _amount0Min, uint128 _amount1Min, bytes calldata _hookData ) public view { - bytes memory params = abi.encode(_tokenId, _config, _amount0Min, _amount1Min, _hookData); - (uint256 tokenId, PositionConfig memory config, uint128 amount0Min, uint128 amount1Min, bytes memory hookData) = + bytes memory params = abi.encode(_tokenId, _amount0Min, _amount1Min, _hookData); + (uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes memory hookData) = decoder.decodeBurnParams(params); assertEq(tokenId, _tokenId); assertEq(hookData, _hookData); - _assertEq(_config, config); assertEq(amount0Min, _amount0Min); assertEq(amount1Min, _amount1Min); } @@ -69,22 +60,49 @@ contract CalldataDecoderTest is Test { address _owner, bytes calldata _hookData ) public view { - bytes memory params = abi.encode(_config, _liquidity, _amount0Max, _amount1Max, _owner, _hookData); - ( - PositionConfig memory config, - uint256 liquidity, - uint128 amount0Max, - uint128 amount1Max, - address owner, - bytes memory hookData - ) = decoder.decodeMintParams(params); + bytes memory params = abi.encode( + _config.poolKey, + _config.tickLower, + _config.tickUpper, + _liquidity, + _amount0Max, + _amount1Max, + _owner, + _hookData + ); + + (MockCalldataDecoder.MintParams memory mintParams) = decoder.decodeMintParams(params); + + assertEq(mintParams.liquidity, _liquidity); + assertEq(mintParams.amount0Max, _amount0Max); + assertEq(mintParams.amount1Max, _amount1Max); + assertEq(mintParams.owner, _owner); + assertEq(mintParams.hookData, _hookData); + _assertEq(mintParams.poolKey, _config.poolKey); + assertEq(mintParams.tickLower, _config.tickLower); + assertEq(mintParams.tickUpper, _config.tickUpper); + } - assertEq(liquidity, _liquidity); - assertEq(amount0Max, _amount0Max); - assertEq(amount1Max, _amount1Max); - assertEq(owner, _owner); - assertEq(hookData, _hookData); - _assertEq(_config, config); + function test_fuzz_decodeMintFromDeltasParams( + PositionConfig calldata _config, + uint128 _amount0Max, + uint128 _amount1Max, + address _owner, + bytes calldata _hookData + ) public view { + bytes memory params = abi.encode( + _config.poolKey, _config.tickLower, _config.tickUpper, _amount0Max, _amount1Max, _owner, _hookData + ); + + (MockCalldataDecoder.MintFromDeltasParams memory mintParams) = decoder.decodeMintFromDeltasParams(params); + + _assertEq(mintParams.poolKey, _config.poolKey); + assertEq(mintParams.tickLower, _config.tickLower); + assertEq(mintParams.tickUpper, _config.tickUpper); + assertEq(mintParams.amount0Max, _amount0Max); + assertEq(mintParams.amount1Max, _amount1Max); + assertEq(mintParams.owner, _owner); + assertEq(mintParams.hookData, _hookData); } function test_fuzz_decodeSwapExactInParams(IV4Router.ExactInputParams calldata _swapParams) public view { @@ -107,7 +125,6 @@ contract CalldataDecoderTest is Test { assertEq(swapParams.zeroForOne, _swapParams.zeroForOne); assertEq(swapParams.amountIn, _swapParams.amountIn); assertEq(swapParams.amountOutMinimum, _swapParams.amountOutMinimum); - assertEq(swapParams.sqrtPriceLimitX96, _swapParams.sqrtPriceLimitX96); assertEq(swapParams.hookData, _swapParams.hookData); _assertEq(swapParams.poolKey, _swapParams.poolKey); } @@ -132,7 +149,6 @@ contract CalldataDecoderTest is Test { assertEq(swapParams.zeroForOne, _swapParams.zeroForOne); assertEq(swapParams.amountOut, _swapParams.amountOut); assertEq(swapParams.amountInMaximum, _swapParams.amountInMaximum); - assertEq(swapParams.sqrtPriceLimitX96, _swapParams.sqrtPriceLimitX96); assertEq(swapParams.hookData, _swapParams.hookData); _assertEq(swapParams.poolKey, _swapParams.poolKey); } @@ -145,6 +161,18 @@ contract CalldataDecoderTest is Test { assertEq(_address, __address); } + function test_decodeCurrencyAndAddress_outOutBounds() public { + Currency currency = Currency.wrap(address(0x12341234)); + address addy = address(0x23453456); + + bytes memory params = abi.encode(currency, addy); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrencyAndAddress(invalidParams); + } + function test_fuzz_decodeCurrency(Currency _currency) public view { bytes memory params = abi.encode(_currency); (Currency currency) = decoder.decodeCurrency(params); @@ -152,6 +180,59 @@ contract CalldataDecoderTest is Test { assertEq(Currency.unwrap(currency), Currency.unwrap(_currency)); } + function test_decodeCurrency_outOutBounds() public { + Currency currency = Currency.wrap(address(0x12341234)); + + bytes memory params = abi.encode(currency); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrency(invalidParams); + } + + function test_fuzz_decodeActionsRouterParams(bytes memory _actions, bytes[] memory _actionParams) public view { + bytes memory params = abi.encode(_actions, _actionParams); + (bytes memory actions, bytes[] memory actionParams) = decoder.decodeActionsRouterParams(params); + + assertEq(actions, _actions); + for (uint256 i = 0; i < _actionParams.length; i++) { + assertEq(actionParams[i], _actionParams[i]); + } + } + + function test_decodeActionsRouterParams_sliceOutOfBounds() public { + // create actions and parameters + bytes memory _actions = hex"12345678"; + bytes[] memory _actionParams = new bytes[](4); + _actionParams[0] = hex"11111111"; + _actionParams[1] = hex"22"; + _actionParams[2] = hex"3333333333333333"; + _actionParams[3] = hex"4444444444444444444444444444444444444444444444444444444444444444"; + + bytes memory params = abi.encode(_actions, _actionParams); + + bytes memory invalidParams = _removeFinalByte(params); + + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeActionsRouterParams(invalidParams); + } + + function test_decodeActionsRouterParams_emptyParams() public view { + // create actions and parameters + bytes memory _actions = hex""; + bytes[] memory _actionParams = new bytes[](0); + + bytes memory params = abi.encode(_actions, _actionParams); + + (bytes memory actions, bytes[] memory actionParams) = decoder.decodeActionsRouterParams(params); + assertEq(actions, _actions); + assertEq(actionParams.length, _actionParams.length); + assertEq(actionParams.length, 0); + } + function test_fuzz_decodeCurrencyPair(Currency _currency0, Currency _currency1) public view { bytes memory params = abi.encode(_currency0, _currency1); (Currency currency0, Currency currency1) = decoder.decodeCurrencyPair(params); @@ -160,6 +241,18 @@ contract CalldataDecoderTest is Test { assertEq(Currency.unwrap(currency1), Currency.unwrap(_currency1)); } + function test_decodeCurrencyPair_outOutBounds() public { + Currency currency = Currency.wrap(address(0x12341234)); + Currency currency2 = Currency.wrap(address(0x56785678)); + + bytes memory params = abi.encode(currency, currency2); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrencyPair(invalidParams); + } + function test_fuzz_decodeCurrencyPairAndAddress(Currency _currency0, Currency _currency1, address __address) public view @@ -172,6 +265,19 @@ contract CalldataDecoderTest is Test { assertEq(_address, __address); } + function test_decodeCurrencyPairAndAddress_outOutBounds() public { + Currency currency = Currency.wrap(address(0x12341234)); + Currency currency2 = Currency.wrap(address(0x56785678)); + address addy = address(0x23453456); + + bytes memory params = abi.encode(currency, currency2, addy); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrencyPairAndAddress(invalidParams); + } + function test_fuzz_decodeCurrencyAddressAndUint256(Currency _currency, address _addr, uint256 _amount) public view @@ -184,6 +290,19 @@ contract CalldataDecoderTest is Test { assertEq(amount, _amount); } + function test_decodeCurrencyAddressAndUint256_outOutBounds() public { + uint256 value = 12345678; + Currency currency = Currency.wrap(address(0x12341234)); + address addy = address(0x67896789); + + bytes memory params = abi.encode(currency, addy, value); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrencyAddressAndUint256(invalidParams); + } + function test_fuzz_decodeCurrencyAndUint256(Currency _currency, uint256 _amount) public view { bytes memory params = abi.encode(_currency, _amount); (Currency currency, uint256 amount) = decoder.decodeCurrencyAndUint256(params); @@ -192,6 +311,74 @@ contract CalldataDecoderTest is Test { assertEq(amount, _amount); } + function test_decodeCurrencyAndUint256_outOutBounds() public { + uint256 value = 12345678; + Currency currency = Currency.wrap(address(0x12341234)); + + bytes memory params = abi.encode(currency, value); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrencyAndUint256(invalidParams); + } + + function test_fuzz_decodeIncreaseLiquidityFromAmountsParams( + uint256 _tokenId, + uint128 _amount0Max, + uint128 _amount1Max, + bytes calldata _hookData + ) public view { + bytes memory params = abi.encode(_tokenId, _amount0Max, _amount1Max, _hookData); + + (uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes memory hookData) = + decoder.decodeIncreaseLiquidityFromDeltasParams(params); + assertEq(_tokenId, tokenId); + assertEq(_amount0Max, amount0Max); + assertEq(_amount1Max, amount1Max); + assertEq(_hookData, hookData); + } + + function test_fuzz_decodeUint256(uint256 _amount) public view { + bytes memory params = abi.encode(_amount); + uint256 amount = decoder.decodeUint256(params); + + assertEq(amount, _amount); + } + + function test_decodeUint256_outOutBounds() public { + uint256 value = 12345678; + + bytes memory params = abi.encode(value); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeUint256(invalidParams); + } + + function test_fuzz_decodeCurrencyUint256AndBool(Currency _currency, uint256 _amount, bool _boolean) public view { + bytes memory params = abi.encode(_currency, _amount, _boolean); + (Currency currency, uint256 amount, bool boolean) = decoder.decodeCurrencyUint256AndBool(params); + + assertEq(Currency.unwrap(currency), Currency.unwrap(_currency)); + assertEq(amount, _amount); + assertEq(boolean, _boolean); + } + + function test_decodeCurrencyUint256AndBool_outOutBounds() public { + uint256 value = 12345678; + Currency currency = Currency.wrap(address(0x12341234)); + bool boolean = true; + + bytes memory params = abi.encode(currency, value, boolean); + bytes memory invalidParams = _removeFinalByte(params); + assertEq(invalidParams.length, params.length - 1); + + vm.expectRevert(CalldataDecoder.SliceOutOfBounds.selector); + decoder.decodeCurrencyUint256AndBool(invalidParams); + } + function _assertEq(PathKey[] memory path1, PathKey[] memory path2) internal pure { assertEq(path1.length, path2.length); for (uint256 i = 0; i < path1.length; i++) { @@ -203,12 +390,6 @@ contract CalldataDecoderTest is Test { } } - function _assertEq(PositionConfig memory config1, PositionConfig memory config2) internal pure { - _assertEq(config1.poolKey, config2.poolKey); - assertEq(config1.tickLower, config2.tickLower); - assertEq(config1.tickUpper, config2.tickUpper); - } - function _assertEq(PoolKey memory key1, PoolKey memory key2) internal pure { assertEq(Currency.unwrap(key1.currency0), Currency.unwrap(key2.currency0)); assertEq(Currency.unwrap(key1.currency1), Currency.unwrap(key2.currency1)); @@ -216,4 +397,12 @@ contract CalldataDecoderTest is Test { assertEq(key1.tickSpacing, key2.tickSpacing); assertEq(address(key1.hooks), address(key2.hooks)); } + + function _removeFinalByte(bytes memory params) internal pure returns (bytes memory result) { + result = new bytes(params.length - 1); + // dont copy the final byte + for (uint256 i = 0; i < params.length - 2; i++) { + result[i] = params[i]; + } + } } diff --git a/test/libraries/Descriptor.t.sol b/test/libraries/Descriptor.t.sol new file mode 100644 index 000000000..2f3d5fd80 --- /dev/null +++ b/test/libraries/Descriptor.t.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Descriptor} from "../../src/libraries/Descriptor.sol"; +import {Test} from "forge-std/Test.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + +contract DescriptorTest is Test { + function test_feeToPercentString_succeeds() public pure { + assertEq(Descriptor.feeToPercentString(0x800000), "Dynamic"); + assertEq(Descriptor.feeToPercentString(0), "0%"); + assertEq(Descriptor.feeToPercentString(1), "0.0001%"); + assertEq(Descriptor.feeToPercentString(30), "0.003%"); + assertEq(Descriptor.feeToPercentString(33), "0.0033%"); + assertEq(Descriptor.feeToPercentString(500), "0.05%"); + assertEq(Descriptor.feeToPercentString(2500), "0.25%"); + assertEq(Descriptor.feeToPercentString(3000), "0.3%"); + assertEq(Descriptor.feeToPercentString(10000), "1%"); + assertEq(Descriptor.feeToPercentString(17000), "1.7%"); + assertEq(Descriptor.feeToPercentString(100000), "10%"); + assertEq(Descriptor.feeToPercentString(150000), "15%"); + assertEq(Descriptor.feeToPercentString(102000), "10.2%"); + assertEq(Descriptor.feeToPercentString(1000000), "100%"); + assertEq(Descriptor.feeToPercentString(1005000), "100.5%"); + assertEq(Descriptor.feeToPercentString(10000000), "1000%"); + assertEq(Descriptor.feeToPercentString(12300000), "1230%"); + } + + function test_addressToString_succeeds() public pure { + assertEq(Descriptor.addressToString(address(0)), "0x0000000000000000000000000000000000000000"); + assertEq(Descriptor.addressToString(address(1)), "0x0000000000000000000000000000000000000001"); + assertEq( + Descriptor.addressToString(0x1111111111111111111111111111111111111111), + "0x1111111111111111111111111111111111111111" + ); + assertEq( + Descriptor.addressToString(0x1234AbcdEf1234abcDef1234aBCdEF1234ABCDEF), + "0x1234abcdef1234abcdef1234abcdef1234abcdef" + ); + } + + function test_escapeSpecialCharacters_succeeds() public pure { + assertEq(Descriptor.escapeSpecialCharacters(""), ""); + assertEq(Descriptor.escapeSpecialCharacters("a"), "a"); + assertEq(Descriptor.escapeSpecialCharacters("abc"), "abc"); + assertEq(Descriptor.escapeSpecialCharacters("a\"bc"), "a\\\"bc"); + assertEq(Descriptor.escapeSpecialCharacters("a\"b\"c"), "a\\\"b\\\"c"); + assertEq(Descriptor.escapeSpecialCharacters("a\"b\"c\""), "a\\\"b\\\"c\\\""); + assertEq(Descriptor.escapeSpecialCharacters("\"a\"b\"c\""), "\\\"a\\\"b\\\"c\\\""); + assertEq(Descriptor.escapeSpecialCharacters("\"a\"b\"c\"\""), "\\\"a\\\"b\\\"c\\\"\\\""); + + assertEq(Descriptor.escapeSpecialCharacters("a\rbc"), "a\\\rbc"); + assertEq(Descriptor.escapeSpecialCharacters("a\nbc"), "a\\\nbc"); + assertEq(Descriptor.escapeSpecialCharacters("a\tbc"), "a\\\tbc"); + assertEq(Descriptor.escapeSpecialCharacters("a\u000cbc"), "a\\\u000cbc"); + } + + function test_tickToDecimalString_withTickSpacing10() public pure { + int24 tickSpacing = 10; + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + assertEq(Descriptor.tickToDecimalString(minTick, tickSpacing, 18, 18, false), "MIN"); + assertEq(Descriptor.tickToDecimalString(maxTick, tickSpacing, 18, 18, false), "MAX"); + assertEq(Descriptor.tickToDecimalString(1, tickSpacing, 18, 18, false), "1.0001"); + int24 otherMinTick = (TickMath.MIN_TICK / 60) * 60; + assertEq( + Descriptor.tickToDecimalString(otherMinTick, tickSpacing, 18, 18, false), + "0.0000000000000000000000000000000000000029387" + ); + } + + function test_tickToDecimalString_withTickSpacing60() public pure { + int24 tickSpacing = 60; + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + assertEq(Descriptor.tickToDecimalString(minTick, tickSpacing, 18, 18, false), "MIN"); + assertEq(Descriptor.tickToDecimalString(maxTick, tickSpacing, 18, 18, false), "MAX"); + assertEq(Descriptor.tickToDecimalString(-1, tickSpacing, 18, 18, false), "0.99990"); + int24 otherMinTick = (TickMath.MIN_TICK / 200) * 200; + assertEq( + Descriptor.tickToDecimalString(otherMinTick, tickSpacing, 18, 18, false), + "0.0000000000000000000000000000000000000029387" + ); + } + + function test_tickToDecimalString_withTickSpacing200() public pure { + int24 tickSpacing = 200; + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + assertEq(Descriptor.tickToDecimalString(minTick, tickSpacing, 18, 18, false), "MIN"); + assertEq(Descriptor.tickToDecimalString(maxTick, tickSpacing, 18, 18, false), "MAX"); + assertEq(Descriptor.tickToDecimalString(0, tickSpacing, 18, 18, false), "1.0000"); + int24 otherMinTick = (TickMath.MIN_TICK / 60) * 60; + assertEq( + Descriptor.tickToDecimalString(otherMinTick, tickSpacing, 18, 18, false), + "0.0000000000000000000000000000000000000029387" + ); + } + + function test_tickToDecimalString_ratio_returnsInverseMediumNumbers() public pure { + int24 tickSpacing = 200; + assertEq(Descriptor.tickToDecimalString(10, tickSpacing, 18, 18, false), "1.0010"); + assertEq(Descriptor.tickToDecimalString(10, tickSpacing, 18, 18, true), "0.99900"); + } + + function test_tickToDecimalString_ratio_returnsInverseLargeNumbers() public pure { + int24 tickSpacing = 200; + assertEq(Descriptor.tickToDecimalString(487272, tickSpacing, 18, 18, false), "1448400000000000000000"); + assertEq(Descriptor.tickToDecimalString(487272, tickSpacing, 18, 18, true), "0.00000000000000000000069041"); + } + + function test_tickToDecimalString_ratio_returnsInverseSmallNumbers() public pure { + int24 tickSpacing = 200; + assertEq(Descriptor.tickToDecimalString(-387272, tickSpacing, 18, 18, false), "0.000000000000000015200"); + assertEq(Descriptor.tickToDecimalString(-387272, tickSpacing, 18, 18, true), "65791000000000000"); + } + + function test_tickToDecimalString_differentDecimals() public pure { + int24 tickSpacing = 200; + assertEq(Descriptor.tickToDecimalString(1000, tickSpacing, 18, 18, true), "0.90484"); + assertEq(Descriptor.tickToDecimalString(1000, tickSpacing, 18, 10, true), "90484000"); + assertEq(Descriptor.tickToDecimalString(1000, tickSpacing, 10, 18, true), "0.0000000090484"); + } + + function test_fixedPointToDecimalString() public pure { + assertEq( + Descriptor.fixedPointToDecimalString(1457647476727839560029885420909913413788472405159, 18, 18), + "338490000000000000000000000000000000000" + ); + assertEq( + Descriptor.fixedPointToDecimalString(4025149349925610116743993887520032712, 18, 18), "2581100000000000" + ); + assertEq(Descriptor.fixedPointToDecimalString(3329657202331788924044422905302854, 18, 18), "1766200000"); + assertEq(Descriptor.fixedPointToDecimalString(16241966553695418990605751641065, 18, 18), "42026"); + assertEq(Descriptor.fixedPointToDecimalString(2754475062069337566441091812235, 18, 18), "1208.7"); + assertEq(Descriptor.fixedPointToDecimalString(871041495427277622831427623669, 18, 18), "120.87"); + assertEq(Descriptor.fixedPointToDecimalString(275447506206933756644109181223, 18, 18), "12.087"); + + assertEq(Descriptor.fixedPointToDecimalString(88028870788706913884596530851, 18, 18), "1.2345"); + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 18, 18), "1.0000"); + assertEq(Descriptor.fixedPointToDecimalString(27837173154497669652482281089, 18, 18), "0.12345"); + assertEq(Descriptor.fixedPointToDecimalString(1559426812423768092342, 18, 18), "0.00000000000000038741"); + assertEq(Descriptor.fixedPointToDecimalString(74532606916587, 18, 18), "0.00000000000000000000000000000088498"); + assertEq( + Descriptor.fixedPointToDecimalString(4947797163, 18, 18), "0.0000000000000000000000000000000000000029387" + ); + + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 18, 16), "100.00"); + assertEq(Descriptor.fixedPointToDecimalString(250541448375047931186413801569, 18, 17), "100.00"); + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 24, 5), "1.0000"); + + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 10, 18), "0.000000010000"); + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 7, 18), "0.000000000010000"); + } +} diff --git a/test/libraries/PositionConfig.t.sol b/test/libraries/PositionConfig.t.sol deleted file mode 100644 index cfec1a549..000000000 --- a/test/libraries/PositionConfig.t.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "forge-std/Test.sol"; - -import {PositionConfig, PositionConfigLibrary} from "../../src/libraries/PositionConfig.sol"; - -contract PositionConfigTest is Test { - using PositionConfigLibrary for *; - - mapping(uint256 => bytes32) internal testConfigs; - - bytes32 public constant UPPER_BIT_SET = 0x8000000000000000000000000000000000000000000000000000000000000000; - - function test_fuzz_toId(PositionConfig calldata config) public pure { - bytes32 expectedId = _calculateExpectedId(config); - assertEq(expectedId, config.toId()); - } - - function test_fuzz_setConfigId(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); - - bytes32 expectedConfigId = _calculateExpectedId(config); - - bytes32 actualConfigId = testConfigs[tokenId]; - assertEq(expectedConfigId, actualConfigId); - } - - function test_fuzz_getConfigId(uint256 tokenId, PositionConfig calldata config) public { - bytes32 expectedId = _calculateExpectedId(config); - // set - testConfigs[tokenId] = expectedId; - - assertEq(expectedId, testConfigs.getConfigId(tokenId)); - } - - function test_fuzz_setConfigId_getConfigId(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); - - bytes32 expectedId = _calculateExpectedId(config); - - assertEq(testConfigs.getConfigId(tokenId), testConfigs[tokenId]); - assertEq(testConfigs.getConfigId(tokenId), expectedId); - } - - function test_fuzz_getConfigId_equal_afterSubscribe(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); - testConfigs.setSubscribe(tokenId); - - assertEq(testConfigs.getConfigId(tokenId), config.toId()); - } - - function test_fuzz_setSubscribe(uint256 tokenId) public { - testConfigs.setSubscribe(tokenId); - bytes32 upperBitSet = testConfigs[tokenId]; - - assertEq(upperBitSet, UPPER_BIT_SET); - } - - function test_fuzz_setConfigId_setSubscribe(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); - testConfigs.setSubscribe(tokenId); - - bytes32 expectedConfig = _calculateExpectedId(config) | UPPER_BIT_SET; - - bytes32 _config = testConfigs[tokenId]; - - assertEq(_config, expectedConfig); - } - - function test_fuzz_setUnsubscribe(uint256 tokenId) public { - testConfigs.setSubscribe(tokenId); - bytes32 _config = testConfigs[tokenId]; - assertEq(_config, UPPER_BIT_SET); - testConfigs.setUnsubscribe(tokenId); - _config = testConfigs[tokenId]; - assertEq(_config, 0); - } - - function test_hasSubscriber(uint256 tokenId) public { - testConfigs.setSubscribe(tokenId); - assert(testConfigs.hasSubscriber(tokenId)); - testConfigs.setUnsubscribe(tokenId); - assert(!testConfigs.hasSubscriber(tokenId)); - } - - function test_fuzz_setConfigId_setSubscribe_setUnsubscribe_getConfigId( - uint256 tokenId, - PositionConfig calldata config - ) public { - assertEq(testConfigs.getConfigId(tokenId), 0); - - testConfigs.setConfigId(tokenId, config); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); - - testConfigs.setSubscribe(tokenId); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); - assertEq(testConfigs.hasSubscriber(tokenId), true); - - testConfigs.setUnsubscribe(tokenId); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); - assertEq(testConfigs.hasSubscriber(tokenId), false); - } - - function _calculateExpectedId(PositionConfig calldata config) internal pure returns (bytes32 expectedId) { - expectedId = keccak256( - abi.encodePacked( - config.poolKey.currency0, - config.poolKey.currency1, - config.poolKey.fee, - config.poolKey.tickSpacing, - config.poolKey.hooks, - config.tickLower, - config.tickUpper - ) - ); - // truncate the upper bit - expectedId = expectedId >> 1; - } -} diff --git a/test/libraries/PositionInfoLibrary.t.sol b/test/libraries/PositionInfoLibrary.t.sol new file mode 100644 index 000000000..12af50151 --- /dev/null +++ b/test/libraries/PositionInfoLibrary.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PositionInfo, PositionInfoLibrary, PoolId} from "../../src/libraries/PositionInfoLibrary.sol"; + +contract PositionInfoLibraryTest is Test { + using PositionInfoLibrary for PositionInfo; + using PoolIdLibrary for PoolKey; + + function setUp() public {} + + function test_fuzz_initialize(PoolKey memory poolKey, int24 tickLower, int24 tickUpper) public pure { + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + + assertEq(info.poolId(), bytes25(PoolId.unwrap(poolKey.toId()))); + assertEq(info.tickLower(), tickLower); + assertEq(info.tickUpper(), tickUpper); + assertEq(info.hasSubscriber(), false); + } + + function test_fuzz_initialize_setSubscribed(PoolKey memory poolKey, int24 tickLower, int24 tickUpper) public pure { + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + assertEq(info.hasSubscriber(), false); + info = info.setSubscribe(); + assertEq(info.hasSubscriber(), true); + assertEq(info.tickLower(), tickLower); + assertEq(info.tickUpper(), tickUpper); + assertEq(info.poolId(), bytes25(PoolId.unwrap(poolKey.toId()))); + } + + function test_fuzz_initialize_setUnsubscribed(PoolKey memory poolKey, int24 tickLower, int24 tickUpper) + public + pure + { + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + assertEq(info.hasSubscriber(), false); + info = info.setSubscribe(); + assertEq(info.hasSubscriber(), true); + assertEq(info.tickLower(), tickLower); + assertEq(info.tickUpper(), tickUpper); + assertEq(info.poolId(), bytes25(PoolId.unwrap(poolKey.toId()))); + + info = info.setUnsubscribe(); + assertEq(info.hasSubscriber(), false); + assertEq(info.tickLower(), tickLower); + assertEq(info.tickUpper(), tickUpper); + assertEq(info.poolId(), bytes25(PoolId.unwrap(poolKey.toId()))); + } + + function test_fuzz_setSubscribe(PoolKey memory poolKey, int24 tickLower, int24 tickUpper) public pure { + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + assertEq(info.hasSubscriber(), false); + info = info.setSubscribe(); + assertEq(info.hasSubscriber(), true); + + // Calling set subscribe again does nothing. + info = info.setSubscribe(); + assertEq(info.hasSubscriber(), true); + } + + function test_fuzz_setUnsubscribe(PoolKey memory poolKey, int24 tickLower, int24 tickUpper) public pure { + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + assertEq(info.hasSubscriber(), false); + info = info.setSubscribe(); + assertEq(info.hasSubscriber(), true); + info = info.setUnsubscribe(); + assertEq(info.hasSubscriber(), false); + + // Calling set unsubscribe again does nothing. + info = info.setUnsubscribe(); + assertEq(info.hasSubscriber(), false); + } +} diff --git a/test/libraries/SVG.t.sol b/test/libraries/SVG.t.sol new file mode 100644 index 000000000..915557cbb --- /dev/null +++ b/test/libraries/SVG.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {SVG} from "../../src/libraries/SVG.sol"; +import {Test} from "forge-std/Test.sol"; + +contract DescriptorTest is Test { + function test_rangeLocation_succeeds() public pure { + (string memory x, string memory y) = SVG.rangeLocation(-887_272, -887_100); + assertEq(x, "8"); + assertEq(y, "7"); + (x, y) = SVG.rangeLocation(-100_000, -90_000); + assertEq(x, "8"); + assertEq(y, "10.5"); + (x, y) = SVG.rangeLocation(-50_000, -20_000); + assertEq(x, "8"); + assertEq(y, "14.25"); + (x, y) = SVG.rangeLocation(-10_000, -5_000); + assertEq(x, "10"); + assertEq(y, "18"); + (x, y) = SVG.rangeLocation(-5_000, -4_000); + assertEq(x, "11"); + assertEq(y, "21"); + (x, y) = SVG.rangeLocation(4_000, 5_000); + assertEq(x, "13"); + assertEq(y, "23"); + (x, y) = SVG.rangeLocation(10_000, 15_000); + assertEq(x, "15"); + assertEq(y, "25"); + (x, y) = SVG.rangeLocation(25_000, 50_000); + assertEq(x, "18"); + assertEq(y, "26"); + (x, y) = SVG.rangeLocation(100_000, 125_000); + assertEq(x, "21"); + assertEq(y, "27"); + (x, y) = SVG.rangeLocation(200_000, 100_000); + assertEq(x, "24"); + assertEq(y, "27"); + (x, y) = SVG.rangeLocation(887_272, 887_272); + assertEq(x, "24"); + assertEq(y, "27"); + } + + function test_isRare_succeeds() public pure { + bool result = SVG.isRare(1, 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB); + assertTrue(result); + result = SVG.isRare(2, 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB); + assertFalse(result); + } + + function test_substring_succeeds() public pure { + string memory result = SVG.substring("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 0, 5); + assertEq(result, "0xC02"); + result = SVG.substring("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 39, 42); + assertEq(result, "Cc2"); + } +} diff --git a/test/libraries/SafeCurrencyMetadata.t.sol b/test/libraries/SafeCurrencyMetadata.t.sol new file mode 100644 index 000000000..15f6e0f5d --- /dev/null +++ b/test/libraries/SafeCurrencyMetadata.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {SafeCurrencyMetadata} from "../../src/libraries/SafeCurrencyMetadata.sol"; + +contract SafeCurrencyMetadataTest is Test { + function test_truncateSymbol_succeeds() public pure { + // 12 characters + assertEq(SafeCurrencyMetadata.truncateSymbol("123456789012"), "123456789012"); + // 13 characters + assertEq(SafeCurrencyMetadata.truncateSymbol("1234567890123"), "123456789012"); + // 14 characters + assertEq(SafeCurrencyMetadata.truncateSymbol("12345678901234"), "123456789012"); + } +} diff --git a/test/libraries/VanityAddressLib.t.sol b/test/libraries/VanityAddressLib.t.sol new file mode 100644 index 000000000..f9ae5474a --- /dev/null +++ b/test/libraries/VanityAddressLib.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {VanityAddressLib} from "../../src/libraries/VanityAddressLib.sol"; + +contract VanityAddressLibTest is Test { + function test_fuzz_reasonableScoreNeverReverts(address test) public pure { + uint256 score = VanityAddressLib.score(address(test)); + assertGe(score, 0); + assertLe(score, 444); + } + + function test_scoreAllFours() public pure { + address addr = address(0x4444444444444444444444444444444444444444); + uint256 score = VanityAddressLib.score(addr); + uint256 expected = 100; // 40 + 40 + 20 = 100 + assertEq(score, expected); + } + + function test_scoreLaterFours() public pure { + address addr = address(0x1444444444444444444444444444444444444444); + uint256 score = VanityAddressLib.score(addr); + uint256 expected = 0; // no leading 4 + assertEq(score, expected); + } + + function test_scoreMixed_4() public pure { + address addr = address(0x0044001111111111111111111111111111114114); + // counts first null byte + // counts first leading 4s after that + // does not count future null bytes + // counts 4 nibbles after that + uint256 score = VanityAddressLib.score(addr); + uint256 expected = 24; // 10 * 2 + 2 + 2 = 24 + assertEq(score, expected); + } + + function test_scoreMixed_44() public pure { + address addr = address(0x0044001111111111111111111111111111114444); + // counts first null byte + // counts first leading 4s after that + // does not count future null bytes + // counts 4 nibbles after that + uint256 score = VanityAddressLib.score(addr); + uint256 expected = 46; // 10 * 2 + 6 + 20 = 46 + assertEq(score, expected); + } + + function test_scoreMixed_halfZeroHalf4() public pure { + address addr = address(0x0004111111111111111111111111111111111111); + // counts first null byte + // counts first leading 4s after that + uint256 score = VanityAddressLib.score(addr); + uint256 expected = 31; // 10 * 3 + 1 = 31 + assertEq(score, expected); + } + + function test_scores_succeed() public pure { + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000082)), 0); // 0 + assertEq(VanityAddressLib.score(address(0x0400000000000000000000000000000000000000)), 11); // 10 * 1 + 1 = 11 + assertEq(VanityAddressLib.score(address(0x0044000000000000000000000000000000004444)), 46); // 10 * 2 + 6 + 20 = 46 + assertEq(VanityAddressLib.score(address(0x4444000000000000000000000000000000004444)), 88); // 40 + 20 + 20 + 8 = 88 + assertEq(VanityAddressLib.score(address(0x0044440000000000000000000000000000000044)), 86); // 10 * 2 + 40 + 20 + 6 = 86 + assertEq(VanityAddressLib.score(address(0x0000444400000000000000000000000000004444)), 128); // 10 * 4 + 40 + 20 + 20 + 8 = 128 + assertEq(VanityAddressLib.score(address(0x0040444444444444444444444444444444444444)), 77); // 10 * 2 + 37 + 20 = 77 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000444)), 373); // 10 * 37 + 3 = 373 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000044444444)), 388); // 10 * 32 + 40 + 20 + 8 = 388 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000454444)), 365); // 10 * 34 + 20 + 5 = 365 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000044)), 382); // 10 * 38 + 2 = 382 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000000004)), 391); // 10 * 39 + 1 = 391 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000444444)), 406); // 10 * 34 + 40 + 20 + 6 = 406 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000044444)), 415); // 10 * 35 + 40 + 20 + 5 = 415 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000444455)), 404); // 10 * 34 + 40 + 20 + 4 = 404 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000044445)), 414); // 10 * 35 + 40 + 20 + 4 = 414 + assertEq(VanityAddressLib.score(address(0x0000000000000000000000000000000000004444)), 444); // 10 * 36 + 40 + 20 + 20 + 4 = 444 + } + + function test_betterThan() public pure { + address addr1 = address(0x0011111111111111111111111111111111111111); // 0 points + address addr2 = address(0x4000111111111111111111111111111111111111); // 1 points + address addr3 = address(0x0000411111111111111111111111111111111111); // 10 * 4 + 1 = 41 points + address addr4 = address(0x0000441111111111111111111111111111111111); // 10 * 4 + 2 = 42 points + address addr5 = address(0x0000440011111111111111111111111111111111); // 10 * 4 + 2 = 42 points + assertTrue(VanityAddressLib.betterThan(addr2, addr1)); // 1 > 0 + assertTrue(VanityAddressLib.betterThan(addr3, addr2)); // 41 > 1 + assertTrue(VanityAddressLib.betterThan(addr3, addr1)); // 41 > 0 + assertTrue(VanityAddressLib.betterThan(addr4, addr3)); // 42 > 41 + assertTrue(VanityAddressLib.betterThan(addr4, addr2)); // 42 > 1 + assertTrue(VanityAddressLib.betterThan(addr4, addr1)); // 42 > 0 + assertFalse(VanityAddressLib.betterThan(addr5, addr4)); // 42 == 42 + assertEq(VanityAddressLib.score(addr5), VanityAddressLib.score(addr4)); // 42 == 42 + assertTrue(VanityAddressLib.betterThan(addr5, addr3)); // 42 > 41 + assertTrue(VanityAddressLib.betterThan(addr5, addr2)); // 42 > 1 + assertTrue(VanityAddressLib.betterThan(addr5, addr1)); // 42 > 0 + + address addr6 = address(0x0000000000000000000000000000000000004444); + address addr7 = address(0x0000000000000000000000000000000000000082); + assertTrue(VanityAddressLib.betterThan(addr6, addr7)); // 10 * 36 + 40 + 20 + 20 + 4 = 444 > 0 + } +} diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index d619ff633..d9c99cbbe 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -1,9 +1,10 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol"; /// @notice A subscriber contract that returns values from the subscriber entrypoints contract MockReturnDataSubscriber is ISubscriber { @@ -12,7 +13,6 @@ contract MockReturnDataSubscriber is ISubscriber { uint256 public notifySubscribeCount; uint256 public notifyUnsubscribeCount; uint256 public notifyModifyLiquidityCount; - uint256 public notifyTransferCount; error NotAuthorizedNotifer(address sender); @@ -29,11 +29,11 @@ contract MockReturnDataSubscriber is ISubscriber { _; } - function notifySubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + function notifySubscribe(uint256, bytes memory) external onlyByPosm { notifySubscribeCount++; } - function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + function notifyUnsubscribe(uint256) external onlyByPosm { notifyUnsubscribeCount++; uint256 _memPtr = memPtr; assembly { @@ -44,15 +44,61 @@ contract MockReturnDataSubscriber is ISubscriber { } } - function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { + function notifyModifyLiquidity(uint256, int256, BalanceDelta) external onlyByPosm { notifyModifyLiquidityCount++; } - function notifyTransfer(uint256, address, address) external onlyByPosm { - notifyTransferCount++; + function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) + external + { + return; } function setReturnDataSize(uint256 _value) external { memPtr = _value; } } + +/// @notice A subscriber contract that returns values from the subscriber entrypoints +contract MockRevertSubscriber is ISubscriber { + PositionManager posm; + + error NotAuthorizedNotifer(address sender); + + error TestRevert(string); + + constructor(PositionManager _posm) { + posm = _posm; + } + + bool shouldRevert; + + modifier onlyByPosm() { + if (msg.sender != address(posm)) revert NotAuthorizedNotifer(msg.sender); + _; + } + + function notifySubscribe(uint256, bytes memory) external view onlyByPosm { + if (shouldRevert) { + revert TestRevert("notifySubscribe"); + } + } + + function notifyUnsubscribe(uint256) external view onlyByPosm { + revert TestRevert("notifyUnsubscribe"); + } + + function notifyModifyLiquidity(uint256, int256, BalanceDelta) external view onlyByPosm { + revert TestRevert("notifyModifyLiquidity"); + } + + function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) + external + { + return; + } + + function setRevert(bool _shouldRevert) external { + shouldRevert = _shouldRevert; + } +} diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index b7630297e..53b92a24a 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index 695a526b2..151b3c247 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -1,26 +1,49 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {CalldataDecoder} from "../../src/libraries/CalldataDecoder.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; // we need to use a mock contract to make the calls happen in calldata not memory contract MockCalldataDecoder { using CalldataDecoder for bytes; + // This is used to avoid compiling with via-ir. + struct MintParams { + PoolKey poolKey; + int24 tickLower; + int24 tickUpper; + uint256 liquidity; + uint128 amount0Max; + uint128 amount1Max; + address owner; + bytes hookData; + } + + struct MintFromDeltasParams { + PoolKey poolKey; + int24 tickLower; + int24 tickUpper; + uint128 amount0Max; + uint128 amount1Max; + address owner; + bytes hookData; + } + + function decodeActionsRouterParams(bytes calldata params) + external + pure + returns (bytes calldata actions, bytes[] calldata actionParams) + { + return params.decodeActionsRouterParams(); + } + function decodeModifyLiquidityParams(bytes calldata params) external pure - returns ( - uint256 tokenId, - PositionConfig calldata config, - uint256 liquidity, - uint128 amount0, - uint128 amount1, - bytes calldata hookData - ) + returns (uint256 tokenId, uint256 liquidity, uint128 amount0, uint128 amount1, bytes calldata hookData) { return params.decodeModifyLiquidityParams(); } @@ -28,13 +51,7 @@ contract MockCalldataDecoder { function decodeBurnParams(bytes calldata params) external pure - returns ( - uint256 tokenId, - PositionConfig calldata config, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) + returns (uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) { return params.decodeBurnParams(); } @@ -71,19 +88,27 @@ contract MockCalldataDecoder { return params.decodeSwapExactOutSingleParams(); } - function decodeMintParams(bytes calldata params) - external - pure - returns ( - PositionConfig calldata config, + function decodeMintParams(bytes calldata params) external pure returns (MintParams memory mintParams) { + ( + PoolKey memory poolKey, + int24 tickLower, + int24 tickUpper, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, address owner, - bytes calldata hookData - ) - { - return params.decodeMintParams(); + bytes memory hookData + ) = params.decodeMintParams(); + return MintParams({ + poolKey: poolKey, + tickLower: tickLower, + tickUpper: tickUpper, + liquidity: liquidity, + amount0Max: amount0Max, + amount1Max: amount1Max, + owner: owner, + hookData: hookData + }); } function decodeCurrencyAndAddress(bytes calldata params) @@ -121,4 +146,45 @@ contract MockCalldataDecoder { { return params.decodeCurrencyAddressAndUint256(); } + + function decodeIncreaseLiquidityFromDeltasParams(bytes calldata params) + external + pure + returns (uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) + { + return params.decodeIncreaseLiquidityFromDeltasParams(); + } + + function decodeMintFromDeltasParams(bytes calldata params) + external + pure + returns (MintFromDeltasParams memory mintParams) + { + ( + PoolKey memory poolKey, + int24 tickLower, + int24 tickUpper, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes memory hookData + ) = params.decodeMintFromDeltasParams(); + return MintFromDeltasParams({ + poolKey: poolKey, + tickLower: tickLower, + tickUpper: tickUpper, + amount0Max: amount0Max, + amount1Max: amount1Max, + owner: owner, + hookData: hookData + }); + } + + function decodeUint256(bytes calldata params) external pure returns (uint256) { + return params.decodeUint256(); + } + + function decodeCurrencyUint256AndBool(bytes calldata params) external pure returns (Currency, uint256, bool) { + return params.decodeCurrencyUint256AndBool(); + } } diff --git a/test/mocks/MockDeltaResolver.sol b/test/mocks/MockDeltaResolver.sol index 4a3941e4a..6de67fbb7 100644 --- a/test/mocks/MockDeltaResolver.sol +++ b/test/mocks/MockDeltaResolver.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; @@ -22,7 +22,7 @@ contract MockDeltaResolver is Test, DeltaResolver, IUnlockCallback { function unlockCallback(bytes calldata data) external returns (bytes memory) { (Currency currency, address caller, uint256 amount) = abi.decode(data, (Currency, address, uint256)); - address recipient = (currency.isNative()) ? address(this) : caller; + address recipient = (currency.isAddressZero()) ? address(this) : caller; uint256 balanceBefore = currency.balanceOf(recipient); _take(currency, recipient, amount); diff --git a/test/mocks/MockERC721Permit.sol b/test/mocks/MockERC721Permit.sol index b76bc547d..0cd2a7789 100644 --- a/test/mocks/MockERC721Permit.sol +++ b/test/mocks/MockERC721Permit.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {ERC721Permit_v4} from "../../src/base/ERC721Permit_v4.sol"; @@ -8,12 +8,12 @@ contract MockERC721Permit is ERC721Permit_v4 { constructor(string memory name, string memory symbol) ERC721Permit_v4(name, symbol) {} - function tokenURI(uint256) public pure override returns (string memory) { - return ""; - } - function mint() external returns (uint256 tokenId) { tokenId = ++lastTokenId; _mint(msg.sender, tokenId); } + + function tokenURI(uint256) public pure override returns (string memory) { + return "mock"; + } } diff --git a/test/mocks/MockFeeOnTransfer.sol b/test/mocks/MockFeeOnTransfer.sol new file mode 100644 index 000000000..7488a5d87 --- /dev/null +++ b/test/mocks/MockFeeOnTransfer.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; + +contract MockFOT is MockERC20 { + using BipsLibrary for uint256; + + IPositionManager immutable posm; + + uint256 public bips; + + constructor(IPositionManager _posm) MockERC20("FOT Token", "FOT", 18) { + posm = _posm; + } + + function setFee(uint256 amountInBips) public { + bips = amountInBips; + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + uint256 allowed = allowance[from][msg.sender]; + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // bips% fee on the recipient + uint256 amountAfterFee = amount - amount.calculatePortion(bips); + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amountAfterFee; + } + + emit Transfer(from, to, amount); + + return true; + } +} diff --git a/test/mocks/MockMulticall.sol b/test/mocks/MockMulticall.sol index 38ddaa09e..1bae4d1f0 100644 --- a/test/mocks/MockMulticall.sol +++ b/test/mocks/MockMulticall.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "../../src/base/Multicall_v4.sol"; diff --git a/test/mocks/MockReenterHook.sol b/test/mocks/MockReenterHook.sol new file mode 100644 index 000000000..e6d6f2db5 --- /dev/null +++ b/test/mocks/MockReenterHook.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {BaseTestHooks} from "@uniswap/v4-core/src/test/BaseTestHooks.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +contract MockReenterHook is BaseTestHooks { + PositionManager posm; + + function beforeAddLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata functionSelector + ) external override returns (bytes4) { + if (functionSelector.length == 0) { + return this.beforeAddLiquidity.selector; + } + (bytes4 selector, address owner, uint256 tokenId) = abi.decode(functionSelector, (bytes4, address, uint256)); + + if (selector == posm.transferFrom.selector) { + posm.transferFrom(owner, address(this), tokenId); + } else if (selector == posm.subscribe.selector) { + posm.subscribe(tokenId, address(this), ""); + } else if (selector == posm.unsubscribe.selector) { + posm.unsubscribe(tokenId); + } + return this.beforeAddLiquidity.selector; + } + + function setPosm(PositionManager _posm) external { + posm = _posm; + } +} diff --git a/test/mocks/MockSafeCallback.sol b/test/mocks/MockSafeCallback.sol index 232fbe3c5..4739a1461 100644 --- a/test/mocks/MockSafeCallback.sol +++ b/test/mocks/MockSafeCallback.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol index 1e317ad19..64a167abb 100644 --- a/test/mocks/MockSubscriber.sol +++ b/test/mocks/MockSubscriber.sol @@ -1,9 +1,10 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol"; /// @notice A subscriber contract that ingests updates from the v4 position manager contract MockSubscriber is ISubscriber { @@ -12,10 +13,11 @@ contract MockSubscriber is ISubscriber { uint256 public notifySubscribeCount; uint256 public notifyUnsubscribeCount; uint256 public notifyModifyLiquidityCount; - uint256 public notifyTransferCount; + uint256 public notifyBurnCount; + int256 public liquidityChange; + BalanceDelta public feesAccrued; bytes public subscribeData; - bytes public unsubscribeData; error NotAuthorizedNotifer(address sender); @@ -30,21 +32,25 @@ contract MockSubscriber is ISubscriber { _; } - function notifySubscribe(uint256, PositionConfig memory, bytes memory data) external onlyByPosm { + function notifySubscribe(uint256, bytes memory data) external onlyByPosm { notifySubscribeCount++; subscribeData = data; } - function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory data) external onlyByPosm { + function notifyUnsubscribe(uint256) external onlyByPosm { notifyUnsubscribeCount++; - unsubscribeData = data; } - function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { + function notifyModifyLiquidity(uint256, int256 _liquidityChange, BalanceDelta _feesAccrued) external onlyByPosm { notifyModifyLiquidityCount++; + liquidityChange = _liquidityChange; + feesAccrued = _feesAccrued; } - function notifyTransfer(uint256, address, address) external onlyByPosm { - notifyTransferCount++; + function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) + external + onlyByPosm + { + notifyBurnCount++; } } diff --git a/test/mocks/MockUnorderedNonce.sol b/test/mocks/MockUnorderedNonce.sol index 8f3cfc57a..338b8f2d2 100644 --- a/test/mocks/MockUnorderedNonce.sol +++ b/test/mocks/MockUnorderedNonce.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; diff --git a/test/mocks/ReentrantToken.sol b/test/mocks/ReentrantToken.sol index 63cc71ee3..522d76fba 100644 --- a/test/mocks/ReentrantToken.sol +++ b/test/mocks/ReentrantToken.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index 5417c71a1..dd99d86b1 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -19,7 +19,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {PositionManager} from "../../src/PositionManager.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {Actions} from "../../src/libraries/Actions.sol"; @@ -47,7 +47,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // This is needed to receive return deltas from modifyLiquidity calls. deployPosmHookSavesDelta(); - (key, poolId) = initPool(currency0, currency1, IHooks(address(hook)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(address(hook)), 3000, SQRT_PRICE_1_1); // Requires currency0 and currency1 to be set in base Deployers contract. deployAndApprovePosm(manager); @@ -72,7 +72,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, initialLiquidity + liquidityToAdd); } @@ -92,17 +92,17 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2); } @@ -122,17 +122,17 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2); } @@ -149,7 +149,9 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, initialLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -159,13 +161,13 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { ); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, initialLiquidity + liquidityToAdd); } @@ -197,14 +199,14 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.BURN_POSITION, - abi.encode( - tokenId, config, uint128(-delta.amount0()) - 1 wei, uint128(-delta.amount1()) - 1 wei, ZERO_BYTES - ) + abi.encode(tokenId, uint128(-delta.amount0()) - 1 wei, uint128(-delta.amount1()) - 1 wei, ZERO_BYTES) ); planner.add( Actions.MINT_POSITION, abi.encode( - newConfig, + newConfig.poolKey, + newConfig.tickLower, + newConfig.tickUpper, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -234,7 +236,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { { // old position has no liquidity - uint128 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint128 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); // new token was minted @@ -243,7 +245,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // new token has expected liquidity - liquidity = lpm.getPositionLiquidity(newTokenId, newConfig); + liquidity = lpm.getPositionLiquidity(newTokenId); assertEq(liquidity, newLiquidity); } } diff --git a/test/position-managers/FeeCollection.t.sol b/test/position-managers/FeeCollection.t.sol index 54ad44ad3..c25085365 100644 --- a/test/position-managers/FeeCollection.t.sol +++ b/test/position-managers/FeeCollection.t.sol @@ -14,7 +14,7 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {PositionManager} from "../../src/PositionManager.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; @@ -40,7 +40,7 @@ contract FeeCollectionTest is Test, PosmTestSetup, LiquidityFuzzers { // This is needed to receive return deltas from modifyLiquidity calls. deployPosmHookSavesDelta(); - (key, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1); FEE_WAD = uint256(key.fee).mulDivDown(FixedPointMathLib.WAD, 1_000_000); // Requires currency0 and currency1 to be set in base Deployers contract. diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index f1a65efd9..a0264d2b1 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -21,8 +21,8 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; -import {SlippageCheckLibrary} from "../../src/libraries/SlippageCheck.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; +import {SlippageCheck} from "../../src/libraries/SlippageCheck.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {Planner, Plan} from "../shared/Planner.sol"; @@ -57,7 +57,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { // This is needed to receive return deltas from modifyLiquidity calls. deployPosmHookSavesDelta(); - (key, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1); FEE_WAD = uint256(key.fee).mulDivDown(FixedPointMathLib.WAD, 1_000_000); // Requires currency0 and currency1 to be set in base Deployers contract. @@ -75,6 +75,70 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); } + /// @notice Increase liquidity by less than the amount of liquidity the position has earned, requiring a take + function test_increaseLiquidity_withCollection_takePair() public { + // Alice and Bob provide liquidity on the range + // Alice uses her exact fees to increase liquidity (compounding) + + uint256 liquidityAlice = 3_000e18; + uint256 liquidityBob = 1_000e18; + + // alice provides liquidity + vm.startPrank(alice); + uint256 tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // bob provides liquidity + vm.startPrank(bob); + mint(config, liquidityBob, bob, ZERO_BYTES); + vm.stopPrank(); + + // donate to create fees + uint256 amountDonate = 0.1e18; + donateRouter.donate(key, amountDonate, amountDonate, ZERO_BYTES); + + // alice uses her half her fees to increase liquidity + // Slight error in this calculation vs. actual fees.. TODO: Fix this. + BalanceDelta feesOwedAlice = IPositionManager(lpm).getFeesOwed(manager, config, tokenIdAlice); + // Note: You can alternatively calculate Alice's fees owed from the swap amount, fee on the pool, and total liquidity in that range. + // swapAmount.mulWadDown(FEE_WAD).mulDivDown(liquidityAlice, liquidityAlice + liquidityBob); + + (uint160 sqrtPriceX96,,,) = StateLibrary.getSlot0(manager, config.poolKey.toId()); + uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + uint256(int256(feesOwedAlice.amount0() / 2)), + uint256(int256(feesOwedAlice.amount1() / 2)) + ); + + uint256 balance0BeforeAlice = currency0.balanceOf(alice); + uint256 balance1BeforeAlice = currency1.balanceOf(alice); + + // Set the slippage amounts to be exactly half the fees that alice is reinvesting. + + Plan memory planner = Planner.init(); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode( + tokenIdAlice, liquidityDelta, feesOwedAlice.amount0() / 2, feesOwedAlice.amount1() / 2, ZERO_BYTES + ) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(alice)); + vm.startPrank(alice); + lpm.modifyLiquidities(calls, _deadline); + vm.stopPrank(); + + // alices current balance is the balanceBefore plus half of her fees owed + assertApproxEqAbs( + currency0.balanceOf(alice), balance0BeforeAlice + uint256(int256(feesOwedAlice.amount0() / 2)), tolerance + ); + assertApproxEqAbs( + currency1.balanceOf(alice), balance1BeforeAlice + uint256(int256(feesOwedAlice.amount1() / 2)), tolerance + ); + } + /// @notice Increase liquidity with exact fees, taking dust function test_increaseLiquidity_withExactFees_take() public { // Alice and Bob provide liquidity on the range @@ -119,7 +183,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( - Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, 0 wei, 0 wei, ZERO_BYTES) + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenIdAlice, liquidityDelta, feesOwedAlice.amount0(), feesOwedAlice.amount1(), ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.startPrank(alice); @@ -176,7 +241,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, 18 wei)); // alice is willing to forfeit 18 wei planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, 18 wei)); @@ -279,7 +344,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, 1 wei)); // alice is willing to forfeit 1 wei planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, 1 wei)); @@ -294,30 +359,6 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { assertEq(currency1.balanceOf(alice), balance1BeforeAlice); } - function test_increaseLiquidity_withUnapprovedCaller() public { - // Alice provides liquidity - // Bob increases Alice's liquidity without being approved - uint256 liquidityAlice = 3_000e18; - - // alice provides liquidity - vm.startPrank(alice); - uint256 tokenIdAlice = lpm.nextTokenId(); - mint(config, liquidityAlice, alice, ZERO_BYTES); - vm.stopPrank(); - - uint128 oldLiquidity = lpm.getPositionLiquidity(tokenIdAlice, config); - - // bob can increase liquidity for alice even though he is not the owner / not approved - vm.startPrank(bob); - increaseLiquidity(tokenIdAlice, config, 100e18, ZERO_BYTES); - vm.stopPrank(); - - uint128 newLiquidity = lpm.getPositionLiquidity(tokenIdAlice, config); - - // assert liqudity increased by the correct amount - assertEq(newLiquidity, oldLiquidity + uint128(100e18)); - } - function test_increaseLiquidity_sameRange_withExcessFees() public { // Alice and Bob provide liquidity on the same range // Alice uses half her fees to increase liquidity. The other half are collected to her wallet. @@ -492,9 +533,16 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, ActionConstants.MSG_SENDER, ZERO_BYTES); + uint128 newLiquidity = 100e18; + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + newLiquidity + ); // revert since amount0Max is too low - bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, 1 wei, type(uint128).max, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); + bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, 1 wei, type(uint128).max, ZERO_BYTES); + vm.expectRevert(abi.encodeWithSelector(SlippageCheck.MaximumAmountExceeded.selector, 1 wei, amount0 + 1)); lpm.modifyLiquidities(calls, _deadline); } @@ -503,9 +551,16 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, ActionConstants.MSG_SENDER, ZERO_BYTES); + uint128 newLiquidity = 100e18; + (, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + newLiquidity + ); // revert since amount1Max is too low - bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, type(uint128).max, 1 wei, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); + bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, type(uint128).max, 1 wei, ZERO_BYTES); + vm.expectRevert(abi.encodeWithSelector(SlippageCheck.MaximumAmountExceeded.selector, 1 wei, amount1 + 1)); lpm.modifyLiquidities(calls, _deadline); } @@ -553,7 +608,9 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { swap(key, true, -10e18, ZERO_BYTES); bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, slippage, slippage, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); + vm.expectRevert( + abi.encodeWithSelector(SlippageCheck.MaximumAmountExceeded.selector, slippage, 299996249439153403) + ); lpm.modifyLiquidities(calls, _deadline); } @@ -563,7 +620,16 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + liquidityAlice, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + alice, + ZERO_BYTES + ) ); planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); @@ -605,7 +671,16 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + liquidityAlice, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + alice, + ZERO_BYTES + ) ); planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); @@ -643,14 +718,14 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { mint(config, liquidityAlice, alice, ZERO_BYTES); uint256 tokenIdAlice = lpm.nextTokenId() - 1; - uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice); assertEq(liquidity, liquidityAlice); // alice increases with the balance in the position manager Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenIdAlice, config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenIdAlice, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); @@ -678,7 +753,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { uint256 amount0 = uint128(-delta.amount0()); uint256 amount1 = uint128(-delta.amount1()); - liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); + liquidity = lpm.getPositionLiquidity(tokenIdAlice); assertEq(liquidity, 2 * liquidityAlice); // The balances were swept back to this address. @@ -715,7 +790,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, maxClear)); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, maxClear)); @@ -744,7 +819,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, type(uint256).max)); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, type(uint256).max)); diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index 662ddda15..c79f74dea 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -32,12 +32,11 @@ import {MockSubscriber} from "../mocks/MockSubscriber.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Planner, Plan} from "../shared/Planner.sol"; -import {PositionConfig, PositionConfigLibrary} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { using FixedPointMathLib for uint256; using CurrencyLibrary for Currency; - using PositionConfigLibrary for PositionConfig; using Planner for Plan; using PoolIdLibrary for PoolKey; using StateLibrary for IPoolManager; @@ -54,8 +53,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // This is needed to receive return deltas from modifyLiquidity calls. deployPosmHookSavesDelta(); - currency0 = CurrencyLibrary.NATIVE; - (nativeKey, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + currency0 = CurrencyLibrary.ADDRESS_ZERO; + (nativeKey, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1); deployPosm(manager); // currency0 is the native token so only execute approvals for currency1. @@ -91,7 +90,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities{value: amount0 + 1}(calls, _deadline); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0())), "incorrect amount0"); @@ -117,7 +116,9 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -143,7 +144,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities{value: amount0 * 2 + 1}(calls, _deadline); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); // only paid the delta amount, with excess tokens returned to caller @@ -169,7 +170,16 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + liquidityToAdd, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES + ) ); planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); // sweep the excess eth @@ -188,7 +198,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities{value: amount0 * 2 + 1}(calls, _deadline); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); // only paid the delta amount, with excess tokens returned to caller @@ -212,7 +222,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -227,7 +237,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // No decrease/modifyLiq call will actually happen on the call to burn so the deltas array will be the same length. assertEq(numDeltas, hook.numberDeltasReturned()); - liquidity = lpm.getPositionLiquidity(tokenId, config); + liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -265,7 +275,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -278,14 +288,14 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 numDeltas = hook.numberDeltasReturned(); Plan memory planner = Planner.init(); planner.add( - Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); lpm.modifyLiquidities(calls, _deadline); // No decrease/modifyLiq call will actually happen on the call to burn so the deltas array will be the same length. assertEq(numDeltas, hook.numberDeltasReturned()); - liquidity = lpm.getPositionLiquidity(tokenId, config); + liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -323,7 +333,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -333,7 +343,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { burn(tokenId, config, ZERO_BYTES); BalanceDelta deltaBurn = getLastDelta(); - liquidity = lpm.getPositionLiquidity(tokenId, config); + liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -371,7 +381,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -380,13 +390,13 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( - Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); lpm.modifyLiquidities(calls, _deadline); BalanceDelta deltaBurn = getLastDelta(); - liquidity = lpm.getPositionLiquidity(tokenId, config); + liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -437,7 +447,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); // verify position liquidity increased - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled // verify native token balances changed as expected @@ -477,7 +487,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); @@ -489,7 +499,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); // verify position liquidity increased - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled // verify native token balances changed as expected, with overpaid tokens returned @@ -528,7 +538,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); // sweep the excess eth @@ -539,7 +549,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); // verify position liquidity increased - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled // verify native token balances changed as expected, with overpaid tokens returned @@ -576,7 +586,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); // verify native token balances changed as expected @@ -613,15 +623,13 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.DECREASE_LIQUIDITY, - abi.encode( - tokenId, config, decreaseLiquidityDelta, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES - ) + abi.encode(tokenId, decreaseLiquidityDelta, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); // verify native token balances changed as expected @@ -676,8 +684,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); Plan memory planner = Planner.init(); planner.add( - Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); lpm.modifyLiquidities(calls, _deadline); @@ -711,8 +718,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( - Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); address alice = address(0xABCD); @@ -755,8 +761,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( - Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, ActionConstants.MSG_SENDER); @@ -777,21 +782,30 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory plan = Planner.init(); plan.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + 100e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES + ) ); plan.add(Actions.CLOSE_CURRENCY, abi.encode(config.poolKey.currency0)); plan.add(Actions.CLOSE_CURRENCY, abi.encode(config.poolKey.currency1)); - plan.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, address(this))); + plan.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, address(this))); bytes memory actions = plan.encode(); bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); - calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub, ZERO_BYTES); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, sub, ZERO_BYTES); lpm.multicall{value: 10e18}(calls); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 100e18); assertEq(sub.notifySubscribeCount(), 1); diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 22f887f04..0ce9a5b9a 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -17,7 +17,7 @@ import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; import {ERC721Permit_v4} from "../../src/base/ERC721Permit_v4.sol"; import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; @@ -43,7 +43,7 @@ contract PermitTest is Test, PosmTestSetup { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); - (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); // Requires currency0 and currency1 to be set in base Deployers contract. deployAndApprovePosm(manager); @@ -64,7 +64,7 @@ contract PermitTest is Test, PosmTestSetup { keccak256( abi.encode( keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), - keccak256("Uniswap V4 Positions NFT"), // storage is private on EIP712.sol so we need to hardcode these + keccak256("Uniswap v4 Positions NFT"), // storage is private on EIP712.sol so we need to hardcode these block.chainid, address(lpm) ) @@ -72,6 +72,27 @@ contract PermitTest is Test, PosmTestSetup { ); } + function test_permit_increaseLiquidity() public { + uint256 liquidityAlice = 1e18; + uint256 tokenIdAlice = lpm.nextTokenId(); + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + + // alice gives bob permissions + permit(alicePK, tokenIdAlice, bob, 1); + + // bob can increase liquidity on alice's token + uint256 liquidityToAdd = 0.4444e18; + vm.startPrank(bob); + increaseLiquidity(tokenIdAlice, config, liquidityToAdd, ZERO_BYTES); + vm.stopPrank(); + + // alice's position increased liquidity + uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice); + + assertEq(liquidity, liquidityAlice + liquidityToAdd); + } + function test_permit_decreaseLiquidity() public { uint256 liquidityAlice = 1e18; vm.prank(alice); @@ -88,7 +109,7 @@ contract PermitTest is Test, PosmTestSetup { vm.stopPrank(); // alice's position decreased liquidity - uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice); assertEq(liquidity, liquidityAlice - liquidityToRemove); } @@ -143,9 +164,22 @@ contract PermitTest is Test, PosmTestSetup { vm.stopPrank(); } - // unapproved callers can increase others' positions - // see `test_increaseLiquidity_withUnapprovedCaller()` - // function test_noPermit_increaseLiquidityRevert() public {} + /// @dev unapproved callers CANNOT increase others' positions + function test_noPermit_increaseLiquidityRevert() public { + // increase fails if the owner did not permit + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // bob cannot increase liquidity on alice's token + uint256 liquidityToAdd = 0.4444e18; + bytes memory decrease = getIncreaseEncoded(tokenIdAlice, config, liquidityToAdd, ZERO_BYTES); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(bob))); + lpm.modifyLiquidities(decrease, _deadline); + vm.stopPrank(); + } function test_noPermit_decreaseLiquidityRevert() public { // decreaseLiquidity fails if the owner did not permit @@ -183,6 +217,27 @@ contract PermitTest is Test, PosmTestSetup { vm.stopPrank(); } + /// @notice revoking a nonce prevents it from being used in permit() + function test_fuzz_noPermit_revokeRevert(uint256 nonce) public { + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // alice revokes the nonce + vm.prank(alice); + lpm.revokeNonce(nonce); + + // alice gives bob spender permissions + bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + } + // Bob can use alice's signature to permit & decrease liquidity function test_permit_operatorSelfPermit() public { uint256 liquidityAlice = 1e18; @@ -207,7 +262,7 @@ contract PermitTest is Test, PosmTestSetup { decreaseLiquidity(tokenId, config, liquidityToRemove, ZERO_BYTES); vm.stopPrank(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, liquidityAlice - liquidityToRemove); } @@ -236,7 +291,7 @@ contract PermitTest is Test, PosmTestSetup { decreaseLiquidity(tokenId, config, liquidityToRemove, ZERO_BYTES); vm.stopPrank(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, liquidityAlice - liquidityToRemove); } diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 161321eac..bf27604f9 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -19,11 +19,12 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {MockSubscriber} from "../mocks/MockSubscriber.sol"; contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { using FixedPointMathLib for uint256; @@ -43,6 +44,8 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { PositionConfig config; PositionConfig configNative; + MockSubscriber sub; + function setUp() public { (alice, alicePK) = makeAddrAndKey("ALICE"); (bob, bobPK) = makeAddrAndKey("BOB"); @@ -50,8 +53,8 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); - (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); - (nativeKey,) = initPool(CurrencyLibrary.NATIVE, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); + (nativeKey,) = initPool(CurrencyLibrary.ADDRESS_ZERO, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1); FEE_WAD = uint256(key.fee).mulDivDown(FixedPointMathLib.WAD, 1_000_000); // Requires currency0 and currency1 to be set in base Deployers contract. @@ -68,13 +71,21 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // define a reusable range config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); configNative = PositionConfig({poolKey: nativeKey, tickLower: -300, tickUpper: 300}); + + sub = new MockSubscriber(lpm); + } + + function test_bytecodeSize_positionManager() public { + snapSize("positionManager bytecode size", address(lpm)); } function test_gas_mint_withClose() public { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -90,7 +101,16 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_mint_withSettlePair() public { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, - abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + 10_000 ether, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); @@ -107,7 +127,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -131,7 +153,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -155,7 +179,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -175,7 +201,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -189,7 +215,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); @@ -233,7 +259,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); // because its a perfect autocompound, the delta is exactly 0 and we dont need to "close" deltas bytes memory calls = planner.encode(); @@ -281,7 +307,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, halfTokensOwedAlice + 1 wei)); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, halfTokensOwedAlice + 1 wei)); @@ -328,7 +354,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenIdAlice, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -344,7 +370,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -358,7 +384,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); @@ -371,7 +397,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Use multicall to initialize a pool and mint liquidity bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1, ZERO_BYTES); + calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); config = PositionConfig({ poolKey: key, @@ -383,7 +409,14 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { planner.add( Actions.MINT_POSITION, abi.encode( - config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + config.poolKey, + config.tickLower, + config.tickUpper, + 100e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -403,8 +436,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Collect by calling decrease with 0. Plan memory planner = Planner.init().add( - Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -421,8 +453,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Collect by calling decrease with 0. Plan memory planner = Planner.init().add( - Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); @@ -437,7 +468,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, + config.poolKey, + config.tickLower, + config.tickUpper, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -462,7 +495,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -483,8 +516,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -497,7 +529,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -510,7 +542,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); @@ -524,7 +556,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { decreaseLiquidity(tokenId, config, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); // There is no need to include CLOSE commands. @@ -541,10 +573,10 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); planner.add( - Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); // We must include CLOSE commands. @@ -579,7 +611,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { planner.add( Actions.MINT_POSITION, abi.encode( - configNative, + configNative.poolKey, + configNative.tickLower, + configNative.tickUpper, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, @@ -589,7 +623,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); - planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER)); bytes memory calls = planner.encode(); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( @@ -610,11 +644,18 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { planner.add( Actions.MINT_POSITION, abi.encode( - configNative, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES + configNative.poolKey, + configNative.tickLower, + configNative.tickUpper, + liquidityToAdd, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES ) ); planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); - planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, address(this))); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, address(this))); bytes memory calls = planner.encode(); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( @@ -671,8 +712,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.BURN_POSITION, - abi.encode(tokenId, configNative, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); @@ -685,8 +725,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.BURN_POSITION, - abi.encode(tokenId, configNative, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(configNative.poolKey, address(this)); @@ -700,8 +739,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { decreaseLiquidity(tokenId, configNative, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( - Actions.BURN_POSITION, - abi.encode(tokenId, configNative, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); // There is no need to include CLOSE commands. @@ -718,9 +756,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, configNative, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - planner.add(Actions.BURN_POSITION, abi.encode(tokenId, configNative, 0 wei, 0 wei, ZERO_BYTES)); + planner.add(Actions.BURN_POSITION, abi.encode(tokenId, 0 wei, 0 wei, ZERO_BYTES)); // We must include CLOSE commands. bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); @@ -818,7 +856,16 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + liquidityAlice, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + alice, + ZERO_BYTES + ) ); planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); @@ -843,11 +890,22 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory plan = Planner.init(); plan.add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, ActionConstants.MSG_SENDER); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_take_take"); } + + function test_gas_subscribe_unsubscribe() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + snapLastCall("PositionManager_subscribe"); + + lpm.unsubscribe(tokenId); + snapLastCall("PositionManager_unsubscribe"); + } } diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol new file mode 100644 index 000000000..664758616 --- /dev/null +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -0,0 +1,967 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; + +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; + +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; +import {ReentrancyLock} from "../../src/base/ReentrancyLock.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; +import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; + +import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; +import {Planner, Plan} from "../shared/Planner.sol"; +import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {Planner, Plan} from "../shared/Planner.sol"; +import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; +import {MockFOT} from "../mocks/MockFeeOnTransfer.sol"; + +contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityFuzzers { + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + using Planner for Plan; + using BipsLibrary for uint256; + + PoolId poolId; + address alice; + uint256 alicePK; + address bob; + + PoolKey fotKey; + + PositionConfig config; + PositionConfig wethConfig; + PositionConfig nativeConfig; + PositionConfig fotConfig; + + MockERC20 fotToken; + + function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob,) = makeAddrAndKey("BOB"); + + deployFreshManagerAndRouters(); + deployMintAndApprove2Currencies(); + + // Requires currency0 and currency1 to be set in base Deployers contract. + deployAndApprovePosm(manager); + + seedBalance(alice); + approvePosmFor(alice); + + // must deploy after posm + // Deploys a hook which can accesses IPositionManager.modifyLiquiditiesWithoutUnlock + deployPosmHookModifyLiquidities(); + seedBalance(address(hookModifyLiquidities)); + + (key, poolId) = initPool(currency0, currency1, IHooks(hookModifyLiquidities), 3000, SQRT_PRICE_1_1); + wethKey = initPoolUnsorted(Currency.wrap(address(_WETH9)), currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); + + seedWeth(address(this)); + approvePosmCurrency(Currency.wrap(address(_WETH9))); + + nativeKey = PoolKey(CurrencyLibrary.ADDRESS_ZERO, currency1, 3000, 60, IHooks(address(0))); + manager.initialize(nativeKey, SQRT_PRICE_1_1); + + config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); + wethConfig = PositionConfig({ + poolKey: wethKey, + tickLower: TickMath.minUsableTick(wethKey.tickSpacing), + tickUpper: TickMath.maxUsableTick(wethKey.tickSpacing) + }); + nativeConfig = PositionConfig({poolKey: nativeKey, tickLower: -120, tickUpper: 120}); + + vm.deal(address(this), 1000 ether); + + fotToken = new MockFOT(lpm); + approvePosmCurrency(Currency.wrap(address(fotToken))); + seedToken(fotToken, address(this)); + fotKey = initPoolUnsorted(Currency.wrap(address(fotToken)), currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); + } + + /// @dev minting liquidity without approval is allowable + function test_hook_mint() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook mints a new position in beforeSwap via hookData + uint256 hookTokenId = lpm.nextTokenId(); + uint256 newLiquidity = 10e18; + bytes memory calls = getMintEncoded(config, newLiquidity, address(hookModifyLiquidities), ZERO_BYTES); + + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId); + + // original liquidity unchanged + assertEq(liquidity, initialLiquidity); + + // hook minted its own position + liquidity = lpm.getPositionLiquidity(hookTokenId); + assertEq(liquidity, newLiquidity); + + assertEq(lpm.ownerOf(tokenId), address(this)); // original position owned by this contract + assertEq(lpm.ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook + } + + /// @dev hook must be approved to increase liquidity + function test_hook_increaseLiquidity() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // approve the hook for increasing liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + // hook increases liquidity in beforeSwap via hookData + uint256 newLiquidity = 10e18; + bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, ZERO_BYTES); + + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId); + + assertEq(liquidity, initialLiquidity + newLiquidity); + } + + /// @dev hook can decrease liquidity with approval + function test_hook_decreaseLiquidity() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // approve the hook for decreasing liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + // hook decreases liquidity in beforeSwap via hookData + uint256 liquidityToDecrease = 10e18; + bytes memory calls = getDecreaseEncoded(tokenId, config, liquidityToDecrease, ZERO_BYTES); + + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId); + + assertEq(liquidity, initialLiquidity - liquidityToDecrease); + } + + /// @dev hook can collect liquidity with approval + function test_hook_collect() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // approve the hook for collecting liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + // donate to generate revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate(config.poolKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + uint256 balance0HookBefore = currency0.balanceOf(address(hookModifyLiquidities)); + uint256 balance1HookBefore = currency1.balanceOf(address(hookModifyLiquidities)); + + // hook collects liquidity in beforeSwap via hookData + bytes memory calls = getCollectEncoded(tokenId, config, ZERO_BYTES); + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId); + + // liquidity unchanged + assertEq(liquidity, initialLiquidity); + + // hook collected the fee revenue + assertEq(currency0.balanceOf(address(hookModifyLiquidities)), balance0HookBefore + feeRevenue0 - 1 wei); // imprecision, core is keeping 1 wei + assertEq(currency1.balanceOf(address(hookModifyLiquidities)), balance1HookBefore + feeRevenue1 - 1 wei); + } + + /// @dev hook can burn liquidity with approval + function test_hook_burn() public { + // mint some liquidity that is NOT burned in beforeSwap + mint(config, 100e18, address(this), ZERO_BYTES); + + // the position to be burned by the hook + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + // TODO: make this less jank since HookModifyLiquidites also has delta saving capabilities + // BalanceDelta mintDelta = getLastDelta(); + BalanceDelta mintDelta = hookModifyLiquidities.deltas(hookModifyLiquidities.numberDeltasReturned() - 1); + + // approve the hook for burning liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + uint256 balance0HookBefore = currency0.balanceOf(address(hookModifyLiquidities)); + uint256 balance1HookBefore = currency1.balanceOf(address(hookModifyLiquidities)); + + // hook burns liquidity in beforeSwap via hookData + bytes memory calls = getBurnEncoded(tokenId, config, ZERO_BYTES); + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId); + + // liquidity burned + assertEq(liquidity, 0); + // 721 will revert if the token does not exist + vm.expectRevert(); + lpm.ownerOf(tokenId); + + // hook claimed the burned liquidity + assertEq( + currency0.balanceOf(address(hookModifyLiquidities)), + balance0HookBefore + uint128(-mintDelta.amount0() - 1 wei) // imprecision since core is keeping 1 wei + ); + assertEq( + currency1.balanceOf(address(hookModifyLiquidities)), + balance1HookBefore + uint128(-mintDelta.amount1() - 1 wei) + ); + } + + // --- Revert Scenarios --- // + /// @dev Hook does not have approval so increasing liquidity should revert + function test_hook_increaseLiquidity_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook decreases liquidity in beforeSwap via hookData + uint256 liquidityToAdd = 10e18; + bytes memory calls = getIncreaseEncoded(tokenId, config, liquidityToAdd, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hookModifyLiquidities), + IHooks.beforeSwap.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev Hook does not have approval so decreasing liquidity should revert + function test_hook_decreaseLiquidity_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook decreases liquidity in beforeSwap via hookData + uint256 liquidityToDecrease = 10e18; + bytes memory calls = getDecreaseEncoded(tokenId, config, liquidityToDecrease, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hookModifyLiquidities), + IHooks.beforeSwap.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev hook does not have approval so collecting liquidity should revert + function test_hook_collect_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // donate to generate revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate(config.poolKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + // hook collects liquidity in beforeSwap via hookData + bytes memory calls = getCollectEncoded(tokenId, config, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hookModifyLiquidities), + IHooks.beforeSwap.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev hook does not have approval so burning liquidity should revert + function test_hook_burn_revert() public { + // the position to be burned by the hook + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook burns liquidity in beforeSwap via hookData + bytes memory calls = getBurnEncoded(tokenId, config, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hookModifyLiquidities), + IHooks.beforeSwap.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev hook cannot re-enter modifyLiquiditiesWithoutUnlock in beforeRemoveLiquidity + function test_hook_increaseLiquidity_reenter_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + uint256 newLiquidity = 10e18; + + // to be provided as hookData, so beforeAddLiquidity attempts to increase liquidity + bytes memory hookCall = getIncreaseEncoded(tokenId, config, newLiquidity, ZERO_BYTES); + bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, hookCall); + + // should revert because hook is re-entering modifyLiquiditiesWithoutUnlock + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hookModifyLiquidities), + IHooks.beforeAddLiquidity.selector, + abi.encodeWithSelector(ReentrancyLock.ContractLocked.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + lpm.modifyLiquidities(calls, _deadline); + } + + function test_wrap_mint_usingContractBalance() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _wrap with contract balance + // 2 _mint + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // 5 _sweep weth since eth was entirely wrapped + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + planner.add(Actions.WRAP, abi.encode(ActionConstants.CONTRACT_BALANCE)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + wethConfig.poolKey, + wethConfig.tickLower, + wethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + // we wrapped the full contract balance so we sweep back in the wrapped currency + planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); + bytes memory actions = planner.encode(); + + // Overestimate eth amount. + lpm.modifyLiquidities{value: 102 ether}(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + // The full eth amount was "spent" because some was wrapped into weth and refunded. + assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 102 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_wrap_mint_openDelta() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _mint + // 2 _wrap with open delta + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // 5 _sweep eth since only the open delta amount was wrapped + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + + planner.add( + Actions.MINT_POSITION, + abi.encode( + wethConfig.poolKey, + wethConfig.tickLower, + wethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + planner.add(Actions.WRAP, abi.encode(ActionConstants.OPEN_DELTA)); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + // we wrapped the open delta balance so we sweep back in the native currency + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER)); + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities{value: 102 ether}(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + // Approx 100 eth was spent because the extra 2 were refunded. + assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_wrap_mint_usingExactAmount() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _wrap with an amount + // 2 _mint + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // 5 _sweep weth since eth was entirely wrapped + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + planner.add(Actions.WRAP, abi.encode(100 ether)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + wethConfig.poolKey, + wethConfig.tickLower, + wethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + // we wrapped all 100 eth so we sweep back in the wrapped currency for safety measure + planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities{value: 100 ether}(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + // The full eth amount was "spent" because some was wrapped into weth and refunded. + assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_wrap_mint_revertsInsufficientBalance() public { + // 1 _wrap with more eth than is sent in + + Plan memory planner = Planner.init(); + // Wrap more eth than what is sent in. + planner.add(Actions.WRAP, abi.encode(101 ether)); + + bytes memory actions = planner.encode(); + + vm.expectRevert(DeltaResolver.InsufficientBalance.selector); + lpm.modifyLiquidities{value: 100 ether}(actions, _deadline); + } + + function test_unwrap_usingContractBalance() public { + // weth-currency1 pool + // output: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _burn + // 2 _take where the weth is sent to the lpm contract + // 3 _take where currency1 is sent to the msg sender + // 4 _unwrap using contract balance + // 5 _sweep where eth is sent to msg sender + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + bytes memory actions = getMintEncoded(wethConfig, liquidityAmount, address(this), ZERO_BYTES); + lpm.modifyLiquidities(actions, _deadline); + + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + Plan memory planner = Planner.init(); + planner.add( + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + // take the weth to the position manager to be unwrapped + planner.add(Actions.TAKE, abi.encode(address(_WETH9), ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); + planner.add( + Actions.TAKE, + abi.encode(address(Currency.unwrap(currency1)), ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA) + ); + planner.add(Actions.UNWRAP, abi.encode(ActionConstants.CONTRACT_BALANCE)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER)); + + actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + assertApproxEqAbs(balanceEthAfter - balanceEthBefore, 100 ether, 1 wei); + assertApproxEqAbs(balance1After - balance1Before, 100 ether, 1 wei); + assertEq(lpm.getPositionLiquidity(tokenId), 0); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_unwrap_openDelta_reinvest() public { + // weth-currency1 pool rolls half to eth-currency1 pool + // output: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _burn (weth-currency1) + // 2 _take where the weth is sent to the lpm contract + // 4 _mint to an eth pool + // 4 _unwrap using open delta (pool managers ETH balance) + // 3 _take where leftover currency1 is sent to the msg sender + // 5 _settle eth open delta + // 5 _sweep leftover weth + + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + bytes memory actions = getMintEncoded(wethConfig, liquidityAmount, address(this), ZERO_BYTES); + lpm.modifyLiquidities(actions, _deadline); + + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 balanceWethBefore = _WETH9.balanceOf(address(this)); + + uint128 newLiquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(nativeConfig.tickLower), + TickMath.getSqrtPriceAtTick(nativeConfig.tickUpper), + 50 ether, + 50 ether + ); + + Plan memory planner = Planner.init(); + planner.add( + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + // take the weth to the position manager to be unwrapped + planner.add(Actions.TAKE, abi.encode(address(_WETH9), ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + nativeConfig.poolKey, + nativeConfig.tickLower, + nativeConfig.tickUpper, + newLiquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + planner.add(Actions.UNWRAP, abi.encode(ActionConstants.OPEN_DELTA)); + // pay the eth + planner.add(Actions.SETTLE, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.OPEN_DELTA, false)); + // take the leftover currency1 + planner.add( + Actions.TAKE, + abi.encode(address(Currency.unwrap(currency1)), ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA) + ); + planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); + + actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 balanceWethAfter = _WETH9.balanceOf(address(this)); + + // Eth balance should not change. + assertEq(balanceEthAfter, balanceEthBefore); + // Only half of the original liquidity was reinvested. + assertApproxEqAbs(balance1After - balance1Before, 50 ether, 1 wei); + assertApproxEqAbs(balanceWethAfter - balanceWethBefore, 50 ether, 1 wei); + assertEq(lpm.getPositionLiquidity(tokenId), 0); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_unwrap_revertsInsufficientBalance() public { + // 1 _unwrap with more than is in the contract + + Plan memory planner = Planner.init(); + // unwraps more eth than what is in the contract + planner.add(Actions.UNWRAP, abi.encode(101 ether)); + + bytes memory actions = planner.encode(); + + vm.expectRevert(DeltaResolver.InsufficientBalance.selector); + lpm.modifyLiquidities(actions, _deadline); + } + + function test_mintFromDeltas_fot() public { + // Use a 1% fee. + MockFOT(address(fotToken)).setFee(100); + uint256 tokenId = lpm.nextTokenId(); + + uint256 fotBalanceBefore = Currency.wrap(address(fotToken)).balanceOf(address(this)); + + uint256 amountAfterTransfer = 990e18; + uint256 amountToSendFot = 1000e18; + + (uint256 amount0, uint256 amount1) = fotKey.currency0 == Currency.wrap(address(fotToken)) + ? (amountToSendFot, amountAfterTransfer) + : (amountAfterTransfer, amountToSendFot); + + // Calculcate the expected liquidity from the amounts after the transfer. They are the same for both currencies. + uint256 expectedLiquidity = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(LIQUIDITY_PARAMS.tickLower), + TickMath.getSqrtPriceAtTick(LIQUIDITY_PARAMS.tickUpper), + amountAfterTransfer, + amountAfterTransfer + ); + + Plan memory planner = Planner.init(); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency0, amount0, true)); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency1, amount1, true)); + planner.add( + Actions.MINT_POSITION_FROM_DELTAS, + abi.encode( + fotKey, + LIQUIDITY_PARAMS.tickLower, + LIQUIDITY_PARAMS.tickUpper, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + bytes memory plan = planner.encode(); + + lpm.modifyLiquidities(plan, _deadline); + + uint256 fotBalanceAfter = Currency.wrap(address(fotToken)).balanceOf(address(this)); + + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity); + assertEq(fotBalanceBefore - fotBalanceAfter, 1000e18); + } + + function test_increaseFromDeltas() public { + uint128 initialLiquidity = 1000e18; + uint256 tokenId = lpm.nextTokenId(); + fotConfig = PositionConfig({poolKey: fotKey, tickLower: -120, tickUpper: 120}); + + mint(fotConfig, initialLiquidity, address(this), ZERO_BYTES); + + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity); + + Plan memory planner = Planner.init(); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency0, 10e18, true)); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency1, 10e18, true)); + planner.add( + Actions.INCREASE_LIQUIDITY_FROM_DELTAS, + abi.encode(tokenId, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(fotConfig.tickLower), + TickMath.getSqrtPriceAtTick(fotConfig.tickUpper), + 10e18, + 10e18 + ); + + assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity + newLiquidity); + } + + function test_increaseFromDeltas_fot() public { + uint128 initialLiquidity = 1000e18; + uint256 tokenId = lpm.nextTokenId(); + fotConfig = PositionConfig({poolKey: fotKey, tickLower: -120, tickUpper: 120}); + + mint(fotConfig, initialLiquidity, address(this), ZERO_BYTES); + + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity); + + // Use a 1% fee. + MockFOT(address(fotToken)).setFee(100); + + // Set the fee on transfer amount 1% higher. + (uint256 amount0, uint256 amount1) = + fotKey.currency0 == Currency.wrap(address(fotToken)) ? (100e18, 99e18) : (99e19, 100e18); + + Plan memory planner = Planner.init(); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency0, amount0, true)); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency1, amount1, true)); + planner.add( + Actions.INCREASE_LIQUIDITY_FROM_DELTAS, + abi.encode(tokenId, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + (uint256 amount0AfterTransfer, uint256 amount1AfterTransfer) = + fotKey.currency0 == Currency.wrap(address(fotToken)) ? (99e18, 100e18) : (100e18, 99e19); + + uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(fotConfig.tickLower), + TickMath.getSqrtPriceAtTick(fotConfig.tickUpper), + amount0AfterTransfer, + amount1AfterTransfer + ); + + assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity + newLiquidity); + } + + function test_fuzz_mintFromDeltas_burn_fot( + uint256 bips, + uint256 amount0, + uint256 amount1, + int24 tickLower, + int24 tickUpper + ) public { + bips = bound(bips, 1, 10_000); + MockFOT(address(fotToken)).setFee(bips); + + tickLower = int24( + bound( + tickLower, + fotKey.tickSpacing * (TickMath.MIN_TICK / fotKey.tickSpacing), + fotKey.tickSpacing * (TickMath.MAX_TICK / fotKey.tickSpacing) + ) + ); + tickUpper = int24( + bound( + tickUpper, + fotKey.tickSpacing * (TickMath.MIN_TICK / fotKey.tickSpacing), + fotKey.tickSpacing * (TickMath.MAX_TICK / fotKey.tickSpacing) + ) + ); + + tickLower = fotKey.tickSpacing * (tickLower / fotKey.tickSpacing); + tickUpper = fotKey.tickSpacing * (tickUpper / fotKey.tickSpacing); + vm.assume(tickUpper > tickLower); + + (uint160 sqrtPriceX96,,,) = manager.getSlot0(fotKey.toId()); + uint128 maxLiquidityPerTick = Pool.tickSpacingToMaxLiquidityPerTick(fotKey.tickSpacing); + + (uint256 maxAmount0, uint256 maxAmount1) = LiquidityAmounts.getAmountsForLiquidity( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + maxLiquidityPerTick + ); + + maxAmount0 = maxAmount0 == 0 ? 1 : maxAmount0 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount0; + maxAmount1 = maxAmount1 == 0 ? 1 : maxAmount1 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount1; + amount0 = bound(amount0, 1, maxAmount0); + amount1 = bound(amount1, 1, maxAmount1); + + uint256 tokenId = lpm.nextTokenId(); + + uint256 balance0 = fotKey.currency0.balanceOf(address(this)); + uint256 balance1 = fotKey.currency1.balanceOf(address(this)); + uint256 balance0PM = fotKey.currency0.balanceOf(address(manager)); + uint256 balance1PM = fotKey.currency1.balanceOf(address(manager)); + + Plan memory planner = Planner.init(); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency0, amount0, true)); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency1, amount1, true)); + planner.add( + Actions.MINT_POSITION_FROM_DELTAS, + abi.encode( + fotKey, + tickLower, + tickUpper, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + // take the excess of each currency + planner.add(Actions.TAKE_PAIR, abi.encode(fotKey.currency0, fotKey.currency1, ActionConstants.MSG_SENDER)); + + bytes memory actions = planner.encode(); + + bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken)); + bool positionIsEntirelyInOtherToken = currency0IsFOT + ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96) + : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96); + + if (bips == 10000 && !positionIsEntirelyInOtherToken) { + vm.expectRevert(Position.CannotUpdateEmptyPosition.selector); + lpm.modifyLiquidities(actions, _deadline); + } else { + // MINT FROM DELTAS. + lpm.modifyLiquidities(actions, _deadline); + + uint256 balance0After = fotKey.currency0.balanceOf(address(this)); + uint256 balance1After = fotKey.currency1.balanceOf(address(this)); + uint256 balance0PMAfter = fotKey.currency0.balanceOf(address(manager)); + uint256 balance1PMAfter = fotKey.currency1.balanceOf(address(manager)); + + // Calculate the expected resulting balances used to create liquidity after the fee is applied. + uint256 amountInFOT = currency0IsFOT ? amount0 : amount1; + uint256 expectedFee = amountInFOT.calculatePortion(bips); + (uint256 expected0, uint256 expected1) = currency0IsFOT + ? (balance0 - balance0After - expectedFee, balance1 - balance1After) + : (balance0 - balance0After, balance1 - balance1After - expectedFee); + + assertEq(expected0, balance0PMAfter - balance0PM); + assertEq(expected1, balance1PMAfter - balance1PM); + + // the liquidity that was created is a diff of the balance change + uint128 expectedLiquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + expected0, + expected1 + ); + + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity); + + // BURN. + planner = Planner.init(); + // Note that the slippage does not include the fee from the transfer. + planner.add( + Actions.BURN_POSITION, + abi.encode(tokenId, expected0 == 0 ? 0 : expected0 - 1, expected1 == 0 ? 0 : expected1 - 1, ZERO_BYTES) + ); + + planner.add(Actions.TAKE_PAIR, abi.encode(fotKey.currency0, fotKey.currency1, ActionConstants.MSG_SENDER)); + + actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + assertEq(lpm.getPositionLiquidity(tokenId), 0); + } + } +} diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index da9734c0e..087c21027 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -21,7 +21,7 @@ import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {PoolInitializer} from "../../src/base/PoolInitializer.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; @@ -46,6 +46,8 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest address bob; // bob used for permit2 signature tests uint256 bobPK; + address charlie; // charlie will NOT approve posm in setup() + uint256 charliePK; Permit2Forwarder permit2Forwarder; @@ -54,6 +56,9 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest uint48 permitExpiration = uint48(block.timestamp + 10e18); uint48 permitNonce = 0; + // redefine error from permit2/src/PermitErrors.sol since its hard-pinned to a solidity version + error InvalidNonce(); + bytes32 PERMIT2_DOMAIN_SEPARATOR; PositionConfig config; @@ -61,11 +66,12 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest function setUp() public { (alice, alicePK) = makeAddrAndKey("ALICE"); (bob, bobPK) = makeAddrAndKey("BOB"); + (charlie, charliePK) = makeAddrAndKey("CHARLIE"); deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); - (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); // Requires currency0 and currency1 to be set in base Deployers contract. deployAndApprovePosm(manager); @@ -78,6 +84,13 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest seedBalance(bob); approvePosmFor(bob); + + // do not approve posm for charlie, but approve permit2 for allowance transfer + seedBalance(charlie); + vm.startPrank(charlie); + IERC20(Currency.unwrap(currency0)).approve(address(permit2), type(uint256).max); + IERC20(Currency.unwrap(currency1)).approve(address(permit2), type(uint256).max); + vm.stopPrank(); } function test_multicall_initializePool_mint() public { @@ -85,7 +98,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // Use multicall to initialize a pool and mint liquidity bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1, ZERO_BYTES); + calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); config = PositionConfig({ poolKey: key, @@ -97,7 +110,14 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest planner.add( Actions.MINT_POSITION, abi.encode( - config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + config.poolKey, + config.tickLower, + config.tickUpper, + 100e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -113,6 +133,86 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest assertGt(result.amount1(), 0); } + function test_multicall_initializePool_twice_andMint_succeeds() public { + key = PoolKey({currency0: currency0, currency1: currency1, fee: 0, tickSpacing: 10, hooks: IHooks(address(0))}); + manager.initialize(key, SQRT_PRICE_1_1); + + // Use multicall to initialize the pool again. + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); + + config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + 100e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); + + calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); + + IMulticall_v4(address(lpm)).multicall(calls); + + // test swap, doesn't revert, showing the mint succeeded even after initialize reverted + int256 amountSpecified = -1e18; + BalanceDelta result = swap(key, true, amountSpecified, ZERO_BYTES); + assertEq(result.amount0(), amountSpecified); + assertGt(result.amount1(), 0); + } + + function test_multicall_initializePool_mint_native() public { + key = PoolKey({ + currency0: CurrencyLibrary.ADDRESS_ZERO, + currency1: currency1, + fee: 0, + tickSpacing: 10, + hooks: IHooks(address(0)) + }); + + // Use multicall to initialize a pool and mint liquidity + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); + + config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode( + config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + ) + ); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); + + calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); + + IMulticall_v4(address(lpm)).multicall{value: 1000 ether}(calls); + + // test swap, doesn't revert, showing the pool was initialized + int256 amountSpecified = -1e18; + BalanceDelta result = swap(key, true, amountSpecified, ZERO_BYTES); + assertEq(result.amount0(), amountSpecified); + assertGt(result.amount1(), 0); + } + // charlie will attempt to decrease liquidity without approval // posm's NotApproved(charlie) should bubble up through Multicall function test_multicall_bubbleRevert() public { @@ -127,7 +227,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest Plan memory planner = Planner.init(); planner.add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 100e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 100e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -135,7 +235,6 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest bytes[] memory calls = new bytes[](1); calls[0] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); - address charlie = makeAddr("CHARLIE"); vm.startPrank(charlie); vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, charlie)); lpm.multicall(calls); @@ -157,7 +256,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest Plan memory planner = Planner.init(); planner.add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 100e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 100e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory actions = planner.encode(); @@ -169,26 +268,6 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest lpm.multicall(calls); } - // create a pool where tickSpacing is negative - // core's TickSpacingTooSmall(int24) should bubble up through Multicall - function test_multicall_bubbleRevert_core_args() public { - int24 tickSpacing = -10; - key = PoolKey({ - currency0: currency0, - currency1: currency1, - fee: 0, - tickSpacing: tickSpacing, - hooks: IHooks(address(0)) - }); - - // Use multicall to initialize a pool - bytes[] memory calls = new bytes[](1); - calls[0] = abi.encodeWithSelector(PoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1, ZERO_BYTES); - - vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooSmall.selector, tickSpacing)); - lpm.multicall(calls); - } - function test_multicall_permitAndDecrease() public { config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); uint256 liquidityAlice = 1e18; @@ -210,12 +289,12 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest ); uint256 liquidityToRemove = 0.4444e18; bytes memory actions = getDecreaseEncoded(tokenId, config, liquidityToRemove, ZERO_BYTES); - calls[1] = abi.encodeWithSelector(PositionManager(lpm).modifyLiquidities.selector, actions, _deadline); + calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); vm.prank(bob); lpm.multicall(calls); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, liquidityAlice - liquidityToRemove); } @@ -256,7 +335,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest vm.prank(bob); lpm.multicall(calls); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); (_amount,,) = permit2.allowance(address(bob), Currency.unwrap(currency0), address(lpm)); @@ -312,7 +391,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest vm.prank(bob); lpm.multicall(calls); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); (_amount0,,) = permit2.allowance(address(bob), Currency.unwrap(currency0), address(lpm)); (_amount1,,) = permit2.allowance(address(bob), Currency.unwrap(currency1), address(lpm)); @@ -321,4 +400,109 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest assertEq(liquidity, 10e18); assertEq(lpm.ownerOf(tokenId), bob); } + + /// @notice test that a front-ran permit does not fail a multicall with permit + function test_multicall_permit_frontrun_suceeds() public { + config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + + // Charlie signs permit for the two tokens + IAllowanceTransfer.PermitSingle memory permit0 = + defaultERC20PermitAllowance(Currency.unwrap(currency0), permitAmount, permitExpiration, permitNonce); + permit0.spender = address(lpm); + bytes memory sig0 = getPermitSignature(permit0, charliePK, PERMIT2_DOMAIN_SEPARATOR); + + IAllowanceTransfer.PermitSingle memory permit1 = + defaultERC20PermitAllowance(Currency.unwrap(currency1), permitAmount, permitExpiration, permitNonce); + permit1.spender = address(lpm); + bytes memory sig1 = getPermitSignature(permit1, charliePK, PERMIT2_DOMAIN_SEPARATOR); + + // bob front-runs the permits + vm.startPrank(bob); + lpm.permit(charlie, permit0, sig0); + lpm.permit(charlie, permit1, sig1); + vm.stopPrank(); + + // bob's front-run was successful + (uint160 _amount, uint48 _expiration, uint48 _nonce) = + permit2.allowance(charlie, Currency.unwrap(currency0), address(lpm)); + assertEq(_amount, permitAmount); + assertEq(_expiration, permitExpiration); + assertEq(_nonce, permitNonce + 1); + (uint160 _amount1, uint48 _expiration1, uint48 _nonce1) = + permit2.allowance(charlie, Currency.unwrap(currency1), address(lpm)); + assertEq(_amount1, permitAmount); + assertEq(_expiration1, permitExpiration); + assertEq(_nonce1, permitNonce + 1); + + // charlie tries to mint an LP token with multicall(permit, permit, mint) + bytes[] memory calls = new bytes[](3); + calls[0] = abi.encodeWithSelector(Permit2Forwarder(lpm).permit.selector, charlie, permit0, sig0); + calls[1] = abi.encodeWithSelector(Permit2Forwarder(lpm).permit.selector, charlie, permit1, sig1); + bytes memory mintCall = getMintEncoded(config, 10e18, charlie, ZERO_BYTES); + calls[2] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, mintCall, _deadline); + + uint256 tokenId = lpm.nextTokenId(); + vm.expectRevert(); + lpm.ownerOf(tokenId); // token does not exist + + bytes[] memory results = lpm.multicall(calls); + assertEq(results[0], abi.encode(abi.encodeWithSelector(InvalidNonce.selector))); + assertEq(results[1], abi.encode(abi.encodeWithSelector(InvalidNonce.selector))); + + assertEq(lpm.ownerOf(tokenId), charlie); + } + + /// @notice test that a front-ran permitBatch does not fail a multicall with permitBatch + function test_multicall_permitBatch_frontrun_suceeds() public { + config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + + // Charlie signs permitBatch for the two tokens + address[] memory tokens = new address[](2); + tokens[0] = Currency.unwrap(currency0); + tokens[1] = Currency.unwrap(currency1); + + IAllowanceTransfer.PermitBatch memory permit = + defaultERC20PermitBatchAllowance(tokens, permitAmount, permitExpiration, permitNonce); + permit.spender = address(lpm); + bytes memory sig = getPermitBatchSignature(permit, charliePK, PERMIT2_DOMAIN_SEPARATOR); + + // bob front-runs the permits + vm.prank(bob); + lpm.permitBatch(charlie, permit, sig); + + // bob's front-run was successful + (uint160 _amount, uint48 _expiration, uint48 _nonce) = + permit2.allowance(charlie, Currency.unwrap(currency0), address(lpm)); + assertEq(_amount, permitAmount); + assertEq(_expiration, permitExpiration); + assertEq(_nonce, permitNonce + 1); + (uint160 _amount1, uint48 _expiration1, uint48 _nonce1) = + permit2.allowance(charlie, Currency.unwrap(currency1), address(lpm)); + assertEq(_amount1, permitAmount); + assertEq(_expiration1, permitExpiration); + assertEq(_nonce1, permitNonce + 1); + + // charlie tries to mint an LP token with multicall(permitBatch, mint) + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector(Permit2Forwarder(lpm).permitBatch.selector, charlie, permit, sig); + bytes memory mintCall = getMintEncoded(config, 10e18, charlie, ZERO_BYTES); + calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, mintCall, _deadline); + + uint256 tokenId = lpm.nextTokenId(); + vm.expectRevert(); + lpm.ownerOf(tokenId); // token does not exist + + bytes[] memory results = lpm.multicall(calls); + assertEq(results[0], abi.encode(abi.encodeWithSelector(InvalidNonce.selector))); + + assertEq(lpm.ownerOf(tokenId), charlie); + } } diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index 3f8c7024e..cab0d0a94 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -2,54 +2,77 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {MockSubscriber} from "../mocks/MockSubscriber.sol"; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {MockReturnDataSubscriber} from "../mocks/MockBadSubscribers.sol"; +import {INotifier} from "../../src/interfaces/INotifier.sol"; +import {MockReturnDataSubscriber, MockRevertSubscriber} from "../mocks/MockBadSubscribers.sol"; +import {PositionInfoLibrary, PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol"; +import {MockReenterHook} from "../mocks/MockReenterHook.sol"; contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { using PoolIdLibrary for PoolKey; using StateLibrary for IPoolManager; using Planner for Plan; + using PositionInfoLibrary for PositionInfo; MockSubscriber sub; MockReturnDataSubscriber badSubscriber; PositionConfig config; + MockRevertSubscriber revertSubscriber; + MockReenterHook reenterHook; address alice = makeAddr("ALICE"); address bob = makeAddr("BOB"); + PositionConfig reenterConfig; + function setUp() public { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); - (key,) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1); // Requires currency0 and currency1 to be set in base Deployers contract. deployAndApprovePosm(manager); sub = new MockSubscriber(lpm); badSubscriber = new MockReturnDataSubscriber(lpm); + revertSubscriber = new MockRevertSubscriber(lpm); config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); + // set the reenter hook + MockReenterHook impl = new MockReenterHook(); + address hookAddr = payable(address(uint160(Hooks.BEFORE_ADD_LIQUIDITY_FLAG))); + vm.etch(hookAddr, address(impl).code); + reenterHook = MockReenterHook(hookAddr); + reenterHook.setPosm(lpm); + + PoolKey memory reenterKey = PoolKey(currency0, currency1, 3000, 60, IHooks(reenterHook)); + manager.initialize(reenterKey, SQRT_PRICE_1_1); + reenterConfig = PositionConfig({poolKey: reenterKey, tickLower: -60, tickUpper: 60}); + // TODO: Test NATIVE poolKey } function test_subscribe_revertsWithEmptyPositionConfig() public { uint256 tokenId = lpm.nextTokenId(); vm.expectRevert("NOT_MINTED"); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); } function test_subscribe_revertsWhenNotApproved() public { @@ -59,10 +82,10 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { // this contract is not approved to operate on alice's liq vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(this))); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); } - function test_subscribe_reverts_withIncorrectConfig() public { + function test_subscribe_succeeds() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); @@ -71,13 +94,17 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - PositionConfig memory incorrectConfig = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 301}); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); - vm.expectRevert(abi.encodeWithSelector(IPositionManager.IncorrectPositionConfigForTokenId.selector, tokenId)); - lpm.subscribe(tokenId, incorrectConfig, address(sub), ZERO_BYTES); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + assertEq(sub.notifySubscribeCount(), 1); } - function test_subscribe_succeeds() public { + /// @notice Revert when subscribing to an address without code + function test_subscribe_revert_empty(address _subscriber) public { + vm.assume(_subscriber.code.length == 0); + uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); @@ -86,11 +113,27 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + vm.expectRevert(INotifier.NoCodeSubscriber.selector); + lpm.subscribe(tokenId, _subscriber, ZERO_BYTES); + } + + function test_subscribe_revertsWithAlreadySubscribed() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); - assertEq(lpm.hasSubscriber(tokenId), true); + // successfully subscribe + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); assertEq(sub.notifySubscribeCount(), 1); + + vm.expectRevert(abi.encodeWithSelector(INotifier.AlreadySubscribed.selector, tokenId, sub)); + lpm.subscribe(tokenId, address(2), ZERO_BYTES); } function test_notifyModifyLiquidity_succeeds() public { @@ -102,16 +145,16 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); Plan memory plan = Planner.init(); for (uint256 i = 0; i < 10; i++) { plan.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); } @@ -122,26 +165,101 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { assertEq(sub.notifyModifyLiquidityCount(), 10); } - function test_notifyTransfer_withTransferFrom_succeeds() public { + function test_notifyModifyLiquidity_selfDestruct_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + // simulate selfdestruct by etching the bytecode to 0 + vm.etch(address(sub), ZERO_BYTES); + + uint256 liquidityToAdd = 10e18; + vm.expectRevert(INotifier.NoCodeSubscriber.selector); + increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); + } + + function test_notifyModifyLiquidity_args() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); + // donate to generate fee revenue, to be checked in subscriber + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate(config.poolKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + // approve this contract to operate on alices liq vm.startPrank(alice); lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); + uint256 liquidityToAdd = 10e18; + increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); + + assertEq(sub.notifyModifyLiquidityCount(), 1); + assertEq(sub.liquidityChange(), int256(liquidityToAdd)); + assertEq(int256(sub.feesAccrued().amount0()), int256(feeRevenue0) - 1 wei); + assertEq(int256(sub.feesAccrued().amount1()), int256(feeRevenue1) - 1 wei); + } + + function test_transferFrom_unsubscribes() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + lpm.transferFrom(alice, bob, tokenId); + + assertEq(sub.notifyUnsubscribeCount(), 1); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); + } + + function test_transferFrom_unsubscribes_selfDestruct() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + // simulate selfdestruct by etching the bytecode to 0 + vm.etch(address(sub), ZERO_BYTES); + + // unsubscribe happens anyway lpm.transferFrom(alice, bob, tokenId); - assertEq(sub.notifyTransferCount(), 1); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); } - function test_notifyTransfer_withSafeTransferFrom_succeeds() public { + function test_safeTransferFrom_unsubscribes() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); @@ -150,17 +268,19 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); lpm.safeTransferFrom(alice, bob, tokenId); - assertEq(sub.notifyTransferCount(), 1); + assertEq(sub.notifyUnsubscribeCount(), 1); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); } - function test_notifyTransfer_withSafeTransferFromData_succeeds() public { + function test_safeTransferFrom_unsubscribes_selfDestruct() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); @@ -169,14 +289,39 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + // simulate selfdestruct by etching the bytecode to 0 + vm.etch(address(sub), ZERO_BYTES); + + // unsubscribe happens anyway + lpm.safeTransferFrom(alice, bob, tokenId); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); + } + + function test_safeTransferFrom_unsubscribes_withData() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); lpm.safeTransferFrom(alice, bob, tokenId, ""); - assertEq(sub.notifyTransferCount(), 1); + assertEq(sub.notifyUnsubscribeCount(), 1); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); } function test_unsubscribe_succeeds() public { @@ -188,12 +333,12 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); - lpm.unsubscribe(tokenId, config, ZERO_BYTES); + lpm.unsubscribe(tokenId); assertEq(sub.notifyUnsubscribeCount(), 1); - assertEq(lpm.hasSubscriber(tokenId), false); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); assertEq(address(lpm.subscriber(tokenId)), address(0)); } @@ -206,14 +351,34 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(badSubscriber), ZERO_BYTES); + lpm.subscribe(tokenId, address(badSubscriber), ZERO_BYTES); MockReturnDataSubscriber(badSubscriber).setReturnDataSize(0x600000); - lpm.unsubscribe(tokenId, config, ZERO_BYTES); + lpm.unsubscribe(tokenId); // the subscriber contract call failed bc it used too much gas assertEq(MockReturnDataSubscriber(badSubscriber).notifyUnsubscribeCount(), 0); - assertEq(lpm.hasSubscriber(tokenId), false); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); + } + + function test_unsubscribe_selfDestructed() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + + // simulate selfdestruct by etching the bytecode to 0 + vm.etch(address(sub), ZERO_BYTES); + + lpm.unsubscribe(tokenId); + + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); assertEq(address(lpm.subscriber(tokenId)), address(0)); } @@ -223,23 +388,32 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { Plan memory plan = Planner.init(); plan.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + 100e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES + ) ); bytes memory actions = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); - calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub, ZERO_BYTES); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, sub, ZERO_BYTES); lpm.multicall(calls); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 100e18); assertEq(sub.notifySubscribeCount(), 1); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); } @@ -250,7 +424,16 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { Plan memory plan = Planner.init(); plan.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + 100e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES + ) ); bytes memory actions = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); @@ -258,24 +441,24 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { plan = Planner.init(); plan.add( Actions.INCREASE_LIQUIDITY, - abi.encode(tokenId, config, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + abi.encode(tokenId, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); bytes memory actions2 = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); bytes[] memory calls = new bytes[](3); calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); - calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub, ZERO_BYTES); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, sub, ZERO_BYTES); calls[2] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions2, _deadline); lpm.multicall(calls); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 110e18); assertEq(sub.notifySubscribeCount(), 1); assertEq(sub.notifyModifyLiquidityCount(), 1); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); } @@ -288,8 +471,25 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - vm.expectRevert(); - lpm.unsubscribe(tokenId, config, ZERO_BYTES); + vm.expectRevert(INotifier.NotSubscribed.selector); + lpm.unsubscribe(tokenId); + } + + function test_unsubscribe_twice_reverts() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + + lpm.unsubscribe(tokenId); + + vm.expectRevert(INotifier.NotSubscribed.selector); + lpm.unsubscribe(tokenId); } function test_subscribe_revertsOnEOA() public { @@ -317,15 +517,71 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), subData); + lpm.subscribe(tokenId, address(sub), subData); - assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); assertEq(sub.notifySubscribeCount(), 1); assertEq(abi.decode(sub.subscribeData(), (address)), address(this)); } - function test_unsubscribe_withData() public { + function test_subscribe_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + revertSubscriber.setRevert(true); + + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(revertSubscriber), + ISubscriber.notifySubscribe.selector, + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifySubscribe"), + abi.encodeWithSelector(INotifier.SubscriptionReverted.selector) + ) + ); + lpm.subscribe(tokenId, address(revertSubscriber), ZERO_BYTES); + } + + function test_notifyModifyLiquidiy_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(revertSubscriber), ZERO_BYTES); + + Plan memory plan = Planner.init(); + for (uint256 i = 0; i < 10; i++) { + plan.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + } + + bytes memory calls = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(revertSubscriber), + ISubscriber.notifyModifyLiquidity.selector, + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifyModifyLiquidity"), + abi.encodeWithSelector(INotifier.ModifyLiquidityNotificationReverted.selector) + ) + ); + lpm.modifyLiquidities(calls, _deadline); + } + + /// @notice burning a position will automatically notify burn + function test_notifyBurn_succeeds() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); @@ -336,13 +592,115 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + lpm.subscribe(tokenId, address(sub), subData); - lpm.unsubscribe(tokenId, config, subData); + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); + assertEq(sub.notifyUnsubscribeCount(), 0); - assertEq(sub.notifyUnsubscribeCount(), 1); - assertEq(lpm.hasSubscriber(tokenId), false); - assertEq(address(lpm.subscriber(tokenId)), address(0)); - assertEq(abi.decode(sub.unsubscribeData(), (address)), address(this)); + // burn the position, causing a notifyBurn + burn(tokenId, config, ZERO_BYTES); + + // position is now unsubscribed + assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); + assertEq(sub.notifyUnsubscribeCount(), 0); + assertEq(sub.notifyBurnCount(), 1); + } + + /// @notice Test that users cannot forcibly avoid unsubscribe logic via gas limits + function test_fuzz_unsubscribe_with_gas_limit(uint64 gasLimit) public { + // enforce a minimum amount of gas to avoid OutOfGas reverts + gasLimit = uint64(bound(gasLimit, 125_000, block.gaslimit)); + + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + uint256 beforeUnsubCount = sub.notifyUnsubscribeCount(); + + if (gasLimit < lpm.unsubscribeGasLimit()) { + // gas too low to call a valid unsubscribe + vm.expectRevert(INotifier.GasLimitTooLow.selector); + lpm.unsubscribe{gas: gasLimit}(tokenId); + } else { + // increasing gas limit succeeds and unsubscribe was called + lpm.unsubscribe{gas: gasLimit}(tokenId); + assertEq(sub.notifyUnsubscribeCount(), beforeUnsubCount + 1); + } + } + + function test_unsubscribe_reverts_PoolManagerMustBeLocked() public { + uint256 tokenId = lpm.nextTokenId(); + mint(reenterConfig, 10e18, address(this), ZERO_BYTES); + + bytes memory hookData = abi.encode(lpm.unsubscribe.selector, address(this), tokenId); + bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData); + + // approve hook as it should not revert because it does not have permissions + lpm.approve(address(reenterHook), tokenId); + // subscribe as it should not revert because there is no subscriber + lpm.subscribe(tokenId, address(sub), ZERO_BYTES); + + // should revert since the pool manager is unlocked + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(reenterHook), + IHooks.beforeAddLiquidity.selector, + abi.encodeWithSelector(IPositionManager.PoolManagerMustBeLocked.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + lpm.modifyLiquidities(actions, _deadline); + } + + function test_subscribe_reverts_PoolManagerMustBeLocked() public { + uint256 tokenId = lpm.nextTokenId(); + mint(reenterConfig, 10e18, address(this), ZERO_BYTES); + + bytes memory hookData = abi.encode(lpm.subscribe.selector, address(this), tokenId); + bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData); + + // approve hook as it should not revert because it does not have permissions + lpm.approve(address(reenterHook), tokenId); + + // should revert since the pool manager is unlocked + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(reenterHook), + IHooks.beforeAddLiquidity.selector, + abi.encodeWithSelector(IPositionManager.PoolManagerMustBeLocked.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + lpm.modifyLiquidities(actions, _deadline); + } + + function test_transferFrom_reverts_PoolManagerMustBeLocked() public { + uint256 tokenId = lpm.nextTokenId(); + mint(reenterConfig, 10e18, address(this), ZERO_BYTES); + + bytes memory hookData = abi.encode(lpm.transferFrom.selector, address(this), tokenId); + bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData); + + // approve hook as it should not revert because it does not have permissions + lpm.approve(address(reenterHook), tokenId); + + // should revert since the pool manager is unlocked + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(reenterHook), + IHooks.beforeAddLiquidity.selector, + abi.encodeWithSelector(IPositionManager.PoolManagerMustBeLocked.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + lpm.modifyLiquidities(actions, _deadline); } } diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index 6daf517d6..7db64e9b9 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -24,8 +24,8 @@ import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; -import {SlippageCheckLibrary} from "../../src/libraries/SlippageCheck.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; +import {SlippageCheck} from "../../src/libraries/SlippageCheck.sol"; import {BaseActionsRouter} from "../../src/base/BaseActionsRouter.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; @@ -52,7 +52,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // This is needed to receive return deltas from modifyLiquidity calls. deployPosmHookSavesDelta(); - (key, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1); // Requires currency0 and currency1 to be set in base Deployers contract. deployAndApprovePosm(manager); @@ -61,6 +61,16 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { approvePosmFor(alice); } + function test_modifyLiquidities_reverts_deadlinePassed() public { + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: 0, tickUpper: 60}); + bytes memory calls = getMintEncoded(config, 1e18, ActionConstants.MSG_SENDER, ""); + + uint256 deadline = vm.getBlockTimestamp() - 1; + + vm.expectRevert(abi.encodeWithSelector(IPositionManager.DeadlinePassed.selector, deadline)); + lpm.modifyLiquidities(calls, deadline); + } + function test_modifyLiquidities_reverts_mismatchedLengths() public { Plan memory planner = Planner.init(); planner.add(Actions.MINT_POSITION, abi.encode("test")); @@ -82,10 +92,11 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // Set up approvals for the reentrant token approvePosmCurrency(reentrantToken); - (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); // Try to add liquidity at that range, but the token reenters posm - PositionConfig memory config = PositionConfig({poolKey: key, tickLower: 0, tickUpper: 60}); + PositionConfig memory config = + PositionConfig({poolKey: key, tickLower: -int24(key.tickSpacing), tickUpper: int24(key.tickSpacing)}); bytes memory calls = getMintEncoded(config, 1e18, ActionConstants.MSG_SENDER, ""); // Permit2.transferFrom does not bubble the ContractLocked error and instead reverts with its own error @@ -115,7 +126,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(lpm.nextTokenId(), 2); assertEq(lpm.ownerOf(tokenId), address(this)); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0())), "incorrect amount0"); @@ -229,7 +240,16 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + liquidityToAdd, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + address(this), + ZERO_BYTES + ) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency0, type(uint256).max)); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency1, type(uint256).max)); @@ -248,18 +268,33 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_mint_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); + uint256 liquidity = 1e18; + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + uint128(liquidity) + ); + bytes memory calls = - getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); + getMintEncoded(config, liquidity, 1 wei, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES); + vm.expectRevert(abi.encodeWithSelector(SlippageCheck.MaximumAmountExceeded.selector, 1 wei, amount0 + 1)); lpm.modifyLiquidities(calls, _deadline); } function test_mint_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); + uint256 liquidity = 1e18; + (, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + uint128(liquidity) + ); bytes memory calls = - getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, ActionConstants.MSG_SENDER, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); + getMintEncoded(config, liquidity, MAX_SLIPPAGE_INCREASE, 1 wei, ActionConstants.MSG_SENDER, ZERO_BYTES); + vm.expectRevert(abi.encodeWithSelector(SlippageCheck.MaximumAmountExceeded.selector, 1 wei, amount1 + 1)); lpm.modifyLiquidities(calls, _deadline); } @@ -304,7 +339,9 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swap to move the price and cause a slippage revert swap(key, true, -1e18, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); + vm.expectRevert( + abi.encodeWithSelector(SlippageCheck.MaximumAmountExceeded.selector, slippage, 1199947202932782783) + ); lpm.modifyLiquidities(calls, _deadline); } @@ -320,7 +357,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(tokenId, 1); assertEq(lpm.ownerOf(1), address(this)); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); @@ -335,14 +372,14 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { burn(tokenId, config, ZERO_BYTES); assertEq(numDeltas, hook.numberDeltasReturned()); - liquidity = lpm.getPositionLiquidity(tokenId, config); + liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); assertEq(currency0.balanceOfSelf(), balance0BeforeBurn + uint256(int256(deltaDecrease.amount0()))); assertEq(currency1.balanceOfSelf(), balance1BeforeBurn + uint256(uint128(deltaDecrease.amount1()))); - // OZ 721 will revert if the token does not exist + // 721 will revert if the token does not exist vm.expectRevert(); lpm.ownerOf(1); @@ -364,7 +401,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(tokenId, 1); assertEq(lpm.ownerOf(1), address(this)); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta)); @@ -386,7 +423,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(uint256(int256(deltaBurn.amount0())), amount0); assertEq(uint256(int256(deltaBurn.amount1())), amount1); - liquidity = lpm.getPositionLiquidity(tokenId, config); + liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, 0); @@ -408,10 +445,12 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); + uint128 amount0 = uint128(-delta.amount0()); - bytes memory calls = - getBurnEncoded(tokenId, config, uint128(-delta.amount0()) + 1 wei, MIN_SLIPPAGE_DECREASE, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MinimumAmountInsufficient.selector); + bytes memory calls = getBurnEncoded(tokenId, config, amount0 + 1 wei, MIN_SLIPPAGE_DECREASE, ZERO_BYTES); + vm.expectRevert( + abi.encodeWithSelector(SlippageCheck.MinimumAmountInsufficient.selector, amount0 + 1, amount0 - 1) + ); lpm.modifyLiquidities(calls, _deadline); } @@ -420,10 +459,14 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); + uint128 amount1 = uint128(-delta.amount1()); - bytes memory calls = - getBurnEncoded(tokenId, config, MIN_SLIPPAGE_DECREASE, uint128(-delta.amount1()) + 1 wei, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MinimumAmountInsufficient.selector); + bytes memory calls = getBurnEncoded(tokenId, config, MIN_SLIPPAGE_DECREASE, amount1 + 1 wei, ZERO_BYTES); + + // reverts on amount1, because the swap sent token0 into the pool and took token1 + vm.expectRevert( + abi.encodeWithSelector(SlippageCheck.MinimumAmountInsufficient.selector, amount1 + 1, amount1 - 1) + ); lpm.modifyLiquidities(calls, _deadline); } @@ -450,15 +493,15 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); + uint128 amount1 = uint128(-delta.amount1()); - bytes memory calls = getBurnEncoded( - tokenId, config, uint128(-delta.amount0()) - 1 wei, uint128(-delta.amount1()) - 1 wei, ZERO_BYTES - ); + bytes memory calls = + getBurnEncoded(tokenId, config, uint128(-delta.amount0()) - 1 wei, amount1 - 1 wei, ZERO_BYTES); // swap to move the price and cause a slippage revert swap(key, true, -1e18, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MinimumAmountInsufficient.selector); + vm.expectRevert(abi.encodeWithSelector(SlippageCheck.MinimumAmountInsufficient.selector, amount1 - 1, 0)); lpm.modifyLiquidities(calls, _deadline); } @@ -478,7 +521,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); assertEq(currency0.balanceOfSelf(), balance0Before + uint256(uint128(delta.amount0()))); @@ -494,9 +537,6 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { (tokenId, params) = addFuzzyLiquidity(lpm, address(this), key, params, SQRT_PRICE_1_1, ZERO_BYTES); decreaseLiquidityDelta = uint256(bound(int256(decreaseLiquidityDelta), 0, params.liquidityDelta)); - PositionConfig memory config = - PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); - uint256 balance0Before = currency0.balanceOfSelf(); uint256 balance1Before = currency1.balanceOfSelf(); @@ -504,9 +544,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.DECREASE_LIQUIDITY, - abi.encode( - tokenId, config, decreaseLiquidityDelta, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES - ) + abi.encode(tokenId, decreaseLiquidityDelta, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency0, type(uint256).max)); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency1, type(uint256).max)); @@ -514,7 +552,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities(calls, _deadline); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); // did not recieve tokens, as they were forfeited with CLEAR @@ -548,7 +586,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, liquidityToRemove, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, liquidityToRemove, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency0, amount0 - 1 wei)); planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency1, amount1 - 1 wei)); @@ -589,7 +627,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); @@ -610,11 +648,13 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); + uint128 amount0Delta = uint128(-delta.amount0()); - bytes memory calls = getDecreaseEncoded( - tokenId, config, 1e18, uint128(-delta.amount0()) + 1 wei, MIN_SLIPPAGE_DECREASE, ZERO_BYTES + bytes memory calls = + getDecreaseEncoded(tokenId, config, 1e18, amount0Delta + 1, MIN_SLIPPAGE_DECREASE, ZERO_BYTES); + vm.expectRevert( + abi.encodeWithSelector(SlippageCheck.MinimumAmountInsufficient.selector, amount0Delta + 1, amount0Delta - 1) ); - vm.expectRevert(SlippageCheckLibrary.MinimumAmountInsufficient.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -623,11 +663,13 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); + uint128 amount1Delta = uint128(-delta.amount0()); - bytes memory calls = getDecreaseEncoded( - tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, uint128(-delta.amount1()) + 1 wei, ZERO_BYTES + bytes memory calls = + getDecreaseEncoded(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, amount1Delta + 1 wei, ZERO_BYTES); + vm.expectRevert( + abi.encodeWithSelector(SlippageCheck.MinimumAmountInsufficient.selector, amount1Delta + 1, amount1Delta - 1) ); - vm.expectRevert(SlippageCheckLibrary.MinimumAmountInsufficient.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -655,15 +697,16 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); + uint128 amount1 = uint128(-delta.amount1()); - bytes memory calls = getDecreaseEncoded( - tokenId, config, 1e18, uint128(-delta.amount0()) - 1 wei, uint128(-delta.amount1()) - 1 wei, ZERO_BYTES - ); + bytes memory calls = + getDecreaseEncoded(tokenId, config, 1e18, uint128(-delta.amount0()) - 1 wei, amount1 - 1 wei, ZERO_BYTES); // swap to move the price and cause a slippage revert swap(key, true, -1e18, ZERO_BYTES); - vm.expectRevert(SlippageCheckLibrary.MinimumAmountInsufficient.selector); + // reverts on amount1, because the swap sent token0 into the pool and took token1 + vm.expectRevert(abi.encodeWithSelector(SlippageCheck.MinimumAmountInsufficient.selector, amount1 - 1, 0)); lpm.modifyLiquidities(calls, _deadline); } @@ -690,7 +733,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); BalanceDelta delta = getLastDelta(); - uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + uint256 liquidity = lpm.getPositionLiquidity(tokenId); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); @@ -775,7 +818,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { vm.stopPrank(); // position liquidity increased - uint256 newLiq = lpm.getPositionLiquidity(tokenId, config); + uint256 newLiq = lpm.getPositionLiquidity(tokenId); assertEq(newLiq, liquidity + liquidityToAdd); // alice paid the tokens @@ -817,7 +860,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { { // position liquidity decreased - uint256 newLiq = lpm.getPositionLiquidity(tokenId, config); + uint256 newLiq = lpm.getPositionLiquidity(tokenId); assertEq(newLiq, liquidity - liquidityToRemove); } @@ -838,7 +881,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_initialize() public { // initialize a new pool and add liquidity key = PoolKey({currency0: currency0, currency1: currency1, fee: 0, tickSpacing: 10, hooks: IHooks(address(0))}); - lpm.initializePool(key, SQRT_PRICE_1_1, ZERO_BYTES); + lpm.initializePool(key, SQRT_PRICE_1_1); (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) = manager.getSlot0(key.toId()); assertEq(sqrtPriceX96, SQRT_PRICE_1_1); @@ -853,7 +896,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { fee = uint24(bound(fee, 0, LPFeeLibrary.MAX_LP_FEE)); key = PoolKey({currency0: currency0, currency1: currency1, fee: fee, tickSpacing: 10, hooks: IHooks(address(0))}); - lpm.initializePool(key, sqrtPrice, ZERO_BYTES); + lpm.initializePool(key, sqrtPrice); (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) = manager.getSlot0(key.toId()); assertEq(sqrtPriceX96, sqrtPrice); @@ -877,7 +920,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory plan = Planner.init(); plan.add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, ActionConstants.MSG_SENDER); @@ -909,12 +952,19 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory plan = Planner.init(); plan.add( Actions.DECREASE_LIQUIDITY, - abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + abi.encode(tokenId, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); plan.add( Actions.MINT_POSITION, abi.encode( - configMint, 1e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + configMint.poolKey, + configMint.tickLower, + configMint.tickUpper, + 1e18, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); plan.add(Actions.TAKE, abi.encode(key.currency0, ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA)); diff --git a/test/router/Payments.gas.t.sol b/test/router/Payments.gas.t.sol index 9717fdad0..6ee98bffc 100644 --- a/test/router/Payments.gas.t.sol +++ b/test/router/Payments.gas.t.sol @@ -22,7 +22,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { function test_gas_swap_settleFromCaller_takeAllToSpecifiedAddress() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, MAX_SETTLE_AMOUNT)); @@ -36,10 +36,11 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { function test_gas_swap_settleFromCaller_takeAllToMsgSender() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, amountIn, true)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, 0)); bytes memory data = plan.encode(); router.executeActions(data); @@ -49,7 +50,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { function test_gas_swap_settleWithBalance_takeAllToSpecifiedAddress() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); // seed the router with tokens key0.currency0.transfer(address(router), amountIn); @@ -66,7 +67,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { function test_gas_swap_settleWithBalance_takeAllToMsgSender() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); // seed the router with tokens key0.currency0.transfer(address(router), amountIn); diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index 3aa077efe..cb50c1728 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -22,46 +22,17 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = Planner.init(); } - function test_settleTakePair() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); - - plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); - - uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); - uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); - // router is empty before - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - - bytes memory data = plan.encode(); - router.executeActions(data); - - uint256 inputBalanceAfter = key0.currency0.balanceOfSelf(); - uint256 outputBalanceAfter = key0.currency1.balanceOfSelf(); - - // router is empty - assertEq(currency0.balanceOf(address(router)), 0); - assertEq(currency1.balanceOf(address(router)), 0); - // caller's balance changed by input and output amounts - assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); - assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); - } - function test_exactIn_settleAll_revertsSlippage() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, amountIn - 1)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, MIN_TAKE_AMOUNT)); bytes memory data = plan.encode(); - vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + vm.expectRevert(abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, amountIn - 1, amountIn)); router.executeActions(data); } @@ -69,14 +40,16 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, MAX_SETTLE_AMOUNT)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, expectedAmountOut + 1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, expectedAmountOut + 1)); bytes memory data = plan.encode(); - vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, expectedAmountOut + 1, expectedAmountOut) + ); router.executeActions(data); } @@ -85,14 +58,16 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountIn - 1)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, MIN_TAKE_AMOUNT)); bytes memory data = plan.encode(); - vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn) + ); router.executeActions(data); } @@ -101,14 +76,14 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, MAX_SETTLE_AMOUNT)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, amountOut + 1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, amountOut + 1)); bytes memory data = plan.encode(); - vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + vm.expectRevert(abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, amountOut + 1, amountOut)); router.executeActions(data); } @@ -117,14 +92,13 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountIn)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, amountOut)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, amountOut)); bytes memory data = plan.encode(); - vm.expectRevert(IV4Router.V4TooLittleReceived.selector); router.executeActions(data); } @@ -132,7 +106,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); // seed the router with tokens key0.currency0.transfer(address(router), amountIn); @@ -166,12 +140,13 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, amountIn, true)); // take 15 bips to Bob plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, 15)); - plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, 0)); uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); @@ -202,12 +177,13 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { function test_settle_takePortion_reverts() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, amountIn, true)); // bips is larger than maximum bips plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, BipsLibrary.BPS_DENOMINATOR + 1)); - plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, 0)); bytes memory data = plan.encode(); diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index 1a63d9fa4..c49f96dcf 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -31,7 +31,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); @@ -107,7 +107,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, ActionConstants.MSG_SENDER); @@ -120,7 +120,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, ActionConstants.MSG_SENDER); @@ -132,12 +132,12 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { function test_gas_nativeIn_swapExactIn_1Hop() public { uint256 amountIn = 1 ether; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.ADDRESS_ZERO, currency0, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn1Hop_nativeIn"); @@ -147,11 +147,11 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountIn = 1 ether; tokenPath.push(currency0); - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_nativeOut"); @@ -160,13 +160,13 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { function test_gas_nativeIn_swapExactIn_2Hops() public { uint256 amountIn = 1 ether; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); tokenPath.push(currency1); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn2Hops_nativeIn"); @@ -175,14 +175,14 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { function test_gas_nativeIn_swapExactIn_3Hops() public { uint256 amountIn = 1 ether; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); tokenPath.push(currency1); tokenPath.push(currency2); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.ADDRESS_ZERO, currency2, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn3Hops_nativeIn"); @@ -196,7 +196,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), type(uint128).max, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), type(uint128).max, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); @@ -272,7 +272,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), type(uint128).max, 0, bytes("")); + IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), type(uint128).max, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, ActionConstants.MSG_SENDER); @@ -285,7 +285,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), type(uint128).max, 0, bytes("")); + IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), type(uint128).max, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, ActionConstants.MSG_SENDER); @@ -297,12 +297,12 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { function test_gas_nativeIn_swapExactOut_1Hop_sweepExcessETH() public { uint256 amountOut = 1 ether; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.ADDRESS_ZERO, currency0, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut1Hop_nativeIn_sweepETH"); @@ -312,11 +312,11 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; tokenPath.push(currency0); - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_nativeOut"); @@ -325,13 +325,13 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { function test_gas_nativeIn_swapExactOut_2Hops_sweepExcessETH() public { uint256 amountOut = 1 ether; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); tokenPath.push(currency1); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut2Hops_nativeIn"); @@ -340,14 +340,14 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { function test_gas_nativeIn_swapExactOut_3Hops_sweepExcessETH() public { uint256 amountOut = 1 ether; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); tokenPath.push(currency1); tokenPath.push(currency2); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.ADDRESS_ZERO, currency2, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut3Hops_nativeIn"); @@ -359,12 +359,12 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { tokenPath.push(currency2); tokenPath.push(currency1); tokenPath.push(currency0); - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops_nativeOut"); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index f6d63805e..0c74e0046 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -28,14 +28,15 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; // min amount out of 1 higher than the actual amount out - IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams( - key0, true, uint128(amountIn), uint128(expectedAmountOut + 1), 0, bytes("") - ); + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), uint128(expectedAmountOut + 1), bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, expectedAmountOut + 1, expectedAmountOut) + ); router.executeActions(data); } @@ -44,7 +45,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) @@ -62,7 +63,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); @@ -90,7 +91,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); @@ -118,7 +119,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountOut * 12 / 10)); @@ -151,7 +152,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) @@ -176,7 +177,9 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, 992054607780215625 + 1, 992054607780215625) + ); router.executeActions(data); } @@ -278,7 +281,7 @@ contract V4RouterTest is RoutingTestHelpers { // amount in of 0 to show it should use the open delta IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, ActionConstants.OPEN_DELTA, 0, bytes("")); plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); @@ -310,7 +313,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); @@ -330,7 +333,7 @@ contract V4RouterTest is RoutingTestHelpers { // native output means we need !zeroForOne IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); @@ -348,13 +351,13 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(nativeKey.currency1); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(CurrencyLibrary.NATIVE, nativeKey.currency1, amountIn); + = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, amountIn); assertEq(nativeKey.currency0.balanceOf(address(router)), 0); assertEq(nativeKey.currency1.balanceOf(address(router)), 0); @@ -368,13 +371,13 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 992054607780215625; tokenPath.push(nativeKey.currency1); - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.NATIVE, amountIn); + = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn); assertEq(nativeKey.currency0.balanceOf(address(router)), 0); assertEq(nativeKey.currency1.balanceOf(address(router)), 0); @@ -388,7 +391,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountOut = 984211133872795298; // the initialized nativeKey is (native, currency0) - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); tokenPath.push(currency1); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); @@ -398,7 +401,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(CurrencyLibrary.NATIVE, currency1, amountIn); + = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, amountIn); // check intermediate token balances assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); @@ -418,7 +421,7 @@ contract V4RouterTest is RoutingTestHelpers { // the initialized nativeKey is (native, currency0) tokenPath.push(currency1); tokenPath.push(currency0); - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); @@ -426,7 +429,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 intermediateBalanceBefore = currency0.balanceOfSelf(); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(currency1, CurrencyLibrary.NATIVE, amountIn); + = _finalizeAndExecuteSwap(currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn); // check intermediate token balances assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); @@ -446,7 +449,7 @@ contract V4RouterTest is RoutingTestHelpers { // amount in of 0 to show it should use the open delta IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(nativeKey, true, ActionConstants.OPEN_DELTA, 0, bytes("")); plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); @@ -477,14 +480,15 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, true, uint128(amountOut), uint128(expectedAmountIn - 1), 0, bytes("") - ); + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn - 1), bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn) + ); router.executeActions(data); } @@ -492,9 +496,8 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") - ); + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn + 1), bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -512,9 +515,8 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - key0, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") - ); + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, false, uint128(amountOut), uint128(expectedAmountIn + 1), bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -528,6 +530,35 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); } + function test_swapExactOutputSingle_swapOpenDelta() public { + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, true, ActionConstants.OPEN_DELTA, uint128(expectedAmountIn + 1), bytes("") + ); + + plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, 1 ether)); + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.OPEN_DELTA, true)); + + bytes memory data = plan.encode(); + + uint256 callerInputBefore = key0.currency0.balanceOfSelf(); + uint256 routerInputBefore = key0.currency1.balanceOfSelf(); + uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); + + router.executeActions(data); + + uint256 callerInputAfter = key0.currency0.balanceOfSelf(); + uint256 routerInputAfter = key0.currency1.balanceOfSelf(); + uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); + + // caller paid + assertEq(callerInputBefore - expectedAmountIn, callerInputAfter); + assertEq(routerInputBefore, routerInputAfter); + assertEq(callerOutputBefore, callerOutputAfter); + } + function test_swapExactOut_revertsForAmountIn() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -540,7 +571,9 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + vm.expectRevert( + abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn) + ); router.executeActions(data); } @@ -633,6 +666,36 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); } + function test_swapExactOut_swapOpenDelta() public { + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(currency0); + tokenPath.push(currency1); + + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, ActionConstants.OPEN_DELTA); + + plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, 1 ether)); + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.OPEN_DELTA, true)); + + bytes memory data = plan.encode(); + + uint256 callerInputBefore = key0.currency0.balanceOfSelf(); + uint256 routerInputBefore = key0.currency1.balanceOfSelf(); + uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); + + router.executeActions(data); + + uint256 callerInputAfter = key0.currency0.balanceOfSelf(); + uint256 routerInputAfter = key0.currency1.balanceOfSelf(); + uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); + + // caller paid + assertEq(callerInputBefore - expectedAmountIn, callerInputAfter); + assertEq(routerInputBefore, routerInputAfter); + assertEq(callerOutputBefore, callerOutputAfter); + } + /*////////////////////////////////////////////////////////////// ETH -> ERC20 and ERC20 -> ETH EXACT OUTPUT //////////////////////////////////////////////////////////////*/ @@ -642,7 +705,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - nativeKey, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + nativeKey, true, uint128(amountOut), uint128(expectedAmountIn + 1), bytes("") ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -662,7 +725,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( - nativeKey, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + nativeKey, false, uint128(amountOut), uint128(expectedAmountIn + 1), bytes("") ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -681,14 +744,16 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(nativeKey.currency1); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteNativeInputExactOutputSwap(CurrencyLibrary.NATIVE, nativeKey.currency1, expectedAmountIn); + = _finalizeAndExecuteNativeInputExactOutputSwap( + CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, expectedAmountIn + ); assertEq(nativeKey.currency0.balanceOf(address(router)), 0); assertEq(nativeKey.currency1.balanceOf(address(router)), 0); @@ -702,13 +767,13 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountIn = 1008049273448486163; tokenPath.push(nativeKey.currency1); - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.NATIVE, expectedAmountIn); + = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, expectedAmountIn); assertEq(nativeKey.currency0.balanceOf(address(router)), 0); assertEq(nativeKey.currency1.balanceOf(address(router)), 0); @@ -722,7 +787,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 expectedAmountIn = 1016204441757464409; // the initialized nativeKey is (native, currency0) - tokenPath.push(CurrencyLibrary.NATIVE); + tokenPath.push(CurrencyLibrary.ADDRESS_ZERO); tokenPath.push(currency0); tokenPath.push(currency1); IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); @@ -732,12 +797,12 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteNativeInputExactOutputSwap(CurrencyLibrary.NATIVE, currency1, expectedAmountIn); + = _finalizeAndExecuteNativeInputExactOutputSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, expectedAmountIn); assertEq(intermediateBalanceBefore, currency0.balanceOfSelf()); assertEq(currency1.balanceOf(address(router)), 0); assertEq(currency0.balanceOf(address(router)), 0); - assertEq(CurrencyLibrary.NATIVE.balanceOf(address(router)), 0); + assertEq(CurrencyLibrary.ADDRESS_ZERO.balanceOf(address(router)), 0); assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn); assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); diff --git a/test/script/DeployPoolManager.t.sol b/test/script/DeployPoolManager.t.sol new file mode 100644 index 000000000..19159c266 --- /dev/null +++ b/test/script/DeployPoolManager.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {DeployPoolManager} from "../../script/01_PoolManager.s.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Test} from "forge-std/Test.sol"; + +contract DeployPoolManagerTest is Test { + DeployPoolManager deployer; + + function setUp() public { + deployer = new DeployPoolManager(); + } + + function test_run_poolManager() public { + IPoolManager manager = deployer.run(); + // Foundry sets a default sender in scripts. + address defaultSender = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; + // Deployer is the owner. + assertEq(_getOwner(manager), defaultSender); + } + + function _getOwner(IPoolManager manager) public view returns (address owner) { + // owner is at slot 0 + owner = address(uint160(uint256(manager.extsload(0)))); + } +} diff --git a/test/script/DeployPoolMofifyLiquidityTest.t.sol b/test/script/DeployPoolMofifyLiquidityTest.t.sol new file mode 100644 index 000000000..6da02e012 --- /dev/null +++ b/test/script/DeployPoolMofifyLiquidityTest.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {DeployPoolModifyLiquidityTest} from "../../script/02_PoolModifyLiquidityTest.s.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {Test} from "forge-std/Test.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; + +// Test of a Test contract (lol) +contract DeployPoolModifyLiquidityTestTest is Test { + DeployPoolModifyLiquidityTest deployer; + + IPoolManager manager; + + function setUp() public { + manager = new PoolManager(address(this)); + deployer = new DeployPoolModifyLiquidityTest(); + } + + function test_run_modifyLiquidityRouter() public { + PoolModifyLiquidityTest testModifyLiquidityRouter = deployer.run(address(manager)); + + assertEq(address(testModifyLiquidityRouter.manager()), address(manager)); + } +} diff --git a/test/script/DeployPoolSwapTest.t.sol b/test/script/DeployPoolSwapTest.t.sol new file mode 100644 index 000000000..de9ca350b --- /dev/null +++ b/test/script/DeployPoolSwapTest.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {DeployPoolSwapTest} from "../../script/03_PoolSwapTest.s.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {Test} from "forge-std/Test.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; + +// Test of a Test contract (lol) +contract DeployPoolSwapTestTest is Test { + DeployPoolSwapTest deployer; + + IPoolManager manager; + + function setUp() public { + manager = new PoolManager(address(this)); + deployer = new DeployPoolSwapTest(); + } + + function test_run_testSwapRouter() public { + PoolSwapTest testSwapRouter = deployer.run(address(manager)); + + assertEq(address(testSwapRouter.manager()), address(manager)); + } +} diff --git a/test/shared/FeeMath.sol b/test/shared/FeeMath.sol index 25bdba5f4..bb593eeb5 100644 --- a/test/shared/FeeMath.sol +++ b/test/shared/FeeMath.sol @@ -13,7 +13,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {PositionManager} from "../../src/PositionManager.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; library FeeMath { using SafeCast for uint256; @@ -35,11 +35,11 @@ library FeeMath { (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) = manager.getPositionInfo(poolId, address(posm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 feeGrowthInside0X218, uint256 feeGrowthInside1X128) = + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = manager.getFeeGrowthInside(poolId, config.tickLower, config.tickUpper); feesOwed = getFeesOwed( - feeGrowthInside0X218, feeGrowthInside1X128, feeGrowthInside0LastX128, feeGrowthInside1LastX128, liquidity + feeGrowthInside0X128, feeGrowthInside1X128, feeGrowthInside0LastX128, feeGrowthInside1LastX128, liquidity ); } diff --git a/test/shared/HookModifyLiquidities.sol b/test/shared/HookModifyLiquidities.sol new file mode 100644 index 000000000..2e9b5ecf5 --- /dev/null +++ b/test/shared/HookModifyLiquidities.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; + +import {HookSavesDelta} from "./HookSavesDelta.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; + +/// @notice This contract is NOT a production use contract. It is meant to be used in testing to verify that external contracts can modify liquidity without a lock (IPositionManager.modifyLiquiditiesWithoutUnlock) +/// @dev a hook that can modify liquidity in beforeSwap +contract HookModifyLiquidities is HookSavesDelta { + IPositionManager posm; + IAllowanceTransfer permit2; + + function setAddresses(IPositionManager _posm, IAllowanceTransfer _permit2) external { + posm = _posm; + permit2 = _permit2; + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata key, /* key **/ + IPoolManager.SwapParams calldata, /* params **/ + bytes calldata hookData + ) external override returns (bytes4, BeforeSwapDelta, uint24) { + approvePosmCurrency(key.currency0); + approvePosmCurrency(key.currency1); + + (bytes memory actions, bytes[] memory params) = abi.decode(hookData, (bytes, bytes[])); + posm.modifyLiquiditiesWithoutUnlock(actions, params); + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function beforeAddLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + bytes calldata hookData + ) external override returns (bytes4) { + if (hookData.length > 0) { + (bytes memory actions, bytes[] memory params) = abi.decode(hookData, (bytes, bytes[])); + posm.modifyLiquiditiesWithoutUnlock(actions, params); + } + return this.beforeAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + bytes calldata hookData + ) external override returns (bytes4) { + if (hookData.length > 0) { + (bytes memory actions, bytes[] memory params) = abi.decode(hookData, (bytes, bytes[])); + posm.modifyLiquiditiesWithoutUnlock(actions, params); + } + return this.beforeRemoveLiquidity.selector; + } + + function approvePosmCurrency(Currency currency) internal { + // Because POSM uses permit2, we must execute 2 permits/approvals. + // 1. First, the caller must approve permit2 on the token. + IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max); + // 2. Then, the caller must approve POSM as a spender of permit2. TODO: This could also be a signature. + permit2.approve(Currency.unwrap(currency), address(posm), type(uint160).max, type(uint48).max); + } +} diff --git a/test/shared/HookSavesDelta.sol b/test/shared/HookSavesDelta.sol index 8ff86ac1f..fa11a90b9 100644 --- a/test/shared/HookSavesDelta.sol +++ b/test/shared/HookSavesDelta.sol @@ -16,6 +16,7 @@ contract HookSavesDelta is BaseTestHooks { PoolKey calldata, /* key **/ IPoolManager.ModifyLiquidityParams calldata, /* params **/ BalanceDelta delta, + BalanceDelta, /* feesAccrued **/ bytes calldata /* hookData **/ ) external override returns (bytes4, BalanceDelta) { _storeDelta(delta); @@ -27,6 +28,7 @@ contract HookSavesDelta is BaseTestHooks { PoolKey calldata, /* key **/ IPoolManager.ModifyLiquidityParams calldata, /* params **/ BalanceDelta delta, + BalanceDelta, /* feesAccrued */ bytes calldata /* hookData **/ ) external override returns (bytes4, BalanceDelta) { _storeDelta(delta); diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol index 026abe8c2..80d2d81e7 100644 --- a/test/shared/LiquidityOperations.sol +++ b/test/shared/LiquidityOperations.sol @@ -10,7 +10,7 @@ import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {PositionManager, Actions} from "../../src/PositionManager.sol"; -import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfig} from "./PositionConfig.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {HookSavesDelta} from "./HookSavesDelta.sol"; @@ -81,7 +81,7 @@ abstract contract LiquidityOperations is CommonBase { lpm.modifyLiquidities(calls, _deadline); } - // Helper functions for getting encoded calldata for .modifyLiquidities + // Helper functions for getting encoded calldata for .modifyLiquidities() or .modifyLiquiditiesWithoutUnlock() function getMintEncoded(PositionConfig memory config, uint256 liquidity, address recipient, bytes memory hookData) internal pure @@ -99,7 +99,19 @@ abstract contract LiquidityOperations is CommonBase { bytes memory hookData ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); - planner.add(Actions.MINT_POSITION, abi.encode(config, liquidity, amount0Max, amount1Max, recipient, hookData)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + config.poolKey, + config.tickLower, + config.tickUpper, + liquidity, + amount0Max, + amount1Max, + recipient, + hookData + ) + ); return planner.finalizeModifyLiquidityWithClose(config.poolKey); } @@ -124,9 +136,7 @@ abstract contract LiquidityOperations is CommonBase { bytes memory hookData ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); - planner.add( - Actions.INCREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToAdd, amount0Max, amount1Max, hookData) - ); + planner.add(Actions.INCREASE_LIQUIDITY, abi.encode(tokenId, liquidityToAdd, amount0Max, amount1Max, hookData)); return planner.finalizeModifyLiquidityWithClose(config.poolKey); } @@ -151,7 +161,7 @@ abstract contract LiquidityOperations is CommonBase { ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); planner.add( - Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToRemove, amount0Min, amount1Min, hookData) + Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, liquidityToRemove, amount0Min, amount1Min, hookData) ); return planner.finalizeModifyLiquidityWithClose(config.poolKey); } @@ -172,7 +182,7 @@ abstract contract LiquidityOperations is CommonBase { bytes memory hookData ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); - planner.add(Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, 0, amount0Min, amount1Min, hookData)); + planner.add(Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, 0, amount0Min, amount1Min, hookData)); return planner.finalizeModifyLiquidityWithClose(config.poolKey); } @@ -192,7 +202,7 @@ abstract contract LiquidityOperations is CommonBase { bytes memory hookData ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); - planner.add(Actions.BURN_POSITION, abi.encode(tokenId, config, amount0Min, amount1Min, hookData)); + planner.add(Actions.BURN_POSITION, abi.encode(tokenId, amount0Min, amount1Min, hookData)); // Close needed on burn in case there is liquidity left in the position. return planner.finalizeModifyLiquidityWithClose(config.poolKey); } diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 979f12151..0d5ca4fc1 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -86,7 +86,9 @@ library Planner { returns (bytes memory) { if (takeRecipient == ActionConstants.MSG_SENDER) { - plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(inputCurrency, outputCurrency)); + // blindly settling and taking all, without slippage checks, isnt recommended in prod + plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency, type(uint256).max)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, 0)); } else { plan = plan.add(Actions.SETTLE, abi.encode(inputCurrency, ActionConstants.OPEN_DELTA, true)); plan = plan.add(Actions.TAKE, abi.encode(outputCurrency, takeRecipient, ActionConstants.OPEN_DELTA)); diff --git a/test/shared/PositionConfig.sol b/test/shared/PositionConfig.sol new file mode 100644 index 000000000..1d0a5a5ad --- /dev/null +++ b/test/shared/PositionConfig.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +// A helper struct used for tests +struct PositionConfig { + PoolKey poolKey; + int24 tickLower; + int24 tickUpper; +} diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index 3ae10590c..bce86b97b 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -14,15 +14,35 @@ import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; import {HookSavesDelta} from "./HookSavesDelta.sol"; -import {ERC721PermitHashLibrary} from "../../src/libraries/ERC721PermitHash.sol"; +import {HookModifyLiquidities} from "./HookModifyLiquidities.sol"; +import {PositionDescriptor} from "../../src/PositionDescriptor.sol"; +import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; +import {IWETH9} from "../../src/interfaces/external/IWETH9.sol"; +import {WETH} from "solmate/src/tokens/WETH.sol"; +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; +import {SortTokens} from "@uniswap/v4-core/test/utils/SortTokens.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { uint256 constant STARTING_USER_BALANCE = 10_000_000 ether; IAllowanceTransfer permit2; + PositionDescriptor public positionDescriptor; HookSavesDelta hook; address hookAddr = address(uint160(Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG)); + IWETH9 public _WETH9 = IWETH9(address(new WETH())); + + HookModifyLiquidities hookModifyLiquidities; + address hookModifyLiquiditiesAddr = address( + uint160( + Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG + ) + ); + + PoolKey wethKey; function deployPosmHookSavesDelta() public { HookSavesDelta impl = new HookSavesDelta(); @@ -30,6 +50,16 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { hook = HookSavesDelta(hookAddr); } + /// @dev deploys a special test hook where beforeSwap hookData is used to modify liquidity + function deployPosmHookModifyLiquidities() public { + HookModifyLiquidities impl = new HookModifyLiquidities(); + vm.etch(hookModifyLiquiditiesAddr, address(impl).code); + hookModifyLiquidities = HookModifyLiquidities(hookModifyLiquiditiesAddr); + + // set posm address since constructor args are not easily copied by vm.etch + hookModifyLiquidities.setAddresses(lpm, permit2); + } + function deployAndApprovePosm(IPoolManager poolManager) public { deployPosm(poolManager); approvePosm(); @@ -38,7 +68,8 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { function deployPosm(IPoolManager poolManager) internal { // We use deployPermit2() to prevent having to use via-ir in this repository. permit2 = IAllowanceTransfer(deployPermit2()); - lpm = new PositionManager(poolManager, permit2); + positionDescriptor = new PositionDescriptor(poolManager, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, "ETH"); + lpm = new PositionManager(poolManager, permit2, 100_000, positionDescriptor, _WETH9); } function seedBalance(address to) internal { @@ -66,6 +97,26 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { vm.stopPrank(); } + function seedWeth(address to) internal { + vm.deal(address(this), STARTING_USER_BALANCE); + _WETH9.deposit{value: STARTING_USER_BALANCE}(); + _WETH9.transfer(to, STARTING_USER_BALANCE); + } + + function seedToken(MockERC20 token, address to) internal { + token.mint(to, STARTING_USER_BALANCE); + } + + function initPoolUnsorted(Currency currencyA, Currency currencyB, IHooks hooks, uint24 fee, uint160 sqrtPriceX96) + internal + returns (PoolKey memory poolKey) + { + (Currency _currency0, Currency _currency1) = + SortTokens.sort(MockERC20(Currency.unwrap(currencyA)), MockERC20(Currency.unwrap(currencyB))); + + (poolKey,) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96); + } + function permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { bytes32 digest = getDigest(operator, tokenId, 1, block.timestamp + 1); @@ -85,7 +136,7 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { abi.encodePacked( "\x19\x01", lpm.DOMAIN_SEPARATOR(), - keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) ) ); } diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index e06cc3cc5..67b8f6011 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -76,7 +76,7 @@ contract RoutingTestHelpers is Test, Deployers { if (Currency.unwrap(currencyA) > Currency.unwrap(currencyB)) (currencyA, currencyB) = (currencyB, currencyA); _key = PoolKey(currencyA, currencyB, 3000, 60, IHooks(hookAddr)); - manager.initialize(_key, SQRT_PRICE_1_1, ZERO_BYTES); + manager.initialize(_key, SQRT_PRICE_1_1); MockERC20(Currency.unwrap(currencyA)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(currencyB)).approve(address(positionManager), type(uint256).max); positionManager.modifyLiquidity(_key, IPoolManager.ModifyLiquidityParams(-887220, 887220, 200 ether, 0), "0x"); @@ -86,9 +86,9 @@ contract RoutingTestHelpers is Test, Deployers { internal returns (PoolKey memory _key) { - _key = PoolKey(CurrencyLibrary.NATIVE, currency, 3000, 60, IHooks(hookAddr)); + _key = PoolKey(CurrencyLibrary.ADDRESS_ZERO, currency, 3000, 60, IHooks(hookAddr)); - manager.initialize(_key, SQRT_PRICE_1_1, ZERO_BYTES); + manager.initialize(_key, SQRT_PRICE_1_1); MockERC20(Currency.unwrap(currency)).approve(address(positionManager), type(uint256).max); positionManager.modifyLiquidity{value: 200 ether}( _key, IPoolManager.ModifyLiquidityParams(-887220, 887220, 200 ether, 0), "0x" @@ -146,7 +146,7 @@ contract RoutingTestHelpers is Test, Deployers { bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, takeRecipient); - uint256 value = (inputCurrency.isNative()) ? amountIn : 0; + uint256 value = (inputCurrency.isAddressZero()) ? amountIn : 0; // otherwise just execute as normal router.executeActions{value: value}(data); diff --git a/test/shared/fuzz/LiquidityFuzzers.sol b/test/shared/fuzz/LiquidityFuzzers.sol index 02d6583f4..62f3f87bf 100644 --- a/test/shared/fuzz/LiquidityFuzzers.sol +++ b/test/shared/fuzz/LiquidityFuzzers.sol @@ -9,12 +9,13 @@ import {Fuzzers} from "@uniswap/v4-core/src/test/Fuzzers.sol"; import {IPositionManager} from "../../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../../src/libraries/Actions.sol"; -import {PositionConfig} from "../../../src/libraries/PositionConfig.sol"; import {Planner, Plan} from "../../shared/Planner.sol"; contract LiquidityFuzzers is Fuzzers { using Planner for Plan; + uint128 constant _MAX_SLIPPAGE_INCREASE = type(uint128).max; + function addFuzzyLiquidity( IPositionManager lpm, address recipient, @@ -24,24 +25,23 @@ contract LiquidityFuzzers is Fuzzers { bytes memory hookData ) internal returns (uint256, IPoolManager.ModifyLiquidityParams memory) { params = Fuzzers.createFuzzyLiquidityParams(key, params, sqrtPriceX96); - PositionConfig memory config = - PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); - uint128 MAX_SLIPPAGE_INCREASE = type(uint128).max; Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, + key, + params.tickLower, + params.tickUpper, uint256(params.liquidityDelta), - MAX_SLIPPAGE_INCREASE, - MAX_SLIPPAGE_INCREASE, + _MAX_SLIPPAGE_INCREASE, + _MAX_SLIPPAGE_INCREASE, recipient, hookData ) ); uint256 tokenId = lpm.nextTokenId(); - bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(key); lpm.modifyLiquidities(calls, block.timestamp + 1); return (tokenId, params);