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

Simplify AMM Swap API #4856

Closed
Tracked by #5062
dtribble opened this issue Mar 16, 2022 · 6 comments
Closed
Tracked by #5062

Simplify AMM Swap API #4856

dtribble opened this issue Mar 16, 2022 · 6 comments
Assignees
Labels
Milestone

Comments

@dtribble
Copy link
Member

dtribble commented Mar 16, 2022

What is the Problem Being Solved?

Supporting both "swapIn" and "swapOut" in the contract API increases the complexity of the contract while narrowing its use cases. This is because the swapOut cases uses the want in the proposal as both the limit and as the sale target. Thus for example, clients cannot express the requirement from liquidation of "purchase up to a limit but no more", because a want expresses "at least" not "at most".

Description of the Design

Replace swapIn and swapOut with a single swap operation that takes an additional maxOut offer argument that is not the want amount. The maxOut argument, if present, specifies that the most that the client needs back.

  • The swapIn operation would simply be a deprecated alias for swap.
  • The swapOut operation would invoke swap with maxOut as the Out amount from the want.

The pseudo code for the logic of the swap operation is (with conveniently sloppy fee compute):

swap({ give: { In } , want: { Out }, { maxOut = undefined })
  if In.brand === central
    getPool(Out.brand).giveCentral(In, maxOut)
  else if Out.brand === central
    getPool(In.brand).giveSecondary(In, maxOut)
  else 
    virtualPool(In.brand, Out.brand).swapSecondaries(In, maxOut)
    
pool.giveCentral(X, maxY = undefined) => Y:secondary
  fee = computeFee(X)
  afterFee = X - fee
  ySimple = increaseX(afterFee, xPool, yPool)
  Y = maxY && maxY < ySimple ? maxY : ySimple
  X = decreaseY(Y, xPool, yPool)
 
pool.giveSecondary(X, maxY = undefined) => Y:central
  yRaw = increaseX(X, xPool, yPool)
  ySimple = yRaw - computeFee(yRaw)
  Y = maxY && maxY < ySimple ? maxY : ySimple
  fee = computeFee(Y)
  X = decreaseY(Y, xPool, yPool)
 
virtualPool(xPool, yPool).swapSecondaries(X:secondary, maxY = undefined) => Y:secondary
  { Y: central } = xPool.giveSecondary(X)
  { X: maxCentral, Y, fee: fee2 } = yPool.giveCentral(central, maxY)
  { X, fee: fee1} = xPool.giveSecondary(X, maxCentral)
  fee = fee1 + fee2

increaseX: Return the Y to be returned for a contribution of 
decreaseY: Return the minimum X required to release Y

Note that this also returns an X that is the minimum required In to purchase the resulting Y

Security Considerations

With this change, the want becomes exclusively a safety mechanism, which should make it more useful.

Test Plan

  • existing tests should work.
  • change swapOut tests to use the new API directly instead
  • add tests for the maxOut to be higher than want.Out

refs: #4321

@dtribble dtribble added the enhancement New feature or request label Mar 16, 2022
@Chris-Hibbert
Copy link
Contributor

I'm not getting it yet. swapIn currently must specify give, and swapOut must specify want. swap is currently an alias for swapIn.

Are you proposing that there be just one operation swap, and that it accept either want or give as the required amount? I think that doesn't work compatibly with specifying both. I think the effect we want would be achieved by having swapIn that required a give, and treated offerArgs of { amountOut } as a maximum, along with swapOut that was its dual. swapOut would require want, and respond to offerArgs of { amountIn } as a limit. In both cases, if both want and give are provided, then zoe would enforce them and they aren't useful as advice.

@Tartuffo Tartuffo added the AMM label Mar 21, 2022
@dtribble
Copy link
Member Author

I updated the description such that it should now answer your question here.

@Chris-Hibbert
Copy link
Contributor

That's clear, thanks.

It doesn't let someone express what swapOut(give, want) currently means. swap(give, want) would pay all of give as long as it was sufficient. The current swapOut(give, want) sells enough of give to provide want, but not more, and if give isn't sufficient to provide want, no sale takes place.

That's good enough for what we want when liquidating, but I'm not yet convinced that there aren't use cases for the current API.

I think I'd be happier with swap(want, give, { maxOut = undefined, minIn = undefined }).

@dtribble
Copy link
Member Author

dtribble commented Mar 28, 2022

It supports the current swapOut by having the amountOut as both the want and the maxOut:

        const proposal = { give: { In: collateral }, want: { Out: debt } };
        E(zcf).offer(
          E(amm).makeSwapInvitation(),
          proposal,
          payments,
          { maxOut: debt },
        );

The maxOut (at most) can be different from the Out (at least).

@Chris-Hibbert
Copy link
Contributor

I don't understand the pseudo-code well enough to tell if it does what I want. I don't see that it calculates a trade based on the 'Out' price when maxOut is specified.

What I'd expect swap(in, out, maxOut) to do is calculate a swap based on the in price (since this is the required behavior for swapIn) , and then see if the resulting out is at least out, and not more than maxOut.

This is not the same as calculating a swap based on the out price, and making sure that the required payment is no more than In.

If soda is 6$/bottle

swapIn($20) ==> 3 bottles, $18 swap(20, 3)
swapIn($20, 2 bottles) ==> 3 bottles, $18 swap(20, 2)
swapIn($20, 4 bottles) ==> no trade swap(20, 4)
swapIn($20, empty, max = 2 bottles) ==> 2 bottles, $12 swap(20, empty, 2)

swapOut($20, 4 bottles) ==> no trade swap(20, 4)
swapOut($20, 3 bottles) ==> 3 bottles, $18 swap(20, 3)
swapOut(empty, 2 bottles, max $15) ==> 2 bottles, $12 swap(empty, 2, 15)
swapOut(empty, 3 bottles, max $15) ==> 3 bottles, $18 swap(3, 15)
swapOut(empty, 4 bottles, max $15) ==> 3 bottles, $18 swap(4, 15)
swapOut(3 bottles, $20) ==> no trade no equivalent
swapOut(3 bottles, $15) ==> 3 bottles, $18 swap(3, 15)

I think we want swapIn(), swapOut() and liquidate()

liquidate(3 bottles, $15) ==> 3 bottles, $18
liquidate(3 bottles, $12) ==> 2 bottles, $12
liquidate($19, 4 bottles) ===> $19, 3 bottles
liquidate($19, 3 bottles) ===> $18, 3 bottles
liquidate($19, 2 bottles) ===> $12, 2 bottles

@dtribble
Copy link
Member Author

Closed via #5125

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants