Skip to content
This repository has been archived by the owner on Jan 7, 2020. It is now read-only.

Add autoswap (uniswap) #136

Closed
wants to merge 52 commits into from
Closed

Add autoswap (uniswap) #136

wants to merge 52 commits into from

Conversation

katelynsills
Copy link
Contributor

@katelynsills katelynsills commented Sep 24, 2019

This PR adds the autoswap contract, which is a more complex Zoe governing contract based on Uniswap.

Documentation from the PR:

AutoSwap

An AutoSwap is like a swap, except instead of having to find a
matching offer, an offer is always matched against the existing
liquidity pool. The AutoSwap contract checks whether your offer will
keep the constant product invariant before accepting.

Based on UniSwap.

Initialization

First, we initialize the autoSwapMaker so that we have access to the
liquidity issuer for this particular autoswap. We then pass the
liquidity issuer in as part of the issuers array.

const { liquidityIssuer, makeAutoSwap } = makeAutoSwapMaker();
const allIssuers = [moolaIssuer, simoleanIssuer, liquidityIssuer];

const { zoeInstance, governingContract: autoswap } = zoe.makeInstance(
  makeAutoSwap,
  allIssuers,
);

Adding liquidity to the pool

The moola<->simolean autoswap that we just created has a number of
methods in the API available to the user:

  1. addLiquidity
  2. removeLiquidity
  3. getPrice
  4. makeOffer

We can call addLiquidity with an escrow receipt from Zoe that proves
that we've escrowed moola and simoleans appropriately. For instance,
let's say that Alice decides to add liquidity. She creates an offer
description with the associated payments of moola and simoleans and
escrows them:

const aliceOffer = harden([
  {
    rule: 'haveExactly',
    amount: allIssuers[0].makeAmount(10),
  },
  {
    rule: 'haveExactly',
    amount: allIssuers[1].makeAmount(5),
  },
  {
    rule: 'wantAtLeast',
    amount: allIssuers[2].makeAmount(10),
  },
]);
const alicePayments = [aliceMoolaPayment, aliceSimoleanPayment, undefined];

const {
  escrowReceipt: allegedAliceEscrowReceipt,
  claimPayoff: aliceClaimPayoff,
} = await zoeInstance.escrow(aliceOffer, alicePayments);

She is able to ensure that she will get a minimum number of liquidity
tokens back by specifying a rule for the liquidity token slot with
wantAtLeast. In this case, Alice is stating that she wants at least
10 liquidity tokens back.

Making a swap offer

Let's say that Bob wants to actually use the moola<->simolean autoswap
to exchange 2 moola for 1 simolean. He escrows this with Zoe and
receives an escrow receipt.

 const bobMoolaForSimOfferDesc = harden([
  {
    rule: 'haveExactly',
    amount: allIssuers[0].makeAmount(2),
  },
  {
    rule: 'wantAtLeast',
    amount: allIssuers[1].makeAmount(1),
  },
  {
    rule: 'wantAtLeast',
    amount: allIssuers[2].makeAmount(0),
  },
]);
const bobMoolaForSimPayments = [bobMoolaPayment, undefined, undefined];

const {
  escrowReceipt: allegedBobEscrowReceipt,
  claimPayoff: bobClaimPayoff,
} = await zoeInstance.escrow(
  bobMoolaForSimOfferDesc,
  bobMoolaForSimPayments,
);

Then Bob uses this escrow receipt to make an offer.

const offerOk = await autoswap.makeOffer(bobEscrowReceipt);

Now Bob can claim his payoff:

const bobClaimPayoffSeat = await bobClaimPayoff.unwrap();
const [bobMoolaPayoff, bobSimoleanPayoff, ...] = await bobClaimPayoffSeat.getPayoff();

Copy link
Contributor

@Chris-Hibbert Chris-Hibbert left a comment

Choose a reason for hiding this comment

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

This looks really good. Of course, I have some suggestions.

core/zoe/contractUtils.js Outdated Show resolved Hide resolved
core/zoe/contractUtils.js Outdated Show resolved Hide resolved
@@ -121,6 +124,38 @@ const makeZoe = () => {
adminState.setQuantitiesFor(offerIds, reallocation);
},

reallocateAndBurn: async (offerIds, reallocation, burnQuantities) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like it should replace reallocate. It's nearly the same, and it wouldn't be any trouble to have a single method with an optional arg that doesn't need any duplicate code. I think that simplifies the calling code as well.

core/zoe/contracts/autoswap/calculateSwap.js Outdated Show resolved Hide resolved
core/zoe/contracts/autoswap/calculateSwap.js Outdated Show resolved Hide resolved
core/zoe/docs/autoswap.md Outdated Show resolved Hide resolved
test/unitTests/core/zoe/contracts/test-autoswap.js Outdated Show resolved Hide resolved
core/zoe/contracts/autoswap/calculateSwap.js Show resolved Hide resolved
tokenAQ,
feeInTenthOfPercent = 3,
) => {
const feeTokenAQ = mult(divide(tokenAQ, 1000), feeInTenthOfPercent);
Copy link
Contributor

Choose a reason for hiding this comment

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

So, no fees when tokenAQ is less than 334. I don't think there's anything to do about that except allow for a minimum offer. In Satoshi, a minimum of 1000 wouldn't be outlandish.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very good point. I was going to say that we could override such that the minimum fee is one, but that could be disastrous if the minimum denomination of tokenA is valuable. I think we should do a separate pass once we start using real assets and not moola/simolean to make sure that we are covering unit transformation in the right way. For instance, ERC20 tokens have a decimals function:

/**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }

undefined,
undefined,
]);
t.deepEquals(simoleanAmount, issuers[1].makeAmount(1));
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just an aside, since in an earlier conversation I thought you were saying that the price one gets is as quoted. Any algorithm that adjusts the price as players buy (and maintains liquidity) won't actually sell more than a marginal number of shares at its current price before the price changes.

Bob only gets quoted a price of 2 because the autoswap is losing due to roundoff. the pool goes from 10,5 to 12,4 (also throwing off the 'constant' product for the next trade). If the values were all multiplied by 100, then an offer of 200 would get 84 rather than 100.

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 there's probably some communication problem here - a difference of terms or something. But, a user will get the tokens they were quoted in getPrice as long as no changes to the liquidity pool happened in between.

@katelynsills
Copy link
Contributor Author

I'm closing this PR in favor of a new set of Zoe PRs in which #123, #125, and #136 are squashed and then pulled apart again.

@katelynsills katelynsills mentioned this pull request Dec 4, 2019
5 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants