Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzz test: beforeSwap returns unspecified #730

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA custom curve + swap noop.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
127257
127305
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA fee on unspecified.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
155691
155770
Original file line number Diff line number Diff line change
@@ -1 +1 @@
106660
106764
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
117746
117850
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
129595
129702
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn native 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
118816
118923
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint native output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140629
140733
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
156433
156537
Original file line number Diff line number Diff line change
@@ -1 +1 @@
208496
208600
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140527
140631
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133197
133245
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with lp fee and protocol fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
170330
170437
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with return dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
146758
146862
2 changes: 1 addition & 1 deletion .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149016
149120
20 changes: 15 additions & 5 deletions src/test/PoolSwapTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ contract PoolSwapTest is PoolTestBase {
require(deltaBefore1 == 0, "deltaBefore1 is not equal to 0");

BalanceDelta delta = manager.swap(data.key, data.params, data.hookData);

(,, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this));
(,, int256 deltaAfter1) = _fetchBalances(data.key.currency1, data.sender, address(this));

bool hookCanReturnDeltaUnspecified = data.key.hooks.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)
|| data.key.hooks.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG);

if (data.params.zeroForOne) {
if (data.params.amountSpecified < 0) {
// exact input, 0 for 1
Expand All @@ -69,10 +71,14 @@ contract PoolSwapTest is PoolTestBase {
"deltaAfter0 is not greater than or equal to data.params.amountSpecified"
);
require(delta.amount0() == deltaAfter0, "delta.amount0() is not equal to deltaAfter0");
require(deltaAfter1 >= 0, "deltaAfter1 is not greater than or equal to 0");
if (!hookCanReturnDeltaUnspecified) {
require(deltaAfter1 >= 0, "deltaAfter1 is not greater than or equal to 0");
}
} else {
// exact output, 0 for 1
require(deltaAfter0 <= 0, "deltaAfter0 is not less than or equal to zero");
if (!hookCanReturnDeltaUnspecified) {
require(deltaAfter0 <= 0, "deltaAfter0 is not less than or equal to zero");
}
require(delta.amount1() == deltaAfter1, "delta.amount1() is not equal to deltaAfter1");
require(
deltaAfter1 <= data.params.amountSpecified,
Expand All @@ -87,10 +93,14 @@ contract PoolSwapTest is PoolTestBase {
"deltaAfter1 is not greater than or equal to data.params.amountSpecified"
);
require(delta.amount1() == deltaAfter1, "delta.amount1() is not equal to deltaAfter1");
require(deltaAfter0 >= 0, "deltaAfter0 is not greater than or equal to 0");
if (!hookCanReturnDeltaUnspecified) {
require(deltaAfter0 >= 0, "deltaAfter0 is not greater than or equal to 0");
}
} else {
// exact output, 1 for 0
require(deltaAfter1 <= 0, "deltaAfter1 is not less than or equal to 0");
if (!hookCanReturnDeltaUnspecified) {
require(deltaAfter1 <= 0, "deltaAfter1 is not less than or equal to 0");
}
require(delta.amount0() == deltaAfter0, "delta.amount0() is not equal to deltaAfter0");
require(
deltaAfter0 <= data.params.amountSpecified,
Expand Down
204 changes: 180 additions & 24 deletions test/CustomAccounting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,35 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {

address hook;

address constant BEFORE_SWAP_FLAGS = address(uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG));
address constant AFTER_SWAP_FLAGS = address(uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG));
address constant BEFORE_AND_AFTER_SWAP_FLAGS = address(uint160(BEFORE_SWAP_FLAGS) | uint160(AFTER_SWAP_FLAGS));

function setUp() public {
initializeManagerRoutersAndPoolsWithLiq(IHooks(address(0)));
}

function _setUpDeltaReturnFuzzPool() internal {
address hookAddr = address(uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG));
function _setUpDeltaReturnFuzzPool(address hookAddr) internal {
address impl = address(new DeltaReturningHook(manager));
_etchHookAndInitPool(hookAddr, impl);

// give the hook tokens so it can give tokens
key.currency0.transfer(hook, type(uint128).max);
key.currency1.transfer(hook, type(uint128).max);

// give the manager tokens so the hook can take tokens
key.currency0.transfer(address(manager), type(uint128).max);
key.currency1.transfer(address(manager), type(uint128).max);
}

function _setUpCustomCurvePool() internal {
address hookAddr = address(uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG));
address hookAddr = BEFORE_SWAP_FLAGS;
address impl = address(new CustomCurveHook(manager));
_etchHookAndInitPool(hookAddr, impl);

// add liquidity by sending tokens straight into the contract
key.currency0.transfer(hook, 10e18);
key.currency1.transfer(hook, 10e18);
}

function _setUpFeeTakingPool() internal {
Expand Down Expand Up @@ -104,10 +119,6 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {
function test_swap_beforeSwapNoOpsSwap_exactInput() public {
_setUpCustomCurvePool();

// add liquidity by sending tokens straight into the contract
key.currency0.transfer(hook, 10e18);
key.currency1.transfer(hook, 10e18);

uint256 balanceBefore0 = currency0.balanceOf(address(this));
uint256 balanceBefore1 = currency1.balanceOf(address(this));

Expand All @@ -130,10 +141,6 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {
function test_swap_beforeSwapNoOpsSwap_exactOutput() public {
_setUpCustomCurvePool();

// add liquidity by sending tokens straight into the contract
key.currency0.transfer(hook, 10e18);
key.currency1.transfer(hook, 10e18);

uint256 balanceBefore0 = currency0.balanceOf(address(this));
uint256 balanceBefore1 = currency1.balanceOf(address(this));

Expand All @@ -152,7 +159,7 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {
assertEq(currency1.balanceOf(address(this)), balanceBefore1 + amountToSwap, "amount 1");
}

// maximum available liquidity in each direction for the pool in test_fuzz_swap_beforeSwap_returnsDeltaSpecified
// maximum available liquidity in each direction for the pool in
int128 maxPossibleIn_fuzz_test = -6018336102428409;
int128 maxPossibleOut_fuzz_test = 5981737760509662;

Expand All @@ -162,18 +169,13 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {
bool zeroForOne
) public {
// ------------------------ SETUP ------------------------
Currency specifiedCurrency;
bool isExactIn;
_setUpDeltaReturnFuzzPool();
_setUpDeltaReturnFuzzPool(BEFORE_SWAP_FLAGS);

// initialize the pool and give the hook tokens to pay into swaps
key.currency0.transfer(hook, type(uint128).max);
key.currency1.transfer(hook, type(uint128).max);

// bound amount specified to be a fair amount less than the amount of liquidity we have
amountSpecified = int128(bound(amountSpecified, -3e11, 3e11));
isExactIn = amountSpecified < 0;
specifiedCurrency = (isExactIn == zeroForOne) ? key.currency0 : key.currency1;
// bound amount specified, but can be more/less than the available liquidity
amountSpecified =
int128(bound(amountSpecified, maxPossibleIn_fuzz_test - 3e11, maxPossibleOut_fuzz_test + 3e11));
bool isExactIn = amountSpecified < 0;
Currency specifiedCurrency = (isExactIn == zeroForOne) ? key.currency0 : key.currency1;

// bound delta in specified to not take more than the reserves available, nor be the minimum int to
// stop the hook reverting on take/settle
Expand Down Expand Up @@ -264,6 +266,161 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {
}
}

function test_fuzz_swap_beforeSwap_returnsDeltaUnspecified(
int128 hookDeltaUnspecified,
int256 amountSpecified,
bool zeroForOne
) public {
// ------------------------ SETUP ------------------------
_setUpDeltaReturnFuzzPool(BEFORE_SWAP_FLAGS);

// bound amount specified, but can be more/less than the available liquidity
amountSpecified =
int128(bound(amountSpecified, maxPossibleIn_fuzz_test - 3e11, maxPossibleOut_fuzz_test + 3e11));
bool isExactIn = amountSpecified < 0;
Currency unspecifiedCurrency = (isExactIn == zeroForOne) ? key.currency1 : key.currency0;

// bound delta in unspecified to not take more than the reserves available
// lower bound to make sure that hookDeltaUnspecified + amountUnspecified dont exceed min(int128)
uint128 reservesOfUnspecified = uint128(unspecifiedCurrency.balanceOf(address(manager)));
hookDeltaUnspecified = int128(
bound(
hookDeltaUnspecified, int256(type(int128).min) + maxPossibleOut_fuzz_test, int128(reservesOfUnspecified)
)
);

DeltaReturningHook(hook).setDeltaUnspecifiedBeforeSwap(hookDeltaUnspecified);

// ------------------------ FUZZING CASES ------------------------
_checkUnspecifiedDeltaFuzzCases(amountSpecified, zeroForOne, unspecifiedCurrency, hookDeltaUnspecified);
}

function test_fuzz_swap_afterSwap_returnsDeltaUnspecified(
int128 hookDeltaUnspecified,
int256 amountSpecified,
bool zeroForOne
) public {
// ------------------------ SETUP ------------------------
_setUpDeltaReturnFuzzPool(AFTER_SWAP_FLAGS);

// bound amount specified, but can be more/less than the available liquidity
amountSpecified =
int128(bound(amountSpecified, maxPossibleIn_fuzz_test - 3e11, maxPossibleOut_fuzz_test + 3e11));
bool isExactIn = amountSpecified < 0;
Currency unspecifiedCurrency = (isExactIn == zeroForOne) ? key.currency1 : key.currency0;

// bound delta in unspecified to not take more than the reserves available
// lower bound to make sure that hookDeltaUnspecified + amountUnspecified dont exceed min(int128)
uint128 reservesOfUnspecified = uint128(unspecifiedCurrency.balanceOf(address(manager)));
hookDeltaUnspecified = int128(
bound(
hookDeltaUnspecified, int256(type(int128).min) + maxPossibleOut_fuzz_test, int128(reservesOfUnspecified)
)
);

DeltaReturningHook(hook).setDeltaUnspecifiedAfterSwap(hookDeltaUnspecified);

// ------------------------ FUZZING CASES ------------------------
_checkUnspecifiedDeltaFuzzCases(amountSpecified, zeroForOne, unspecifiedCurrency, hookDeltaUnspecified);
}

function test_fuzz_swap_beforeSwap_and_afterSwap_returnDeltaUnspecified(
int128 hookDeltaUnspecifiedBeforeSwap,
int128 hookDeltaUnspecifiedAfterSwap,
int256 amountSpecified,
bool zeroForOne
) public {
// ------------------------ SETUP ------------------------
_setUpDeltaReturnFuzzPool(BEFORE_AND_AFTER_SWAP_FLAGS);

// bound amount specified, but can be more/less than the available liquidity
amountSpecified =
int128(bound(amountSpecified, maxPossibleIn_fuzz_test - 3e11, maxPossibleOut_fuzz_test + 3e11));
bool isExactIn = amountSpecified < 0;
Currency unspecifiedCurrency = (isExactIn == zeroForOne) ? key.currency1 : key.currency0;

// bound delta in unspecified to not take more than the reserves available
// lower bound to make sure that hookDeltaUnspecified + amountUnspecified dont exceed min(int128)
uint128 reservesOfUnspecified = uint128(unspecifiedCurrency.balanceOf(address(manager)));
hookDeltaUnspecifiedBeforeSwap = int128(
bound(
hookDeltaUnspecifiedBeforeSwap,
int256(type(int128).min) + maxPossibleOut_fuzz_test,
int128(reservesOfUnspecified)
)
);
// bound the second delta by the first delta so that combined they do not exceed our bounds
if (hookDeltaUnspecifiedBeforeSwap >= 0) {
hookDeltaUnspecifiedAfterSwap = int128(
bound(
hookDeltaUnspecifiedAfterSwap,
int256(type(int128).min) + maxPossibleOut_fuzz_test,
int256(int128(reservesOfUnspecified) - hookDeltaUnspecifiedBeforeSwap)
)
);
} else {
hookDeltaUnspecifiedAfterSwap = int128(
bound(
hookDeltaUnspecifiedAfterSwap,
int256(type(int128).min) + maxPossibleOut_fuzz_test - hookDeltaUnspecifiedBeforeSwap,
int128(reservesOfUnspecified)
)
);
}

DeltaReturningHook(hook).setDeltaUnspecifiedBeforeSwap(hookDeltaUnspecifiedBeforeSwap);
DeltaReturningHook(hook).setDeltaUnspecifiedAfterSwap(hookDeltaUnspecifiedAfterSwap);
int128 hookDeltaUnspecified = hookDeltaUnspecifiedBeforeSwap + hookDeltaUnspecifiedAfterSwap;

// ------------------------ FUZZING CASES ------------------------
_checkUnspecifiedDeltaFuzzCases(amountSpecified, zeroForOne, unspecifiedCurrency, hookDeltaUnspecified);
}

function _checkUnspecifiedDeltaFuzzCases(
int256 amountSpecified,
bool zeroForOne,
Currency unspecifiedCurrency,
int128 hookDeltaUnspecified
) internal {
IPoolManager.SwapParams memory params = IPoolManager.SwapParams({
zeroForOne: zeroForOne,
amountSpecified: amountSpecified,
sqrtPriceLimitX96: (zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT)
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});

if (params.amountSpecified == 0) {
vm.expectRevert(IPoolManager.SwapAmountCannotBeZero.selector);
swapRouter.swap(key, params, testSettings, ZERO_BYTES);
// successful swaps !
} else {
uint256 balanceThisBefore = unspecifiedCurrency.balanceOf(address(this));
uint256 balanceHookBefore = unspecifiedCurrency.balanceOf(hook);
uint256 balanceManagerBefore = unspecifiedCurrency.balanceOf(address(manager));

swapRouter.swap(key, params, testSettings, ZERO_BYTES);

// in all cases the hook gets what they took exactly
assertEq(
balanceHookBefore.toInt256() + hookDeltaUnspecified,
unspecifiedCurrency.balanceOf(hook).toInt256(),
"hook balance change incorrect"
);

// positive if exactOut as input balances increases, negative if exactIn as output balance decreases
int256 managerDeltaUnspecified =
unspecifiedCurrency.balanceOf(address(manager)).toInt256() - balanceManagerBefore.toInt256();

// any delta that wasnt for the hook must have been for this swapper
assertEq(
balanceThisBefore.toInt256() - (managerDeltaUnspecified + hookDeltaUnspecified),
unspecifiedCurrency.balanceOf(address(this)).toInt256(),
"swapper balance change incorrect"
);
}
}

// ------------------------ MODIFY LIQUIDITY ------------------------

function test_addLiquidity_withFeeTakingHook() public {
Expand All @@ -275,7 +432,6 @@ contract CustomAccountingTest is Test, Deployers, GasSnapshot {
uint256 hookBalanceBefore1 = currency1.balanceOf(hook);
uint256 managerBalanceBefore0 = currency0.balanceOf(address(manager));
uint256 managerBalanceBefore1 = currency1.balanceOf(address(manager));
// console2.log(address(key.hooks));
modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES);
snapLastCall("addLiquidity CA fee");

Expand Down
Loading