@agoric/inter-protocol 0.13.2-dev-70ba9b0.0 → 0.13.2-dev-ed5ee58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/bundle-econCommitteeCharter-meta.js +78 -78
- package/bundles/bundle-econCommitteeCharter.js +1 -1
- package/bundles/bundle-feeDistributor-meta.js +61 -61
- package/bundles/bundle-feeDistributor.js +1 -1
- package/bundles/bundle-fluxAggregator-meta.js +188 -188
- package/bundles/bundle-fluxAggregator.js +1 -1
- package/bundles/bundle-psm-meta.js +112 -112
- package/bundles/bundle-psm.js +1 -1
- package/bundles/bundle-reserve-meta.js +110 -114
- package/bundles/bundle-reserve.js +1 -1
- package/bundles/bundle-scaledPriceAuthority-meta.js +78 -78
- package/bundles/bundle-stakeFactory-meta.js +118 -118
- package/bundles/bundle-stakeFactory.js +1 -1
- package/bundles/bundle-vaultFactory-meta.js +130 -130
- package/bundles/bundle-vaultFactory.js +1 -1
- package/exported.js +0 -1
- package/package.json +16 -16
- package/scripts/deploy-contracts.js +0 -2
- package/scripts/init-core.js +0 -16
- package/src/feeDistributor.js +1 -1
- package/src/proposals/addAssetToVault.js +4 -57
- package/src/proposals/committee-proposal.js +3 -6
- package/src/proposals/core-proposal.js +1 -73
- package/src/proposals/demoIssuers.js +1 -232
- package/src/proposals/econ-behaviors.js +10 -193
- package/src/proposals/sim-behaviors.js +1 -1
- package/src/reserve/assetReserve.js +9 -170
- package/src/reserve/params.js +1 -4
- package/src/vaultFactory/liquidateIncrementally.js +16 -141
- package/src/vaultFactory/liquidateMinimum.js +19 -21
- package/src/vaultFactory/params.js +0 -29
- package/src/vaultFactory/types.js +0 -6
- package/src/vaultFactory/vault.js +1 -3
- package/src/vaultFactory/vaultDirector.js +1 -33
- package/src/vaultFactory/vaultFactory.js +2 -12
- package/src/vaultFactory/vaultManager.js +10 -217
- package/bundles/bundle-amm-meta.js +0 -686
- package/bundles/bundle-amm.js +0 -1
- package/bundles/bundle-interchainPool-meta.js +0 -606
- package/bundles/bundle-interchainPool.js +0 -1
- package/bundles/bundle-liquidateIncrementally-meta.js +0 -470
- package/bundles/bundle-liquidateIncrementally.js +0 -1
- package/bundles/bundle-liquidateMinimum-meta.js +0 -470
- package/bundles/bundle-liquidateMinimum.js +0 -1
- package/scripts/add-initial-liquidity.js +0 -179
- package/src/interchainPool.js +0 -134
- package/src/vpool-xyk-amm/AttackersGuide.md +0 -89
- package/src/vpool-xyk-amm/addLiquidity.js +0 -221
- package/src/vpool-xyk-amm/addPool.js +0 -200
- package/src/vpool-xyk-amm/constantProduct/AMM-Trade.jpeg +0 -0
- package/src/vpool-xyk-amm/constantProduct/README.md +0 -204
- package/src/vpool-xyk-amm/constantProduct/calcFees.js +0 -93
- package/src/vpool-xyk-amm/constantProduct/calcSwapPrices.js +0 -83
- package/src/vpool-xyk-amm/constantProduct/core.js +0 -145
- package/src/vpool-xyk-amm/constantProduct/defaults.js +0 -3
- package/src/vpool-xyk-amm/constantProduct/getXY.js +0 -48
- package/src/vpool-xyk-amm/constantProduct/internal-types.js +0 -114
- package/src/vpool-xyk-amm/constantProduct/invariants.js +0 -39
- package/src/vpool-xyk-amm/constantProduct/swap.js +0 -270
- package/src/vpool-xyk-amm/constantProduct/types.js +0 -30
- package/src/vpool-xyk-amm/doublePool.js +0 -200
- package/src/vpool-xyk-amm/estimate.js +0 -175
- package/src/vpool-xyk-amm/multipoolMarketMaker.js +0 -530
- package/src/vpool-xyk-amm/params.js +0 -59
- package/src/vpool-xyk-amm/pool.js +0 -398
- package/src/vpool-xyk-amm/priceAuthority.js +0 -92
- package/src/vpool-xyk-amm/removeLiquidity.js +0 -29
- package/src/vpool-xyk-amm/singlePool.js +0 -88
- package/src/vpool-xyk-amm/swap.js +0 -68
- package/src/vpool-xyk-amm/types.js +0 -152
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { AmountMath } from '@agoric/ertp';
|
|
2
|
-
import { makeNotifierFromAsyncIterable } from '@agoric/notifier';
|
|
3
|
-
import { E } from '@endo/far';
|
|
4
|
-
|
|
5
|
-
const { entries } = Object;
|
|
6
|
-
|
|
7
|
-
const dec6 = 10n ** 6n;
|
|
8
|
-
const dec4 = 10n ** 4n;
|
|
9
|
-
|
|
10
|
-
const config = {
|
|
11
|
-
Central: { IST: 10_000n * dec6 },
|
|
12
|
-
Secondary: { IbcATOM: 500n * dec4 }, // 20 IST per ATOM
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const getOfferResult = async (instance, wallet, walletAdmin) => {
|
|
16
|
-
const offerIt = E(walletAdmin).getOffersNotifier();
|
|
17
|
-
let result;
|
|
18
|
-
console.log('awaiting offers...');
|
|
19
|
-
for await (const offers of makeNotifierFromAsyncIterable(offerIt)) {
|
|
20
|
-
console.log('offers iterator next:', offers);
|
|
21
|
-
offers
|
|
22
|
-
.filter(
|
|
23
|
-
({ status, invitationQuery }) =>
|
|
24
|
-
status === 'complete' && invitationQuery.instance === instance,
|
|
25
|
-
)
|
|
26
|
-
.forEach(async ({ id }) => {
|
|
27
|
-
result = await E(wallet).lookup('offerResult', id);
|
|
28
|
-
console.log({ facet: result });
|
|
29
|
-
});
|
|
30
|
-
if (result) break;
|
|
31
|
-
}
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
* @param { { agoricNames: ERef<NameHub>, scratch: ERef<import('@agoric/solo/src/scratch').ScratchPad>, wallet: ERef<WalletAdmin>, zoe: ERef<ZoeService>} } homeP
|
|
38
|
-
* @param { {now?: () => number }} _endowments
|
|
39
|
-
*
|
|
40
|
-
* @typedef {ReturnType<typeof import('../../wallet/api/src/lib-wallet').makeWalletRoot>['admin']} Wallet
|
|
41
|
-
*/
|
|
42
|
-
const addInitialLiquidity = async (homeP, { now = () => Date.now() }) => {
|
|
43
|
-
const { wallet, agoricNames, zoe, scratch } = E.get(homeP);
|
|
44
|
-
|
|
45
|
-
const walletBridge = E(wallet).getBridge();
|
|
46
|
-
|
|
47
|
-
const getService = async name => {
|
|
48
|
-
console.log('lookup instance', name);
|
|
49
|
-
const instance = await E(agoricNames).lookup('instance', name);
|
|
50
|
-
const pub = E(zoe).getPublicFacet(instance);
|
|
51
|
-
return { instance, pub };
|
|
52
|
-
};
|
|
53
|
-
const amm = await getService('amm');
|
|
54
|
-
|
|
55
|
-
const addUsingWallet = async () => {
|
|
56
|
-
console.log('getting purses from walletBridge...');
|
|
57
|
-
const { value: pStates } = await E(
|
|
58
|
-
E(walletBridge).getPursesNotifier(),
|
|
59
|
-
).getUpdateSince();
|
|
60
|
-
console.log(pStates);
|
|
61
|
-
|
|
62
|
-
const toAmt = detail => {
|
|
63
|
-
const [[name, value]] = entries(detail);
|
|
64
|
-
const { pursePetname } =
|
|
65
|
-
pStates.find(p => p.brandPetname === name) ||
|
|
66
|
-
assert.fail(`cannot find purse ${name}`);
|
|
67
|
-
return { pursePetname, value };
|
|
68
|
-
};
|
|
69
|
-
const proposalTemplate = {
|
|
70
|
-
give: {
|
|
71
|
-
Central: toAmt(config.Central),
|
|
72
|
-
Secondary: toAmt(config.Secondary),
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
console.log({ proposalTemplate });
|
|
76
|
-
|
|
77
|
-
console.log('amm.makeAddLiquidityInvitation...');
|
|
78
|
-
const invitation = await E(amm.pub).makeAddLiquidityInvitation();
|
|
79
|
-
console.log({ invitation });
|
|
80
|
-
|
|
81
|
-
const offerConfig = {
|
|
82
|
-
id: `${now()}`,
|
|
83
|
-
invitation,
|
|
84
|
-
instanceHandle: amm.instance,
|
|
85
|
-
proposalTemplate,
|
|
86
|
-
requestContext: undefined,
|
|
87
|
-
};
|
|
88
|
-
console.log('addOffer', offerConfig);
|
|
89
|
-
const added = await E(walletBridge).addOffer(offerConfig);
|
|
90
|
-
console.log({ added });
|
|
91
|
-
|
|
92
|
-
const result = await getOfferResult(
|
|
93
|
-
amm.instance,
|
|
94
|
-
wallet,
|
|
95
|
-
E(wallet).getAdminFacet(),
|
|
96
|
-
);
|
|
97
|
-
console.log({ result });
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const addUsingZoe = async () => {
|
|
101
|
-
const { keys, values } = Object;
|
|
102
|
-
/** @type {<K extends string, V>(entries: [K, V][]) => Record<K, V>} */
|
|
103
|
-
const toRecord = Object.fromEntries;
|
|
104
|
-
|
|
105
|
-
console.log('getting purses, issuers from wallet...');
|
|
106
|
-
const [purseEntries, { value: issuerEntries }] = await Promise.all([
|
|
107
|
-
E(wallet).getPurses(),
|
|
108
|
-
E(E(walletBridge).getIssuersNotifier()).getUpdateSince(),
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
|
-
// @ts-expect-error Petname can be string[]
|
|
112
|
-
const purses = toRecord(purseEntries);
|
|
113
|
-
// @ts-expect-error Petname can be string[]
|
|
114
|
-
const issuers = toRecord(issuerEntries);
|
|
115
|
-
console.log({ purses: keys(purses), issuers: keys(issuers) });
|
|
116
|
-
assert(
|
|
117
|
-
issuers.IbcATOM,
|
|
118
|
-
'Issuers not complete. Try again after purses are provisioned',
|
|
119
|
-
);
|
|
120
|
-
const liqIssuer = E(amm.pub).getLiquidityIssuer(issuers.IbcATOM.brand);
|
|
121
|
-
const liqBrand = await E(liqIssuer).getBrand();
|
|
122
|
-
|
|
123
|
-
/** @param {Record<string, bigint>} detail */
|
|
124
|
-
const toAmt = detail => {
|
|
125
|
-
const [[name, value]] = entries(detail);
|
|
126
|
-
const issuer = issuers[name];
|
|
127
|
-
assert(issuer);
|
|
128
|
-
return AmountMath.make(issuer.brand, value);
|
|
129
|
-
};
|
|
130
|
-
/** @param {Amount} amt */
|
|
131
|
-
const withdraw = async amt => {
|
|
132
|
-
for await (const purse of values(purses)) {
|
|
133
|
-
const b2 = await E(purse).getAllegedBrand();
|
|
134
|
-
if (b2 === amt.brand) {
|
|
135
|
-
return E(purse).withdraw(amt);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
throw Error(`no purse found for ${amt.brand}`);
|
|
139
|
-
};
|
|
140
|
-
const proposal = {
|
|
141
|
-
want: {
|
|
142
|
-
Liquidity: AmountMath.makeEmpty(liqBrand),
|
|
143
|
-
},
|
|
144
|
-
give: {
|
|
145
|
-
Central: toAmt(config.Central),
|
|
146
|
-
Secondary: toAmt(config.Secondary),
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
console.log({ proposal });
|
|
150
|
-
console.log('withdraw payments...', proposal.give);
|
|
151
|
-
const payments = {
|
|
152
|
-
Central: await withdraw(proposal.give.Central),
|
|
153
|
-
Secondary: await withdraw(proposal.give.Secondary),
|
|
154
|
-
};
|
|
155
|
-
console.log('payments', payments);
|
|
156
|
-
console.log('amm.makeAddLiquidityInvitation...');
|
|
157
|
-
const invitation = await E(amm.pub).makeAddLiquidityInvitation();
|
|
158
|
-
console.log({ invitation });
|
|
159
|
-
|
|
160
|
-
const seat = await E(zoe).offer(invitation, proposal, payments);
|
|
161
|
-
console.log({ seat });
|
|
162
|
-
|
|
163
|
-
const result = await E(seat).getOfferResult();
|
|
164
|
-
console.log({ result });
|
|
165
|
-
|
|
166
|
-
const payouts = await E(seat).getPayouts();
|
|
167
|
-
console.log('initialLiquidityPayouts', keys(payouts));
|
|
168
|
-
await E(scratch).set('initialLiquidityPayouts', payouts);
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
console.log(
|
|
172
|
-
addUsingWallet,
|
|
173
|
-
'does not handle want Liquidity so we do not use it',
|
|
174
|
-
);
|
|
175
|
-
await addUsingZoe();
|
|
176
|
-
};
|
|
177
|
-
harden(addInitialLiquidity);
|
|
178
|
-
|
|
179
|
-
export default addInitialLiquidity;
|
package/src/interchainPool.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { E, Far } from '@endo/far';
|
|
2
|
-
import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp';
|
|
3
|
-
import {
|
|
4
|
-
offerTo,
|
|
5
|
-
atomicTransfer,
|
|
6
|
-
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
7
|
-
import { MIN_INITIAL_POOL_LIQUIDITY_KEY } from './vpool-xyk-amm/params.js';
|
|
8
|
-
|
|
9
|
-
const { Fail, quote: q } = assert;
|
|
10
|
-
|
|
11
|
-
const COSMOS_DECIMALS = 6;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Given sufficient IST, create an issuer for an IBC denom
|
|
15
|
-
* and an invitation to add a pool for it to the AMM.
|
|
16
|
-
*
|
|
17
|
-
* @param {ZCF<InterchainPoolTerms>} zcf
|
|
18
|
-
* @param {{
|
|
19
|
-
* bankManager: ERef<BankManager>,
|
|
20
|
-
* }} privateArgs
|
|
21
|
-
*
|
|
22
|
-
* @typedef {{
|
|
23
|
-
* amm: Instance,
|
|
24
|
-
* }} InterchainPoolTerms
|
|
25
|
-
*
|
|
26
|
-
* @typedef {{
|
|
27
|
-
* denom: string,
|
|
28
|
-
* decimalPlaces: number,
|
|
29
|
-
* }} AssetDetail
|
|
30
|
-
*/
|
|
31
|
-
export const start = (zcf, { bankManager }) => {
|
|
32
|
-
const { amm } = zcf.getTerms();
|
|
33
|
-
|
|
34
|
-
const zoe = zcf.getZoeService();
|
|
35
|
-
/** @type {ERef<GovernedPublicFacet<XYKAMMPublicFacet>>} */
|
|
36
|
-
const ammPub = E(zoe).getPublicFacet(amm);
|
|
37
|
-
|
|
38
|
-
let kwNonce = 0;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {ZCFSeat} seat
|
|
42
|
-
* @param {AssetDetail} detail
|
|
43
|
-
*/
|
|
44
|
-
const step1Handler = async (seat, detail) => {
|
|
45
|
-
assert.typeof(
|
|
46
|
-
detail,
|
|
47
|
-
'object',
|
|
48
|
-
'offer arg must be { denom, decimalPlaces? }',
|
|
49
|
-
);
|
|
50
|
-
const { denom, decimalPlaces = COSMOS_DECIMALS } = detail;
|
|
51
|
-
assert.typeof(denom, 'string');
|
|
52
|
-
assert.typeof(decimalPlaces, 'number');
|
|
53
|
-
|
|
54
|
-
const minimumCentral = await E(ammPub).getAmount(
|
|
55
|
-
MIN_INITIAL_POOL_LIQUIDITY_KEY,
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const {
|
|
59
|
-
give: { Central: centralAmt },
|
|
60
|
-
} = seat.getProposal();
|
|
61
|
-
AmountMath.isGTE(centralAmt, minimumCentral) ||
|
|
62
|
-
Fail`at least ${q(minimumCentral)} required; only ${q(centralAmt)} given`;
|
|
63
|
-
|
|
64
|
-
const interAsset = makeIssuerKit(
|
|
65
|
-
denom,
|
|
66
|
-
AssetKind.NAT,
|
|
67
|
-
harden({ decimalPlaces }),
|
|
68
|
-
);
|
|
69
|
-
kwNonce += 1;
|
|
70
|
-
|
|
71
|
-
await zcf.saveIssuer(interAsset.issuer, `Interchain${kwNonce}`);
|
|
72
|
-
|
|
73
|
-
/** @type { OfferHandler } */
|
|
74
|
-
const step2Handler = async seat2 => {
|
|
75
|
-
const {
|
|
76
|
-
give: { Secondary: secondaryAmt },
|
|
77
|
-
} = seat2.getProposal();
|
|
78
|
-
AmountMath.coerce(interAsset.brand, secondaryAmt);
|
|
79
|
-
|
|
80
|
-
const liquidityIssuer = await E(ammPub).addIssuer(
|
|
81
|
-
interAsset.issuer,
|
|
82
|
-
`Interchain${kwNonce}`,
|
|
83
|
-
);
|
|
84
|
-
await zcf.saveIssuer(liquidityIssuer, `Liquidity${kwNonce}`);
|
|
85
|
-
|
|
86
|
-
const proposal = harden({
|
|
87
|
-
give: {
|
|
88
|
-
Central: centralAmt,
|
|
89
|
-
Secondary: secondaryAmt,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
atomicTransfer(zcf, seat, seat2, { Central: centralAmt });
|
|
94
|
-
|
|
95
|
-
const invitation = await E(ammPub).addPoolInvitation();
|
|
96
|
-
const { userSeatPromise, deposited } = await offerTo(
|
|
97
|
-
zcf,
|
|
98
|
-
invitation,
|
|
99
|
-
undefined,
|
|
100
|
-
proposal,
|
|
101
|
-
seat2,
|
|
102
|
-
);
|
|
103
|
-
return E.when(deposited, _ => {
|
|
104
|
-
seat.exit();
|
|
105
|
-
seat2.exit();
|
|
106
|
-
|
|
107
|
-
return E(userSeatPromise).getOfferResult();
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const keyword = denom; // ISSUE #5412: should not show up in all wallets.
|
|
112
|
-
const proposedName = denom;
|
|
113
|
-
const [invitation] = await Promise.all([
|
|
114
|
-
zcf.makeInvitation(step2Handler, 'interchain pool step 2'),
|
|
115
|
-
E(bankManager).addAsset(
|
|
116
|
-
denom,
|
|
117
|
-
keyword,
|
|
118
|
-
proposedName,
|
|
119
|
-
interAsset, // with mint
|
|
120
|
-
),
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
return harden({
|
|
124
|
-
issuer: interAsset.issuer,
|
|
125
|
-
invitation,
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const publicFacet = Far('InterchainPoolGateway', {
|
|
130
|
-
makeInterchainPoolInvitation: () =>
|
|
131
|
-
zcf.makeInvitation(step1Handler, 'interchain pool step 1'),
|
|
132
|
-
});
|
|
133
|
-
return { publicFacet };
|
|
134
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# An Attacker's guide to the Constant Product AMM
|
|
2
|
-
|
|
3
|
-
This is an incomplete list of potential weak points that an attacker might want to focus
|
|
4
|
-
on when looking for ways to violate the integrity of the AMM. It's here to help defenders,
|
|
5
|
-
as "attacker's mindset" is a good way to focus attention for the defender. The list should
|
|
6
|
-
correspond pretty closely to the set of assurances that the AMM aims to support.
|
|
7
|
-
|
|
8
|
-
The main guarantee that an
|
|
9
|
-
[Automated Market Maker](https://mason.gmu.edu/~rhanson/mktscore.pdf) makes is that
|
|
10
|
-
sufficiently liquid pools will always retain enough collateral through any sequence of
|
|
11
|
-
trades to be able to continue trading. The caveat just means that when the amount of
|
|
12
|
-
liquidity in the pool is quite small with respect to desired trades, trades will quickly
|
|
13
|
-
push the balance to extreme enough prices that no further trading will take place. If the
|
|
14
|
-
pool is liquid enough, trades will push the price enough out of equilibrium that it will
|
|
15
|
-
be very attractive for arbitrageurs to restore the price. Anything that allows traders or
|
|
16
|
-
liquidity providers to violate that expectation is a problem.
|
|
17
|
-
|
|
18
|
-
## Multiple Pools
|
|
19
|
-
|
|
20
|
-
`addPool` and `addLiquidity` are publicly available. That means someone could maliciously
|
|
21
|
-
create pools or provide insufficient liquidity. The requirement of a minimum initial
|
|
22
|
-
liquidity should be enough to prevent misuse of these APIs from hurting us.
|
|
23
|
-
|
|
24
|
-
Since `addPool` is public, someone could add a look-alike issuer for a well-known
|
|
25
|
-
currency and make it harder for people to find the pool they intend to trade with. As
|
|
26
|
-
long as people are dealing wiht Brands and Issuers, they shouldn't be confused by
|
|
27
|
-
this. Does the Wallet UI provide enough clarity in the face of this?
|
|
28
|
-
|
|
29
|
-
Zoe holds the collaterral for all the pools in common. We segragate it by Pool, so each
|
|
30
|
-
pool's allocation includes some IST (the central currency) and some of another
|
|
31
|
-
collateral. The doublePool trades move IST between pools, but Zoe's rights conservation
|
|
32
|
-
enforcement ensure that none is ever gained or lost.
|
|
33
|
-
|
|
34
|
-
## Fees
|
|
35
|
-
|
|
36
|
-
Fees are collected as trading takes place, and kept in a distinct Zoe seat. The creator of
|
|
37
|
-
the AMM should be the only one who can collect the fees. Can that invitation be hijacked
|
|
38
|
-
from whoever gets that facet during bootstrap? Is there another way to steal fees?
|
|
39
|
-
|
|
40
|
-
Can users structure trades so they can pay less in fees than intended?
|
|
41
|
-
|
|
42
|
-
## Providing Liquidity
|
|
43
|
-
|
|
44
|
-
Traders can provide liquidity and get liquidity tokens for a particular pool. The goal is
|
|
45
|
-
that they earn a share of the fees that were collected while their liquidity remained in
|
|
46
|
-
the pool. Can liquidity providers gain extra fees over other providers, or can they cause
|
|
47
|
-
traders to be disadvantaged?
|
|
48
|
-
|
|
49
|
-
Can liquidity providers extact more from the pool than they should be able to?
|
|
50
|
-
|
|
51
|
-
## Math
|
|
52
|
-
|
|
53
|
-
The whole design is predicated on the X*Y=K invariant. That invariant says that trading
|
|
54
|
-
should not change the product of X and Y, but in actuality, roundoff and fees are both
|
|
55
|
-
allowed to change the product. Roundoff should increase it ever so slightly, except in
|
|
56
|
-
cases where there are small amounts in the pool, where it can be significant. Fees are
|
|
57
|
-
added to the pool, so they change the size of the pool, but should be interpreted as
|
|
58
|
-
taking place after the trade rebalances the amounts. The equations are explained in detail
|
|
59
|
-
in `constantProduct/README.md`.
|
|
60
|
-
|
|
61
|
-
Adding liquidity adds funds to both pools so, of course, it changes X and Y. But (except
|
|
62
|
-
in the case of `AddLiquidityAtRate`), it should keep X and Y in the same proportion.
|
|
63
|
-
|
|
64
|
-
I don't know whether anyone else has done an implementation of the multi-pool swap that
|
|
65
|
-
works like ours. If neither X nor Y is IST, then we trade X for IST and IST for
|
|
66
|
-
Y. Attributing fees in this case took some art. This is also addressed in
|
|
67
|
-
`constantProduct/README.md`.
|
|
68
|
-
|
|
69
|
-
Adding liquidity while changing the ratio of assets is done in two steps in order to
|
|
70
|
-
prevent people from using a single step as a way to avoid fees on trades. Users shouldn't
|
|
71
|
-
be able to use `AddLiquidityAtRate` to avoid paying fees on what is effectively a trade.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## Economics
|
|
75
|
-
|
|
76
|
-
The Inter Protocol mechanism is envisioned as a major provider of liquidity. It may leave
|
|
77
|
-
its liquidity in the pool over the long term, and never or seldom collect earnings. Does
|
|
78
|
-
this affect prices or trading volumes adverselyj? (More liquidity is expected to provide
|
|
79
|
-
better prices for traders, so this would be a surprise.)
|
|
80
|
-
|
|
81
|
-
Other constant product markets have performed reasonably well through periods of high
|
|
82
|
-
volatillity. Is there any reason to expect the design variations we've introduced to have
|
|
83
|
-
trouble where others did not?
|
|
84
|
-
|
|
85
|
-
## Governance
|
|
86
|
-
|
|
87
|
-
The poolFee and protocolFee can be changed by governance. Can unauthorized parties
|
|
88
|
-
interfere with this? Is there any hazard here beyond the possibility that the holders of
|
|
89
|
-
this power might raise rates more than some users would be happy with?
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assertProposalShape,
|
|
3
|
-
calcSecondaryRequired,
|
|
4
|
-
natSafeMath,
|
|
5
|
-
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
6
|
-
import { AmountMath } from '@agoric/ertp';
|
|
7
|
-
|
|
8
|
-
import '@agoric/zoe/exported.js';
|
|
9
|
-
|
|
10
|
-
const { Fail, quote: q } = assert;
|
|
11
|
-
|
|
12
|
-
const { add, multiply } = natSafeMath;
|
|
13
|
-
/**
|
|
14
|
-
* @param {ZCF} zcf
|
|
15
|
-
* @param {(brand: Brand) => XYKPool} getPool
|
|
16
|
-
*/
|
|
17
|
-
const makeMakeAddLiquidityInvitation = (zcf, getPool) => {
|
|
18
|
-
const addLiquidity = seat => {
|
|
19
|
-
assertProposalShape(seat, {
|
|
20
|
-
give: {
|
|
21
|
-
Central: null,
|
|
22
|
-
Secondary: null,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Get the brand of the secondary token so we can identify the liquidity pool.
|
|
27
|
-
const secondaryBrand = seat.getProposal().give.Secondary.brand;
|
|
28
|
-
const pool = getPool(secondaryBrand);
|
|
29
|
-
const liquidityIssuer = pool.getLiquidityIssuer();
|
|
30
|
-
const liquidityBrand = zcf.getBrandForIssuer(liquidityIssuer);
|
|
31
|
-
seat.getProposal().want.Liquidity.brand === liquidityBrand ||
|
|
32
|
-
Fail`liquidity brand must be ${q(liquidityBrand)}`;
|
|
33
|
-
|
|
34
|
-
return pool.addLiquidity(seat);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const makeAddLiquidityInvitation = () =>
|
|
38
|
-
zcf.makeInvitation(addLiquidity, 'multipool amm add liquidity');
|
|
39
|
-
|
|
40
|
-
return makeAddLiquidityInvitation;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// The desired ratio requires a wider range than K would support.
|
|
44
|
-
const imbalancedRequest = (desiredRatio, startK) => {
|
|
45
|
-
if (desiredRatio > 1.0) {
|
|
46
|
-
return desiredRatio > Number(startK);
|
|
47
|
-
} else {
|
|
48
|
-
return Number(startK) * desiredRatio < 1n;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
harden(imbalancedRequest);
|
|
52
|
-
// exported for testing only
|
|
53
|
-
export { imbalancedRequest };
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The pool has poolX and poolY currently. The user wants to add liquidity of
|
|
57
|
-
* giveX and giveY, but the ratios are probably not the same. We want to adjust
|
|
58
|
-
* the pool to have the ratio (poolX + giveX) / (poolY + giveY), without
|
|
59
|
-
* changing K, the product of the two sides.
|
|
60
|
-
*
|
|
61
|
-
* Calculate targetX and targetY, which multiply to the same product as
|
|
62
|
-
* poolX * poolY, but are in the ratio we'll end at. From this we can produce
|
|
63
|
-
* a proposed trade that maintains K, but changes the ratio, so we can add
|
|
64
|
-
* liquidity at the desired ratio.
|
|
65
|
-
*
|
|
66
|
-
* endX = poolX + giveX; endY = poolY + giveY
|
|
67
|
-
* desiredRatio = endX / endY
|
|
68
|
-
* targetY = sqrt(startK / desiredRatio)
|
|
69
|
-
* targetX = desiredRatio * targetY
|
|
70
|
-
* so targetK equals startK because we square targetY
|
|
71
|
-
* targetK = targetX * targetY = desiredRatio * (startK / desiredRatio)
|
|
72
|
-
*
|
|
73
|
-
* Since startK/endK is less than one, and we have to worry about early loss of
|
|
74
|
-
* precision, we round and convert to bigint as the last step
|
|
75
|
-
*
|
|
76
|
-
* If the desired ratio (the ratio of the sums of contributed amounts and pool
|
|
77
|
-
* balances) is larger than K (which most often happens when the pool balances
|
|
78
|
-
* are small) we somewhat arbitrarily refuse the transaction.
|
|
79
|
-
* For instance, if the desired ratio is 1000:1 because one currency uses 12
|
|
80
|
-
* digits and the other 9, and the pool has single digits of liquidity, then
|
|
81
|
-
* it would be better to manually adjust rather than using this approach.
|
|
82
|
-
*
|
|
83
|
-
* @param {Amount<'nat'>} poolX
|
|
84
|
-
* @param {Amount<'nat'>} poolY
|
|
85
|
-
* @param {Amount<'nat'>} giveX
|
|
86
|
-
* @param {Amount<'nat'>} giveY
|
|
87
|
-
* @returns {{targetX: Amount<'nat'>, targetY: Amount<'nat'> }}
|
|
88
|
-
*/
|
|
89
|
-
export const balancesToReachRatio = (poolX, poolY, giveX, giveY) => {
|
|
90
|
-
const startK = multiply(poolX.value, poolY.value);
|
|
91
|
-
const endX = add(poolX.value, giveX.value);
|
|
92
|
-
const endY = add(poolY.value, giveY.value);
|
|
93
|
-
const desiredRatio = Number(endX) / Number(endY);
|
|
94
|
-
|
|
95
|
-
if (imbalancedRequest(desiredRatio, startK)) {
|
|
96
|
-
return {
|
|
97
|
-
targetX: AmountMath.makeEmpty(poolX.brand),
|
|
98
|
-
targetY: AmountMath.makeEmpty(poolY.brand),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
const targetY = Math.sqrt(Number(startK) / desiredRatio);
|
|
102
|
-
const targetX = targetY * desiredRatio;
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
targetX: AmountMath.make(poolX.brand, BigInt(Math.trunc(targetX))),
|
|
106
|
-
targetY: AmountMath.make(poolY.brand, BigInt(Math.trunc(targetY))),
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* @param {ZCF} zcf
|
|
112
|
-
* @param {(b: Brand) => XYKPool} getPool
|
|
113
|
-
* @param {(i: Brand) => VirtualPool} provideVPool
|
|
114
|
-
* @param {ZCFSeat} feeSeat
|
|
115
|
-
* @param {(b: Brand) => PoolFacets['helper']} getPoolHelper
|
|
116
|
-
*/
|
|
117
|
-
const makeMakeAddLiquidityAtRateInvitation = (
|
|
118
|
-
zcf,
|
|
119
|
-
getPool,
|
|
120
|
-
provideVPool,
|
|
121
|
-
feeSeat,
|
|
122
|
-
getPoolHelper,
|
|
123
|
-
) => {
|
|
124
|
-
const addLiquidityAtRate = seat => {
|
|
125
|
-
assertProposalShape(seat, {
|
|
126
|
-
give: {
|
|
127
|
-
Central: null,
|
|
128
|
-
Secondary: null,
|
|
129
|
-
},
|
|
130
|
-
want: { Liquidity: null },
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const giveAlloc = seat.getProposal().give;
|
|
134
|
-
/** @type {Amount<'nat'>} */
|
|
135
|
-
const secondaryAmount = giveAlloc.Secondary;
|
|
136
|
-
const secondaryBrand = secondaryAmount.brand;
|
|
137
|
-
const centralBrand = giveAlloc.Central.brand;
|
|
138
|
-
const pool = getPool(secondaryBrand);
|
|
139
|
-
// Step 1: trade to adjust the pool's price
|
|
140
|
-
// A figure out the ratio of the inputs
|
|
141
|
-
// B figure out how X*Y changes to reach that ratio (ignoring fees)
|
|
142
|
-
const centralPoolAmount = pool.getCentralAmount();
|
|
143
|
-
const secondaryPoolAmount = pool.getSecondaryAmount();
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
AmountMath.isEmpty(centralPoolAmount) &&
|
|
147
|
-
AmountMath.isEmpty(secondaryPoolAmount)
|
|
148
|
-
) {
|
|
149
|
-
return pool.addLiquidity(seat);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const { targetX: newCentral, targetY: newSecondary } = balancesToReachRatio(
|
|
153
|
-
centralPoolAmount,
|
|
154
|
-
secondaryPoolAmount,
|
|
155
|
-
giveAlloc.Central,
|
|
156
|
-
giveAlloc.Secondary,
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const vPool = provideVPool(secondaryBrand);
|
|
160
|
-
const poolSeat = pool.getPoolSeat();
|
|
161
|
-
const transferForTrade = (prices, incrementKey, decrementKey) => {
|
|
162
|
-
seat.decrementBy(harden({ [incrementKey]: prices.swapperGives }));
|
|
163
|
-
poolSeat.decrementBy(harden({ [decrementKey]: prices.yDecrement }));
|
|
164
|
-
seat.incrementBy(harden({ [decrementKey]: prices.swapperGets }));
|
|
165
|
-
feeSeat.incrementBy(harden({ Fee: prices.protocolFee }));
|
|
166
|
-
poolSeat.incrementBy(harden({ [incrementKey]: prices.xIncrement }));
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// 1C Stage the changes for the trade
|
|
170
|
-
if (AmountMath.isGTE(newCentral, centralPoolAmount)) {
|
|
171
|
-
const prices = vPool.getPriceForOutput(
|
|
172
|
-
AmountMath.makeEmpty(centralBrand),
|
|
173
|
-
AmountMath.subtract(secondaryPoolAmount, newSecondary),
|
|
174
|
-
);
|
|
175
|
-
transferForTrade(prices, 'Central', 'Secondary');
|
|
176
|
-
} else {
|
|
177
|
-
const prices = vPool.getPriceForInput(
|
|
178
|
-
AmountMath.subtract(newSecondary, secondaryPoolAmount),
|
|
179
|
-
AmountMath.makeEmpty(centralBrand),
|
|
180
|
-
);
|
|
181
|
-
transferForTrade(prices, 'Secondary', 'Central');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Step 2: add remaining liquidity
|
|
185
|
-
const stagedAllocation = poolSeat.getStagedAllocation();
|
|
186
|
-
const centralPoolAfterTrade = stagedAllocation.Central;
|
|
187
|
-
const secondaryPoolAfterTrade = stagedAllocation.Secondary;
|
|
188
|
-
const userAllocation = seat.getStagedAllocation();
|
|
189
|
-
|
|
190
|
-
const secondaryRequired = AmountMath.make(
|
|
191
|
-
secondaryBrand,
|
|
192
|
-
calcSecondaryRequired(
|
|
193
|
-
userAllocation.Central.value,
|
|
194
|
-
centralPoolAfterTrade.value,
|
|
195
|
-
secondaryPoolAfterTrade.value,
|
|
196
|
-
userAllocation.Secondary.value,
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
return getPoolHelper(secondaryBrand).addLiquidityActual(
|
|
201
|
-
pool,
|
|
202
|
-
seat,
|
|
203
|
-
secondaryRequired,
|
|
204
|
-
poolSeat.getStagedAllocation().Central,
|
|
205
|
-
feeSeat,
|
|
206
|
-
);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const makeAddLiquidityInvitation = () =>
|
|
210
|
-
zcf.makeInvitation(
|
|
211
|
-
addLiquidityAtRate,
|
|
212
|
-
'multipool amm add liquidity at rate',
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
return makeAddLiquidityInvitation;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
harden(makeMakeAddLiquidityInvitation);
|
|
219
|
-
harden(makeMakeAddLiquidityAtRateInvitation);
|
|
220
|
-
|
|
221
|
-
export { makeMakeAddLiquidityInvitation, makeMakeAddLiquidityAtRateInvitation };
|