Grant Application - Manipulation-Resilient Price Oracle for CoW AMM LP Tokens

Grant Title

Manipulation-Resilient Price Oracle for CoW AMM LP Tokens

Author

The implementation of this project will be carried out by:
@bh2smith & @lumoswiz

About the authors

  • bh2smith is an experienced blockchain engineer and former contributor to CoW Protocol.

  • lumoswiz is a smart contract developer with experience working with DeFi protocols, including lending and p2p lending protocols.

Grant Category

Integrations and protocol order flow

Grant Description

In response to this RFG, we aim to develop the manipulation-resistant price oracle for CoW AMM LP tokens. This will require incorporating the following ideas:

  • Computing the rebalancing trade that a zero-fee constant function AMM would accept based on its current balance and external price feeds. This will require utilisation of the CoW helper contract and Chainlink price feeds for the underlying tokens.
  • The effect on the price of LP tokens is counteracted since their price is a function of the simulated pool state post-rebalancing.

Grant Goals and Impact

The use of AMM LP tokens as collateral is a challenge due to their susceptibility to manipulation within price oracles.

The objective of this work is to produce an oracle for LP tokens that guards against an attackerā€™s ability to capitalise from short-term under-reporting of value or short-term over-reporting of value.

The use of an oracle that guards against these attacks should boost the adoption of LP tokens as collateral in some lending markets.

To demonstrate real-world application, we will create a proof-of-concept integration with Aave V3, showcasing how LP tokens can be used as collateral while utilising our oracle solution.

Milestones

Milestone 1

This milestone has three deliverables, including:

  • A smart contract implementing the oracle. The oracle must:

    • Adhere to the Chainlink oracle interface.

    • Utilise existing Chainlink-compatible oracles for the underlying tokens.

    • Support two token pools with arbitrary weights.

  • Comprehensive test suite demonstrating the oracleā€™s manipulation resistance.

  • Documentation detailing the oracleā€™s functionality. The documentation will be supplied in markdown format within the projectā€™s Github repository.

Milestone 2

An integration example with Aave V3 will be provided where

  • LP tokens can be used as collateral.

  • The oracle source for these tokens will point to the oracle developed herein.

This will involve:

  • Setting up an Anvil mainnet fork.
  • Setup scripts to initialise and configure the LP tokens as a reserve.
  • Demonstration of user actions against this state, such as supplying LP tokens as collateral.
  • Demonstration of oracle resilience to manipulation.

Timeline

  • Milestone 1 aim to ship before 1/1/2025.
  • Milestone 2 aim to ship before 15/1/2025.

Funding Request

Funding request summary:

  • 6000 xDAI for milestone 1.
  • 1000 xDAI for milestone 2.

Gnosis Chain Address (to receive the grant)

0x62780bac6b361C703148B7fdeCDE44987C5C69D0

Referral

@fleupold

Other Information

Foundry will be utilised.

Terms and Conditions

By submitting this grant application, I acknowledge and agree to be bound by the CoW DAO Participation Agreement and the CoW Grant Terms and Conditions.

3 Likes

Having a robust oracle for CoW AMM LP tokens will enable using them as collateral in lending protocols, which can unlock further use cases.
Signalling my support

2 Likes

Hi, thanks for the inclusion of Foundry in the specification of the grant. Tooling and language selection is a large factor when it comes to burden that may be absorbed by the core team if there is some need to provide out-of-grant changes / support later on, significantly easing maintenance.

I signal my support as well for this grant, and think it would be fine to move this to snapshot.

3 Likes

Very interesting proposal. I look forward to seeing this PoC in the real world. Lets get this to snapshot

2 Likes

This proposal has been moved to snapshot:

https://snapshot.box/#/s:cowgrants.eth/proposal/0x9d729b4ab92b19a78a234aa25bbba019146f00278746c28569ec86563c42ddba

cc @middleway.eth, @mfw78 and @netrunner.eth

Votes are in, development will take place here:

1 Like

Just as a heads up, votes can be changed on snapshot before the closing of the poll. Not that I think this will be an issue, just an FYI for your own risk mitigation :grin:

2 Likes

Update:

Development is well under way. We have established what we believe to be a complete and solid first draft of the LPOracle contract as well as an OracleFactory (for convenience).

Unit tests are in place and we have begun setting up fork tests.

A couple of points we would like to highlight for discussion/confirmation are:

cc @fleupold, @mfw78 , @fedgiac

1 Like

