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

[feat: CL hooks]: Add CL hooks into core CL logic and test hook-specific behavior #6859

Merged
merged 13 commits into from
Nov 22, 2023

Conversation

AlpinYukseloglu
Copy link
Contributor

Closes: #XXX

What is the purpose of the change

This PR adds the CL hooks that were implemented in previous PRs into core CL logic.

Testing and Verifying

Testing this behavior in a way that does not circumvent important operations is actually quite tricky. While it is technically possible to cover this behavior using mock hooks, this felt much less robust than having a contract that actually implements the hooks to test against. This is what is done in pool_hooks_test.go.

As explained in the test comments, the tests lean on a CW contracts that implements all hooks with the following logic: when a hook is triggered, send 1 token with a denom corresponding to the hook to the originator of the tx (e.g. if I create a position, I would get 1 beforeCreatePosition token and 1 afterCreatePosition token). This makes testing behavior relatively straightforward (and hopefully readable).

Source code for the contract will be added in a separate PR to spare the diff for this one

Documentation and Release Note

  • Does this pull request introduce a new feature or user-facing behavior changes?
  • Changelog entry added to Unreleased section of CHANGELOG.md?

Where is the change documented?

  • Specification (x/{module}/README.md)
  • Osmosis documentation site
  • Code comments?
  • N/A

@@ -89,15 +92,22 @@ func (k Keeper) CreatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddr
return CreatePositionData{}, err
}

positionId := k.getNextPositionIdAndIncrement(ctx)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: this was moved below the hasPositions check to ensure the before hook was triggered after validation logic but before any state changes (getNextPositionIdAndIncrement mutates state)

}

// BeforeAddToPosition is a hook that is called before liquidity is added to a position.
func (k Keeper) BeforeAddToPosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, positionId uint64, amount0Added osmomath.Int, amount1Added osmomath.Int, amount0Min osmomath.Int, amount1Min osmomath.Int) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

AddToPosition hooks were removed since the function just calls WithdrawPosition then CreatePosition under the hood. This makes it tricky to separate out AddToPosition hooks from the other two and imo unnecessary complexity that would increase the surface for circumventing hooks.

Opted to remove and keep hooks on the most primitive actions.

@@ -178,9 +178,136 @@ func (s *KeeperTestSuite) TestCallPoolActionListener() {
}
}

// Pool hook tests
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please see PR description for further justification of this testing approach

@AlpinYukseloglu AlpinYukseloglu changed the title [feat: CL hooks]: Wire up CL hooks and test hook-specific behavior [feat: CL hooks]: Add CL hooks into core CL logic and test hook-specific behavior Nov 10, 2023
@AlpinYukseloglu AlpinYukseloglu marked this pull request as ready for review November 10, 2023 17:41
Copy link
Member

@p0mvn p0mvn left a comment

Choose a reason for hiding this comment

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

Nice work. Left some comments, please take a look.

Could you please also add a link to where the test contract code is written and make sure that it is easy to locate in case someone needs to find it in the future?

For example, I can see that there are some contract sources here:
https://github.com/osmosis-labs/osmosis/blob/c387e00c6338fa113ccb3abcb129f633670445f6/x/concentrated-liquidity/testcontracts/contract-sources/

Let's put the hooks source in there as well?

Comment on lines 182 to 193
// General testing strategy:
// 1. Build a pre-defined contract that defines the following behavior for all hooks:
// if triggered, transfer 1 token with denom corresponding to the action prefix
// e.g. if action prefix is "beforeSwap", transfer 1 token with denom "beforeSwap"
// 2. Set this contract for all hooks defined by the test case (each case should have a list
// of action prefixes it wants to "activate")
// 3. Run a series of actions that would trigger all the hooks (create, add to, withdraw from, swap against a position),
// and ensure that the correct denoms are in the account balance after each action/at the end.
//
// NOTE: we assume that set contracts have valid implementations for all hooks and that this is validated
// at the contract setting stage at a higher level of abstraction. Thus, this class of errors is not covered
// by these tests.
Copy link
Member

Choose a reason for hiding this comment

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

Nice!

x/concentrated-liquidity/pool_hooks_test.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/pool_hooks_test.go Show resolved Hide resolved
Comment on lines +145 to +150
// Trigger before hook for SwapExactAmountIn prior to mutating state.
// If no contract is set, this will be a no-op.
err = k.BeforeSwapExactAmountIn(ctx, pool.GetId(), sender, tokenIn, tokenOutDenom, tokenOutMinAmount, spreadFactor)
if err != nil {
return osmomath.Int{}, err
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we have a mechanism in our tests for validating that BeforeSwapExactAmountIn is triggered before and not after the swap.

I think it would be valuable to add such an assertion so that we minimize the chances of a move-by-mistake in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems like it will require a meaningful change to the current testing approach so I created a separate issue with what I think would be a viable design in #6862

I agree that it's valuable to add this to ensure future changes/additions to hooks don't break this invariant. Would be great if we can handle this separately as I think it will take careful implementation to keep tests readable and would like to get the remaining core feature work in first

@@ -81,26 +99,6 @@ type AfterCreatePositionMsg struct {
UpperTick int64 `json:"upper_tick"`
}

type BeforeAddToPositionMsg struct {
Copy link
Member

Choose a reason for hiding this comment

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

Why did we decide to remove these?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Base automatically changed from alpo/cl-hooks-feat to main November 13, 2023 06:20
Copy link
Member

@mattverse mattverse left a comment

Choose a reason for hiding this comment

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

LGTM, let's make sure #6862 gets in as high priority! Left some nit comments please take a look!

x/concentrated-liquidity/types/pool_hooks.go Show resolved Hide resolved
Comment on lines +224 to +235
"single hook: before create position": {
actionPrefixes: []string{before(types.CreatePositionPrefix)},
},
"all before hooks": {
actionPrefixes: allBeforeHooks,
},
"all after hooks": {
actionPrefixes: allAfterHooks,
},
"all hooks": {
actionPrefixes: allHooks,
},
Copy link
Member

Choose a reason for hiding this comment

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

Seems like there will be dupicated test runs for before hook & after hook, was it that we wanted to test compound integration of hooks? 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I follow – the "all hooks" case is intended to run every hook simultaneously whereas the others run different subsets

Copy link
Member

Choose a reason for hiding this comment

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

yep, I was just curious of the significance of running hooks simultaneously vs running subset. (But it was a nit question, can be ignored)

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 I see – no particular reason other than general coverage (ensuring that various subsets can correctly run without triggering the others)

Copy link
Member

@mattverse mattverse left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@p0mvn p0mvn left a comment

Choose a reason for hiding this comment

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

LGTM

@AlpinYukseloglu AlpinYukseloglu merged commit 059c5db into main Nov 22, 2023
1 check passed
@AlpinYukseloglu AlpinYukseloglu deleted the alpo/cl-hooks-wiring branch November 22, 2023 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants