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

Allow donate() to specify tick #346

Open
danrobinson opened this issue Sep 1, 2023 · 3 comments
Open

Allow donate() to specify tick #346

danrobinson opened this issue Sep 1, 2023 · 3 comments
Assignees

Comments

@danrobinson
Copy link
Contributor

danrobinson commented Sep 1, 2023

Component

PoolManager/Pool

Describe the suggested feature and problem it solves.

Right now, donate() can donate additional fees to currently in-range liquidity provides. But there is no way to donate fees to out-of-range liquidity providers—specifically liquidity providers who would be in range if liquidity was at a different tick.

The most common use cases would be for hooks that want to distribute some kind of rent to liquidity providers who have not been traded against (which is currently impossible), or who want to distribute fees or rent to liquidity providers after a trade in some way other than proportional to volume (which would require splitting the swap up and donating after each step).

Some examples:

  • In its current implementation, TWAMM charges fees on net volume rather than gross. But if TWAMM wanted to charge gross fees and distribute those to the liquidity providers who would have been in range when those swaps were executed, it would need something like this.
  • Designs for ex ante LVR-capturing auctions (like mcAMM) may want to distribute rents not proportional to actual volume traded, but proportional to the option value given up by liquidity providers. That may even involve distributing rent to liquidity providers who were not swapped against.
  • Some other LVR-capturing designs might want to disproportionately pay fees to the first LPs swapped against, since otherwise they suffer much more LVR than the later LPs (because they trade at a worse price).

Describe the desired implementation.

Change donate signature to:

function donate(PoolKey memory key, uint256 amount0[], uint256 amount1[], int24[] ticks, bytes calldata hookData)

amount0, amount1, and ticks would all have to be arrays of the same length. ticks would indicate the buckets into which the fees should be donated (identified by the tick immediately below them). ticks must be sorted from low to high (reverts if it is not).

Algorithm

  • First, compute the liquidity that would be in range at ticks[0] by walking down to it.
    • Start with liquidityAtTick = liquidityCurrent.
    • Starting at the current tick, walk down all initialized ticks until reaching ticks[0], applying liquidityNet to liquidityAtTick. The last tick applied should be the last one to the right of ticks[0].
  • Next, walk back over all initialized ticks to the current tick, tracking liquidity and cumulative fee growth and updating the feeGrowthOutside values on each tick as you go.
    • Once you've passed the current tick, stop. Add the cumulativeFeeGrowths to the respective feeGrowthGlobals in the current state.
    • At each one, check whether you've passed one or more ticks in theticks array. If so, loop over them and update cumulativeFeeGrowth0 and cumulativeFeeGrowth1 by adding the respective amount / liquidityCurrent. Also check that those ticks are in increasing order and revert if not.
    • At each initialized tick, update liquidityCurrent by applying liquidityNet, and update each feeGrowthOutside by adding cumulativeFeeGrowth to it.

Once that's complete, repeat all of those steps, but for the ticks to the right of the current tick. That means you'll need to keep walking up to the last tick to get the liquidityCurrent, and then walk back down, tracking new cumulativeFeeGrowth values and applying them to each initialized tick, until you get back to the current tick.

Describe alternatives.

The main alternatives hooks can use right now for custom distribution of fees to different liquidity providers who were traded against:

  • They can just use swap fees and accept that the allocation will be imprecise.
  • They can split their trade into multiple steps and donate after each one, incurring additional gas cost during swaps.
  • They can create an independent fee regime by forcing all liquidity providers to stake their liquidity on https://github.com/Uniswap/v3-staker, incurring additional gas cost both on liquidity provision and on swaps (since you have to track your own parallel feeGrowthOutside externally).

If hooks want to distribute rent to liquidity providers who weren't traded against at all (which is something ex ante auctions might want), only the last option is available.

Another alternative would be for donate to only take one tick, but in that case, donations to all ticks crossed after a large swap would require quadratic gas costs rather than linear.

Additional context.

No response

@ewilz ewilz added p1 Great to have, implement first thing after p0s and removed triage labels Sep 1, 2023
@ewilz ewilz self-assigned this Sep 11, 2023
@GarrettPetersen
Copy link

I need this functionality. Thanks for opening the issue!

@snreynolds snreynolds self-assigned this Oct 17, 2023
@wjmelements
Copy link
Contributor

@snreynolds Is this change going to be in the v4 release?

@hensha256 hensha256 removed the p1 Great to have, implement first thing after p0s label Mar 19, 2024
@fuelmessenger
Copy link

✨ Thanks to the community, there's an estimated bounty value of $68.13 USD for a successful merge request of this issue via contributions such as 6.09375 UNI tokens. Happy coding 😀
Details and T&Cs at joinfuel.io

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

No branches or pull requests

7 participants