Skip to content

Commit 92bfdd5

Browse files
committed
fix: revamp multipoolAutoswap: liquidity bug, in vs. out prices
distinguish prices for stated input from prices for stated output. provide swapIn/swapOut to distinguish stated In/Out prices ensure proportionality is enforced when adding liquidity re-use the test jig match the autoswap API Tests for all combinations of swapIn/Out to/from central pool and trading secondary currencies Update documentation
1 parent 73f373b commit 92bfdd5

File tree

10 files changed

+1189
-401
lines changed

10 files changed

+1189
-401
lines changed

packages/zoe/src/contractSupport/bondingCurves.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ export const getInputPrice = ({
3737
const numerator = multiply(inputWithFee, outputReserve);
3838
const denominator = add(multiply(inputReserve, 10000), inputWithFee);
3939

40-
const outputValue = floorDivide(numerator, denominator);
41-
return outputValue;
40+
return floorDivide(numerator, denominator);
4241
};
4342

4443
/**
@@ -127,12 +126,14 @@ export const calcSecondaryRequired = ({
127126
return secondaryIn;
128127
}
129128

130-
const exact =
131-
multiply(centralIn, secondaryPool) === multiply(secondaryIn, centralPool);
132129
const scaledSecondary = floorDivide(
133130
multiply(centralIn, secondaryPool),
134131
centralPool,
135132
);
133+
const exact =
134+
multiply(centralIn, secondaryPool) ===
135+
multiply(scaledSecondary, centralPool);
136+
136137
// doesn't match the x-y-k.pdf paper, but more correct. When the ratios are
137138
// exactly equal, lPrime is exactly l * (1 + alpha) and adding one is wrong
138139
return exact ? scaledSecondary : 1 + scaledSecondary;

packages/zoe/src/contracts/exported.js

+30
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,36 @@
5454
* @property {() => Record<string, Amount>} getPoolAllocation get an
5555
* AmountKeywordRecord showing the current balances in the pool.
5656
*/
57+
58+
/**
59+
* @typedef {Object} MultipoolAutoswapPublicFacet
60+
* @property {(issuer: Issuer, keyword: Keyword) => Pool} addPool
61+
* add a new liquidity pool
62+
* @property {() => Promise<Invitation>} makeSwapInvitation synonym for
63+
* makeSwapInInvitation
64+
* @property {() => Promise<Invitation>} makeSwapInInvitation make an invitation
65+
* that allows one to do a swap in which the In amount is specified and the Out
66+
* amount is calculated
67+
* @property {() => Promise<Invitation>} makeSwapOutInvitation make an invitation
68+
* that allows one to do a swap in which the Out amount is specified and the In
69+
* amount is calculated
70+
* @property {() => Promise<Invitation>} makeAddLiquidityInvitation make an
71+
* invitation that allows one to add liquidity to the pool.
72+
* @property {() => Promise<Invitation>} makeRemoveLiquidityInvitation make an
73+
* invitation that allows one to remove liquidity from the pool.
74+
* @property {() => Issuer} getLiquidityIssuer
75+
* @property {() => number} getLiquiditySupply get the current value of
76+
* liquidity held by investors.
77+
* @property {(amountIn: Amount, brandOut: Brand) => Amount} getInputPrice
78+
* calculate the amount of brandOut that will be returned if the amountIn is
79+
* offered using makeSwapInInvitation at the current price.
80+
* @property {(amountOut: Amount, brandIn: Brand) => Amount} getOutputPrice
81+
* calculate the amount of brandIn that is required in order to get amountOut
82+
* using makeSwapOutInvitation at the current price
83+
* @property {() => Record<string, Amount>} getPoolAllocation get an
84+
* AmountKeywordRecord showing the current balances in the pool.
85+
*/
86+
5787
/**
5888
* @typedef {Object} AutomaticRefundPublicFacet
5989
* @property {() => number} getOffersCount
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,82 @@
1+
import '../../../exported';
2+
13
/**
4+
* Build functions to calculate prices for multipoolAutoswap. Two methods are
5+
* returned. In one the caller specifies the amount they will pay, and in the
6+
* other they specify the amount they wish to receive.
27
*
38
* @param {(brand: Brand) => boolean} isSecondary
49
* @param {(brand: Brand) => boolean} isCentral
510
* @param {(brand: Brand) => Pool} getPool
611
*/
712

8-
import '../../../exported';
9-
1013
export const makeGetCurrentPrice = (isSecondary, isCentral, getPool) => {
1114
/**
12-
* `getCurrentPrice` calculates the result of a trade, given a certain
15+
* `getOutputForGivenInput` calculates the result of a trade, given a certain
1316
* amount of digital assets in.
14-
* @param {Amount} amountIn - the amount of digital assets to be
15-
* sent in
16-
* @param {Brand} brandOut - the brand of the requested payment.
17+
* @param {Amount} amountIn - the amount of digital
18+
* assets to be sent in
19+
* @param {Brand} brandOut - The brand of asset desired
20+
* @return {Amount} the amount that would be paid out at the current price.
1721
*/
18-
// eslint-disable-next-line consistent-return
19-
const getCurrentPrice = (amountIn, brandOut) => {
20-
// BrandIn could either be the central token brand, or one of
21-
// the secondary token brands.
22+
const getOutputForGivenInput = (amountIn, brandOut) => {
2223
const { brand: brandIn, value: inputValue } = amountIn;
2324

2425
if (isCentral(brandIn) && isSecondary(brandOut)) {
25-
return getPool(brandOut).getCurrentPrice(true, inputValue);
26+
return getPool(brandOut).getCentralToSecondaryInputPrice(inputValue);
27+
}
28+
29+
if (isSecondary(brandIn) && isCentral(brandOut)) {
30+
return getPool(brandIn).getSecondaryToCentralInputPrice(inputValue);
31+
}
32+
33+
if (isSecondary(brandIn) && isSecondary(brandOut)) {
34+
// We must do two consecutive calls to get the price: from
35+
// the brandIn to the central token, then from the central
36+
// token to the brandOut.
37+
const centralTokenAmount = getPool(
38+
brandIn,
39+
).getSecondaryToCentralInputPrice(inputValue);
40+
return getPool(brandOut).getCentralToSecondaryInputPrice(
41+
centralTokenAmount.value,
42+
);
43+
}
44+
45+
throw new Error(`brands were not recognized`);
46+
};
47+
48+
/**
49+
* `getInputForGivenOutput` calculates the amount of assets required to be
50+
* provided in order to obtain a specified gain.
51+
* @param {Amount} amountOut - the amount of digital assets desired
52+
* @param {Brand} brandIn - The brand of asset desired
53+
* @return {Amount} The amount required to be paid in order to gain amountOut
54+
*/
55+
const getInputForGivenOutput = (amountOut, brandIn) => {
56+
const { brand: brandOut, value: outputValue } = amountOut;
57+
58+
if (isCentral(brandIn) && isSecondary(brandOut)) {
59+
return getPool(brandOut).getCentralToSecondaryOutputPrice(outputValue);
2660
}
2761

2862
if (isSecondary(brandIn) && isCentral(brandOut)) {
29-
return getPool(brandIn).getCurrentPrice(false, inputValue);
63+
return getPool(brandIn).getSecondaryToCentralOutputPrice(outputValue);
3064
}
3165

3266
if (isSecondary(brandIn) && isSecondary(brandOut)) {
33-
// We must do two consecutive `getCurrentPrice` calls: from
67+
// We must do two consecutive calls to get the price: from
3468
// the brandIn to the central token, then from the central
3569
// token to the brandOut.
36-
const centralTokenAmount = getPool(brandIn).getCurrentPrice(
37-
false,
38-
inputValue,
70+
const centralTokenAmount = getPool(
71+
brandIn,
72+
).getSecondaryToCentralOutputPrice(outputValue);
73+
return getPool(brandOut).getCentralToSecondaryOutputPrice(
74+
centralTokenAmount.value,
3975
);
40-
return getPool(brandOut).getCurrentPrice(true, centralTokenAmount.value);
4176
}
4277

4378
throw new Error(`brands were not recognized`);
4479
};
4580

46-
return getCurrentPrice;
81+
return { getOutputForGivenInput, getInputForGivenOutput };
4782
};

packages/zoe/src/contracts/multipoolAutoswap/multipoolAutoswap.js

+39-15
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { makeMakeRemoveLiquidityInvitation } from './removeLiquidity';
1313
import '../../../exported';
1414

1515
/**
16-
* Autoswap is a rewrite of Uniswap. Please see the documentation for
17-
* more
18-
* https://agoric.com/documentation/zoe/guide/contracts/autoswap.html
16+
* Multipool Autoswap is a rewrite of Uniswap that supports multiple liquidity
17+
* pools, and direct exchanges across pools. Please see the documentation for
18+
* more: https://agoric.com/documentation/zoe/guide/contracts/autoswap.html
1919
*
2020
* We expect that this contract will have tens to hundreds of issuers.
2121
* Each liquidity pool is between the central token and a secondary
@@ -30,9 +30,25 @@ import '../../../exported';
3030
*
3131
* When the contract is instantiated, the central token is specified
3232
* in the terms. Separate invitations are available by calling methods
33-
* on the publicFacet for adding and removing liquidity, and for
34-
* making trades. Other publicFacet operations support monitoring
35-
* prices and the sizes of pools.
33+
* on the publicFacet for adding and removing liquidity and for
34+
* making trades. Other publicFacet operations support querying
35+
* prices and the sizes of pools. New Pools can be created with addPool().
36+
*
37+
* When making trades or requesting prices, the caller must specify that either
38+
* the input price (swapIn, getInputPrice) or the output price (swapOut,
39+
* getOutPutPrice) is fixed. For swaps, the required keywords are `In` for the
40+
* trader's `give` amount, and `Out` for the trader's `want` amount.
41+
* getInputPrice and getOutPrice each take an Amount for the direction that is
42+
* being specified, and just a brand for the desired value, which is returned as
43+
* the appropriate amount.
44+
*
45+
* When adding and removing liquidity, the keywords are Central, Secondary, and
46+
* Liquidity. adding liquidity has Central and Secondary in the `give` section,
47+
* while removing liquidity has `want` and `give` swapped.
48+
*
49+
* Transactions that don't require an invitation include addPool, and the
50+
* queries: getInputPrice, getOutputPrice, getPoolAllocation,
51+
* getLiquidityIssuer, and getLiquiditySupply.
3652
*
3753
* @type {ContractStartFn}
3854
*/
@@ -51,20 +67,23 @@ const start = zcf => {
5167
const isSecondary = secondaryBrandToPool.has;
5268
const isCentral = brand => brand === centralBrand;
5369

70+
const getLiquiditySupply = brand => getPool(brand).getLiquiditySupply();
5471
const getLiquidityIssuer = brand => getPool(brand).getLiquidityIssuer();
5572
const addPool = makeAddPool(zcf, isSecondary, initPool, centralBrand);
5673
const getPoolAllocation = brand => {
5774
return getPool(brand)
5875
.getPoolSeat()
5976
.getCurrentAllocation();
6077
};
61-
const getCurrentPrice = makeGetCurrentPrice(isSecondary, isCentral, getPool);
62-
const makeSwapInvitation = makeMakeSwapInvitation(
63-
zcf,
64-
isSecondary,
65-
isCentral,
66-
getPool,
67-
);
78+
79+
const {
80+
getOutputForGivenInput,
81+
getInputForGivenOutput,
82+
} = makeGetCurrentPrice(isSecondary, isCentral, getPool);
83+
const {
84+
makeSwapInInvitation,
85+
makeSwapOutInvitation,
86+
} = makeMakeSwapInvitation(zcf, isSecondary, isCentral, getPool);
6887
const makeAddLiquidityInvitation = makeMakeAddLiquidityInvitation(
6988
zcf,
7089
getPool,
@@ -75,12 +94,17 @@ const start = zcf => {
7594
getPool,
7695
);
7796

97+
/** @type {MultipoolAutoswapPublicFacet} */
7898
const publicFacet = {
7999
addPool,
80100
getPoolAllocation,
81101
getLiquidityIssuer,
82-
getCurrentPrice,
83-
makeSwapInvitation,
102+
getLiquiditySupply,
103+
getInputPrice: getOutputForGivenInput,
104+
getOutputPrice: getInputForGivenOutput,
105+
makeSwapInvitation: makeSwapInInvitation,
106+
makeSwapInInvitation,
107+
makeSwapOutInvitation,
84108
makeAddLiquidityInvitation,
85109
makeRemoveLiquidityInvitation,
86110
};

0 commit comments

Comments
 (0)