I like how the discussion items are presented, they are all reasonable technical questions.
An overall point is that we should start with ā€œwell-knownā€ tokens and extend the oracle use to less reliable tokens at a later stage.

  • Token Decimals stored immutably as gas savings. I see that the gas impact can be significant, for example a call to decimals() on USDC, somewhat of a worst case, is about ~10k gas. Iā€™ve never seen a token that can change decimals in the wild. So overall it sounds reasonable to have them immutable. Still, it would be good if it were possible to deploy a new oracle for the same two tokens in the case that the decimals change. Then, the occasional meme coin that could change decimals once in a while can still be supported, provided that the new oracle is used.

  • Underlying Pool Assets must have ā‰¤18 decimals. I read itā€™s possible but what I donā€™t see mentioned is the cost of supporting tokens with >18 decimals, I suppose itā€™s significant extra smart-contract risk and complexity. Iā€™ve seen a few tokens with 21 decimals, but they are exceedingly rare and it should be ok to not implement support for them at this point if too costly.

  • Cannot rebalance pool reliably. Thatā€™s indeed concerning and I suppose itā€™s a consequence of the fact that the helper contract isnā€™t really the right tool here (itā€™s more or less a mistake that the grant singles out the helper so much, it should focus more on the rebalancing act). Reverting when the imbalance is large would be a fairly big limitation for an oracle, especially at times of high volatility, which is when the oracle is the most critical. A better solution would be to use the oracle code itself to compute the rebalanced price, which shouldnā€™t add too much complexity. This would also make the overall gas cost smaller.

  • Choice of oracleā€™s updatedAt. Overall, it feels wrong to hijack the oracle semantics to include extra data. I see itā€™s not clear what updatedAt should be, technically weā€™re updating the prices once the most recently updated oracle has been updated, but the least recently updated oracle is a better indication for price trustworthiness, which is usually the point of oracle timestamps. However, the latter might be an issue if for example some oracles rely on updatedAt for refreshing some price cache with a price update by backtracking to an earlier block and simulating the transaction at this point.
    A possible idea to have all data included in the oracle and being semantically consistent could be the following:

    • updatedAt is the timestamp of the most recently updated oracle
    • startedAt is the timestamp of the least recently updated oracle
    • roundId is the same as startedAt, meaning that a new round starts once the least recently updated oracle gets updated (and quite a few roundIds are skipped).
      Not sure how consistent is with the Chainlink oracle specs. One thing that might be against expectation is that old rounds can be current and updated after a new round started.
      Thereā€™s no clear solution here and Iā€™m happy to discuss things further. If itā€™s just about the extra data, we could also use the deprecated field answeredInRound and keep the semantics more consistent.
2 Likes

Thanks for the insights and comments. I wanted to add a few things to this discussion on re-balancing the pool, however, please correct me if my understanding is off on anything.

I donā€™t think the expectation that the helper contract should produce an order that perfectly balances the pool every time is correct, since the user inputs a prices vector. Its purpose seems to be to produce an order that satisfies the pool invariants given input prices. It is actually possible to get the order function to produce an order that balances the pool by varying the prices. However, this is not the intention for this work.

In this work, we are looking at pricing the LP tokens via simulating an order that:

  • satisfies pool invariants
  • zero fees
  • prices vector constructed using external chainlink price feeds

To achieve the above, the helper contract is great. The order function does a good job of producing simulated reserves that move toward pool rebalancing. Just based on some results I am seeing in unit tests from today, the LP token price is either at, or within 0.1%, of the balanced pool LP token price.

1 Like

Hi @lumoswiz ,

you are correct about the helper contract. And in light of the discussion, it may be better to forget about it altogether. The oracle should look at the pool invariant k (calculated by multiplying the two liquidity reserves) and use that to calculate the TVL of a hypothetical AMM with the same invariant k but prices equal to those from Chainlink. If you go through the math, the TVL computed this way should be 2* sqrt(k p1 *p2) (but please verify). Then, to get the LP token prices, you just divide that by the number of LP tokens.

The point we were tying to make (and which ended up generating confusion) is that the oracle logic follows more or less that of the helper contract: it looks at the on-chain state and uses some provided prices to compute a different state. But in this case, there is no need to have a separate helper contract.

I hope this helps!

2 Likes

That helps, thanks for the clarification.

1 Like

I have been looking at this and, as far as I can tell, the math described above works for 2 token pools with equal weights (50/50). Just for reference, here is the 2 token weight pool equations for determining the reserves:

y = āˆš((k * p_x * w_y)/(p_y * w_x))

x = āˆš((k * w_x * p_y)/(w_y * p_x))

Weights sum to 1.

Hi @lumoswiz ,

I get a different result. Let me show you my derivations:

  1. first the invariant Q1^a * Q2^(1-a)=k (for a between 0 and 1), which can be equivalently written as (Q1/Q2)^a * Q2=k
  2. Then the equation for the AMM marginal prices: if p1 and p2 are the chainlink prices, the AMM has the same prices as chainlink if and only if p1 * Q1/a =p2 * Q2/(1-a), which can be rewritten as Q1/Q2=(p2/p1)*(a/1-a)
  3. Now you can compute the reserves as: Q1=k * [(p2/p1)*(a/(1-a))]^(1-a) ; Q2=k * [(p1/p2) * ((1-a)/a)]^a
  4. and then TVL is k *p1^a * p2^(1-a) * [ (a/(1-a))^(1-a) + ((1-a)/a)^a ]

Again, you should double check my calculations, but as you can see, I donā€™t get a sqrt anywhere (unless of course you set a=1/2)

1 Like