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

skip calls to hooks when msg.sender is hook contract + test #503

Merged
merged 19 commits into from
Apr 5, 2024

Conversation

dianakocsis
Copy link
Contributor

Related Issue

Skip calls to hooks when msg.sender is the hook contract #471

Description of changes

skips calls to hooks when msg.sender is the hook contract + tests

src/libraries/Hooks.sol Outdated Show resolved Hide resolved
@@ -96,10 +96,12 @@ library Hooks {

/// @notice performs a hook call using the given calldata on the given hook
function callHook(IHooks self, bytes memory data) internal {
(bytes4 expectedSelector, bytes4 selector) = _callHook(self, data);
if (msg.sender != address(self)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh this is an interesting line of code.. address(self) reads like address(this), but is actually checking address(hook)... I feel like I lean towards a rename of callHook(IHooks hook, bytes memory data)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could collapse the if statement by doing
if (msg.sender == address(hook) return;
_callHook()
if(selector != expectedSelector) revert ..

which may be clean? but try it out and see if you like it.. not that opinionated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like that! Honestly it took me a while to figure out what was what so I think this will make it more clear

@@ -66,9 +66,6 @@ contract PoolSwapTest is Test, PoolTestBase {
(,, uint256 reserveAfter0, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this));
(,, uint256 reserveAfter1, int256 deltaAfter1) = _fetchBalances(data.key.currency1, data.sender, address(this));

assertEq(reserveBefore0, reserveAfter0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be a swap within the hook making the reserves not equal to each other so I removed it. Also since reservesOf might be removed entirely anyways

src/test/SkipCallsTestHook.sol Outdated Show resolved Hide resolved
manager = _manager;
}

function setFee(uint24 _fee) external {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are unneeded functions i believe for the purpose of this test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ya i needed them since I made the hook with a dynamic fee but ill change that so i dont need these

test/SkipCallsTestHook.t.sol Outdated Show resolved Hide resolved
);
}

function test_beforeSwap_invalidSender() public {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by invalid_sender in the test name?

I think this is just testing that the hook's call to beforeSwap doesnt trigger the hook again yes? thus, the count to beforeSwap is 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more like invalid caller aka the hook, but ill rename it to test_beforeSwap_skipIfCalledByHook() to make it more clear

@snreynolds snreynolds self-requested a review March 20, 2024 18:48
Copy link
Member

@snreynolds snreynolds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes look good. @hensha256 brought up a good point that changing away from self diverges from how we do this in other libs (Pool.sol, Position.sol) so I'm actually inclined to change it back... and we can discuss more later. Sorry for back and forth there

snreynolds
snreynolds previously approved these changes Mar 20, 2024
Comment on lines 32 to 37
SkipCallsTestHook impl = new SkipCallsTestHook();
vm.etch(address(skipCallsTestHook), address(impl).code);
deployFreshManagerAndRouters();
skipCallsTestHook.setManager(IPoolManager(manager));

(currency0, currency1) = deployMintAndApprove2Currencies();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just put all of this in setUp and then do the init/addLiq as needed in each of the test cases? I think it may be a bit easier to follow what exactly is being called to understand the expected count amount. And I dont think the hook address needs to change each time ie all of this can just happen once? Unless Im missing something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so make one hook and turn all flags on? and check that count is total 10 at the end?

bytes calldata hookData
) external override returns (bytes4) {
counter++;
_initialize(key, Constants.SQRT_RATIO_1_1, hookData);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe overkill but it seems like we are just calling the same function on the pool again (except for this.. this one does initialize).... Maybe instead we actually fuzz the function thats called inside? That way we are testing that no matter which hook callback we are in, any action on the pool called from the hook skips the call? So it could be any of initialize/swap/modifyPositon/donate

deploy(skipCallsTestHook);

MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256);
MockERC20(Currency.unwrap(key.currency1)).approve(address(skipCallsTestHook), Constants.MAX_UINT256);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert counter is 0 before the call in each of these? that makes it super clear we are expecting a change to counter.. I think you do it below but not here/ i think its missing in some other tests as well

donateRouter.donate(key, 100, 200, abi.encode(address(this)));
assertEq(skipCallsTestHook.counter(), 1);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a test where we call again on the pool and the counter should be 2 but not 4?

Copy link

github-actions bot commented Apr 3, 2024

Forge code coverage:

File % Lines % Statements % Branches % Funcs
src/ERC6909.sol 91.30% (21/23) 85.71% (24/28) 100.00% (4/4) 85.71% (6/7)
src/ERC6909Claims.sol 100.00% (6/6) 100.00% (8/8) 100.00% (4/4) 100.00% (1/1)
src/NoDelegateCall.sol 100.00% (1/1) 100.00% (3/3) 100.00% (2/2) 100.00% (1/1)
src/PoolManager.sol 90.91% (80/88) 89.34% (109/122) 96.67% (29/30) 73.08% (19/26)
src/ProtocolFees.sol 100.00% (13/13) 95.65% (22/23) 87.50% (7/8) 100.00% (3/3)
src/libraries/BitMath.sol 100.00% (47/47) 100.00% (57/57) 100.00% (36/36) 100.00% (2/2)
src/libraries/CurrencyDelta.sol 100.00% (5/5) 100.00% (7/7) 100.00% (0/0) 100.00% (3/3)
src/libraries/FullMath.sol 100.00% (29/29) 100.00% (33/33) 100.00% (8/8) 100.00% (2/2)
src/libraries/Hooks.sol 100.00% (42/42) 98.73% (78/79) 96.67% (29/30) 100.00% (14/14)
src/libraries/Lock.sol 100.00% (4/4) 100.00% (4/4) 100.00% (0/0) 100.00% (3/3)
src/libraries/NonZeroDeltaCount.sol 100.00% (6/6) 100.00% (6/6) 100.00% (0/0) 100.00% (3/3)
src/libraries/Pool.sol 99.23% (129/130) 97.18% (138/142) 93.02% (80/86) 100.00% (13/13)
src/libraries/PoolGetters.sol 100.00% (3/3) 100.00% (3/3) 100.00% (0/0) 100.00% (3/3)
src/libraries/Position.sol 100.00% (12/12) 100.00% (14/14) 100.00% (6/6) 100.00% (2/2)
src/libraries/ProtocolFeeLibrary.sol 100.00% (8/8) 100.00% (12/12) 100.00% (4/4) 100.00% (3/3)
src/libraries/SafeCast.sol 100.00% (8/8) 100.00% (14/14) 100.00% (8/8) 100.00% (4/4)
src/libraries/SqrtPriceMath.sol 100.00% (32/32) 98.48% (65/66) 83.33% (20/24) 100.00% (8/8)
src/libraries/SwapFeeLibrary.sol 100.00% (5/5) 100.00% (9/9) 100.00% (4/4) 100.00% (3/3)
src/libraries/SwapMath.sol 100.00% (23/23) 100.00% (30/30) 100.00% (12/12) 100.00% (1/1)
src/libraries/TickBitmap.sol 100.00% (19/19) 100.00% (34/34) 100.00% (6/6) 100.00% (3/3)
src/libraries/TickMath.sol 97.87% (92/94) 97.30% (144/148) 97.83% (45/46) 50.00% (2/4)
src/libraries/UnsafeMath.sol 100.00% (1/1) 100.00% (1/1) 100.00% (0/0) 100.00% (1/1)
src/types/BalanceDelta.sol 100.00% (2/2) 100.00% (2/2) 100.00% (0/0) 100.00% (2/2)
src/types/Currency.sol 100.00% (15/15) 91.67% (22/24) 80.00% (8/10) 100.00% (6/6)
src/types/PoolId.sol 100.00% (1/1) 100.00% (1/1) 100.00% (0/0) 100.00% (1/1)
Total 77.46% (1041/1344) 77.02% (1411/1832) 56.18% (418/744) 66.45% (202/304)

Copy link
Contributor

@hensha256 hensha256 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally looking great!! couple of small things but we're nearly there

_fetchBalances(data.key.currency1, data.sender, address(this));

require(poolBalanceBefore0 == poolBalanceAfter0, "poolBalanceBefore0 is not equal to poolBalanceAfter0");
require(poolBalanceBefore1 == poolBalanceAfter1, "poolBalanceBefore1 is not equal to poolBalanceAfter1");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these being removed in this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its because the hook might do a swap/add liq and settle that is nested. meaning the balance would have changed before the outermost swap settles

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep exactly, the hook does its own swap which changes the balance

& uint160(
~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG
& ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG
& ~Hooks.AFTER_SWAP_FLAG & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so these lines can become

        address(
            uint160(
                uint256(type(uint160).max) & clearAllHookPermisssionsMask | Hooks.AFTER_INITIALIZE_FLAG
            )
        )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why it needs to go via uint256... you could try making clearAllHookPermisssionsMask a uint160 too

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you commented something similar in #548 which I can do over there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah so much nicer! thank you!!

test/SkipCallsTestHook.t.sol Show resolved Hide resolved
test/SkipCallsTestHook.t.sol Show resolved Hide resolved
deploy(skipCallsTestHook);
approveAndAddLiquidity(skipCallsTestHook);

modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        assertEq(skipCallsTestHook.counter(), 0);

above this line too

@@ -4,6 +4,7 @@ pragma solidity ^0.8.15;
import {Test} from "forge-std/Test.sol";
import {CurrencyLibrary, Currency} from "../src/types/Currency.sol";
import {MockERC6909Claims} from "../src/test/MockERC6909Claims.sol";
import "forge-std/console.sol";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove!

@dianakocsis dianakocsis merged commit 8a4ebb0 into main Apr 5, 2024
6 checks passed
@dianakocsis dianakocsis deleted the skip-calls-to-hooks branch April 5, 2024 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants