@agoric/inter-protocol 0.13.3 → 0.13.4-dev-800c2bb.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/README.md +26 -20
- package/bundles/bundle-auctioneer-js-meta.json +722 -0
- package/bundles/bundle-auctioneer.js +1 -0
- package/bundles/bundle-econCommitteeCharter-js-meta.json +514 -0
- package/bundles/bundle-econCommitteeCharter.js +1 -0
- package/bundles/bundle-feeDistributor-js-meta.json +458 -0
- package/bundles/bundle-feeDistributor.js +1 -1
- package/bundles/bundle-fluxAggregatorKit-js-meta.json +674 -0
- package/bundles/bundle-fluxAggregatorKit.js +1 -0
- package/bundles/bundle-psm-js-meta.json +666 -0
- package/bundles/bundle-psm.js +1 -1
- package/bundles/bundle-reserve-js-meta.json +662 -0
- package/bundles/bundle-reserve.js +1 -1
- package/bundles/bundle-scaledPriceAuthority-js-meta.json +542 -0
- package/bundles/bundle-scaledPriceAuthority.js +1 -1
- package/bundles/bundle-vaultFactory-js-meta.json +786 -0
- package/bundles/bundle-vaultFactory.js +1 -1
- package/exported.js +0 -2
- package/package.json +34 -31
- package/scripts/add-collateral-core.js +14 -4
- package/scripts/build-bundles.js +9 -2
- package/scripts/deploy-contracts.js +1 -4
- package/scripts/init-core.js +20 -38
- package/scripts/invite-committee-core.js +1 -1
- package/scripts/manual-price-feed.js +2 -2
- package/scripts/price-feed-core.js +7 -6
- package/scripts/start-local-chain.sh +84 -0
- package/src/auction/auctionBook.js +755 -0
- package/src/auction/auctioneer.js +720 -0
- package/src/auction/offerBook.js +221 -0
- package/src/auction/params.js +174 -0
- package/src/auction/scheduleMath.js +167 -0
- package/src/auction/scheduler.js +370 -0
- package/src/auction/sortedOffers.js +136 -0
- package/src/auction/util.js +55 -0
- package/src/clientSupport.js +324 -0
- package/src/collectFees.js +8 -14
- package/src/contractSupport.js +24 -68
- package/src/econCommitteeCharter.js +161 -77
- package/src/feeDistributor.js +60 -20
- package/src/index.js +2 -0
- package/src/interest-math.js +1 -4
- package/src/interest.js +20 -13
- package/src/price/README.md +57 -0
- package/src/price/fluxAggregatorContract.js +190 -0
- package/src/price/fluxAggregatorKit.js +375 -0
- package/src/price/priceOracleKit.js +127 -0
- package/src/price/roundsManager.js +726 -0
- package/src/proposals/addAssetToVault.js +89 -94
- package/src/proposals/committee-proposal.js +141 -40
- package/src/proposals/core-proposal.js +100 -167
- package/src/proposals/econ-behaviors.js +320 -564
- package/src/proposals/price-feed-proposal.js +175 -78
- package/src/proposals/startEconCommittee.js +25 -12
- package/src/proposals/startPSM.js +231 -211
- package/src/proposals/utils.js +31 -17
- package/src/psm/README.md +71 -0
- package/src/psm/psm.js +272 -160
- package/src/psm/types.js +2 -0
- package/src/reserve/assetReserve.js +56 -413
- package/src/reserve/assetReserveKit.js +303 -0
- package/src/reserve/doc.md +1 -3
- package/src/reserve/params.js +2 -5
- package/src/typeGuards.js +13 -0
- package/src/vaultFactory/Liquidation.md +100 -0
- package/src/vaultFactory/burn.js +2 -1
- package/src/vaultFactory/liquidation.js +270 -108
- package/src/vaultFactory/math.js +95 -0
- package/src/vaultFactory/orderedVaultStore.js +0 -1
- package/src/vaultFactory/params.js +133 -112
- package/src/vaultFactory/prioritizedVaults.js +36 -79
- package/src/vaultFactory/storeUtils.js +67 -9
- package/src/vaultFactory/type-imports.js +2 -0
- package/src/vaultFactory/types.js +22 -26
- package/src/vaultFactory/vault.js +772 -709
- package/src/vaultFactory/vaultDirector.js +480 -483
- package/src/vaultFactory/vaultFactory.js +99 -60
- package/src/vaultFactory/vaultHolder.js +121 -95
- package/src/vaultFactory/vaultKit.js +39 -31
- package/src/vaultFactory/vaultManager.js +1249 -868
- package/bundles/bundle-amm-meta.js +0 -618
- package/bundles/bundle-amm.js +0 -1
- package/bundles/bundle-feeDistributor-meta.js +0 -346
- package/bundles/bundle-interchainPool-meta.js +0 -534
- package/bundles/bundle-interchainPool.js +0 -1
- package/bundles/bundle-liquidateIncrementally-meta.js +0 -442
- package/bundles/bundle-liquidateIncrementally.js +0 -1
- package/bundles/bundle-liquidateMinimum-meta.js +0 -442
- package/bundles/bundle-liquidateMinimum.js +0 -1
- package/bundles/bundle-psm-meta.js +0 -538
- package/bundles/bundle-psmCharter-meta.js +0 -418
- package/bundles/bundle-psmCharter.js +0 -1
- package/bundles/bundle-reserve-meta.js +0 -542
- package/bundles/bundle-scaledPriceAuthority-meta.js +0 -442
- package/bundles/bundle-stakeFactory-meta.js +0 -574
- package/bundles/bundle-stakeFactory.js +0 -1
- package/bundles/bundle-vaultFactory-meta.js +0 -598
- package/scripts/add-initial-liquidity.js +0 -180
- package/src/collect.js +0 -13
- package/src/interchainPool.js +0 -135
- package/src/makeTracer.js +0 -36
- package/src/my-lien.js +0 -85
- package/src/proposals/demoIssuers.js +0 -685
- package/src/proposals/sim-behaviors.js +0 -40
- package/src/psm/psmCharter.js +0 -139
- package/src/stakeFactory/README.md +0 -62
- package/src/stakeFactory/attestation.js +0 -267
- package/src/stakeFactory/attestationTool.js +0 -66
- package/src/stakeFactory/constants.js +0 -16
- package/src/stakeFactory/params.js +0 -31
- package/src/stakeFactory/stakeFactory.js +0 -236
- package/src/stakeFactory/stakeFactoryKit.js +0 -455
- package/src/stakeFactory/stakeFactoryManager.js +0 -296
- package/src/stakeFactory/types.js +0 -42
- package/src/vaultFactory/liquidateIncrementally.js +0 -350
- package/src/vaultFactory/liquidateMinimum.js +0 -100
- package/src/vpool-xyk-amm/AttackersGuide.md +0 -89
- package/src/vpool-xyk-amm/addLiquidity.js +0 -222
- package/src/vpool-xyk-amm/addPool.js +0 -197
- 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 -99
- package/src/vpool-xyk-amm/constantProduct/calcSwapPrices.js +0 -85
- package/src/vpool-xyk-amm/constantProduct/core.js +0 -153
- package/src/vpool-xyk-amm/constantProduct/defaults.js +0 -5
- package/src/vpool-xyk-amm/constantProduct/getXY.js +0 -52
- package/src/vpool-xyk-amm/constantProduct/internal-types.js +0 -116
- package/src/vpool-xyk-amm/constantProduct/invariants.js +0 -43
- package/src/vpool-xyk-amm/constantProduct/swap.js +0 -272
- package/src/vpool-xyk-amm/constantProduct/types.js +0 -32
- package/src/vpool-xyk-amm/doublePool.js +0 -194
- package/src/vpool-xyk-amm/estimate.js +0 -155
- package/src/vpool-xyk-amm/multipoolMarketMaker.js +0 -520
- package/src/vpool-xyk-amm/params.js +0 -61
- package/src/vpool-xyk-amm/pool.js +0 -396
- package/src/vpool-xyk-amm/priceAuthority.js +0 -94
- package/src/vpool-xyk-amm/removeLiquidity.js +0 -31
- package/src/vpool-xyk-amm/singlePool.js +0 -73
- package/src/vpool-xyk-amm/swap.js +0 -69
- package/src/vpool-xyk-amm/types.js +0 -154
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import { E } from '@endo/eventual-send';
|
|
4
|
-
import {
|
|
5
|
-
ceilMultiplyBy,
|
|
6
|
-
offerTo,
|
|
7
|
-
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
8
|
-
import { AmountMath } from '@agoric/ertp';
|
|
9
|
-
import { Far } from '@endo/marshal';
|
|
10
|
-
|
|
11
|
-
import { makeTracer } from '../makeTracer.js';
|
|
12
|
-
|
|
13
|
-
const trace = makeTracer('LiqMin', false);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* This contract liquidates the minimum amount of vault's collateral necessary
|
|
17
|
-
* to satisfy the debt. It uses the AMM's swapOut, which sells no more than
|
|
18
|
-
* necessary. Because it has offer safety, it can refuse the trade. When that
|
|
19
|
-
* happens, we fall back to selling using the default strategy, which currently
|
|
20
|
-
* uses the AMM's swapIn instead.
|
|
21
|
-
*
|
|
22
|
-
* @param {ZCF<{
|
|
23
|
-
* amm: XYKAMMPublicFacet,
|
|
24
|
-
* }>} zcf
|
|
25
|
-
*/
|
|
26
|
-
const start = async zcf => {
|
|
27
|
-
const { amm } = zcf.getTerms();
|
|
28
|
-
trace('start', zcf.getTerms());
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {ZCFSeat} debtorSeat
|
|
32
|
-
* @param {object} options
|
|
33
|
-
* @param {Amount<'nat'>} options.debt Debt before penalties
|
|
34
|
-
* @param {Ratio} options.penaltyRate
|
|
35
|
-
*/
|
|
36
|
-
const handleLiquidationOffer = async (
|
|
37
|
-
debtorSeat,
|
|
38
|
-
{ debt: originalDebt, penaltyRate },
|
|
39
|
-
) => {
|
|
40
|
-
// XXX does not distribute penalties anywhere
|
|
41
|
-
const { zcfSeat: penaltyPoolSeat } = zcf.makeEmptySeatKit();
|
|
42
|
-
const penalty = ceilMultiplyBy(originalDebt, penaltyRate);
|
|
43
|
-
const debtWithPenalty = AmountMath.add(originalDebt, penalty);
|
|
44
|
-
const debtBrand = originalDebt.brand;
|
|
45
|
-
const {
|
|
46
|
-
give: { In: amountIn },
|
|
47
|
-
} = debtorSeat.getProposal();
|
|
48
|
-
|
|
49
|
-
const swapInvitation = E(amm).makeSwapInvitation();
|
|
50
|
-
const liqProposal = harden({
|
|
51
|
-
give: { In: amountIn },
|
|
52
|
-
want: { Out: AmountMath.makeEmpty(debtBrand) },
|
|
53
|
-
});
|
|
54
|
-
trace(`OFFER TO DEBT: `, debtWithPenalty, amountIn);
|
|
55
|
-
const { deposited } = await offerTo(
|
|
56
|
-
zcf,
|
|
57
|
-
swapInvitation,
|
|
58
|
-
undefined, // The keywords were mapped already
|
|
59
|
-
liqProposal,
|
|
60
|
-
debtorSeat,
|
|
61
|
-
debtorSeat,
|
|
62
|
-
{ stopAfter: debtWithPenalty },
|
|
63
|
-
);
|
|
64
|
-
const amounts = await deposited;
|
|
65
|
-
trace(`Liq results`, {
|
|
66
|
-
debtWithPenalty,
|
|
67
|
-
amountIn,
|
|
68
|
-
paid: debtorSeat.getCurrentAllocation(),
|
|
69
|
-
amounts,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Now we need to know how much was sold so we can pay off the debt.
|
|
73
|
-
// We can use this seat because only liquidation adds debt brand to it..
|
|
74
|
-
const debtPaid = debtorSeat.getAmountAllocated('Out', debtBrand);
|
|
75
|
-
const penaltyPaid = AmountMath.min(penalty, debtPaid);
|
|
76
|
-
|
|
77
|
-
// Allocate penalty portion of proceeds to a seat that will hold it for transfer to reserve
|
|
78
|
-
penaltyPoolSeat.incrementBy(
|
|
79
|
-
debtorSeat.decrementBy(harden({ Out: penaltyPaid })),
|
|
80
|
-
);
|
|
81
|
-
zcf.reallocate(penaltyPoolSeat, debtorSeat);
|
|
82
|
-
|
|
83
|
-
debtorSeat.exit();
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @type {ERef<Liquidator>}
|
|
88
|
-
*/
|
|
89
|
-
const creatorFacet = Far('debtorInvitationCreator (minimum)', {
|
|
90
|
-
makeLiquidateInvitation: () =>
|
|
91
|
-
zcf.makeInvitation(handleLiquidationOffer, 'Liquidate'),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
return harden({ creatorFacet });
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/** @typedef {ContractOf<typeof start>} LiquidationContract */
|
|
98
|
-
|
|
99
|
-
harden(start);
|
|
100
|
-
export { start };
|
|
@@ -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,222 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
assertProposalShape,
|
|
5
|
-
calcSecondaryRequired,
|
|
6
|
-
natSafeMath,
|
|
7
|
-
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
8
|
-
import { AmountMath } from '@agoric/ertp';
|
|
9
|
-
|
|
10
|
-
import '@agoric/zoe/exported.js';
|
|
11
|
-
|
|
12
|
-
const { details: X, quote: q } = assert;
|
|
13
|
-
|
|
14
|
-
const { add, multiply } = natSafeMath;
|
|
15
|
-
/**
|
|
16
|
-
* @param {ZCF} zcf
|
|
17
|
-
* @param {(brand: Brand) => XYKPool} getPool
|
|
18
|
-
*/
|
|
19
|
-
const makeMakeAddLiquidityInvitation = (zcf, getPool) => {
|
|
20
|
-
const addLiquidity = seat => {
|
|
21
|
-
assertProposalShape(seat, {
|
|
22
|
-
give: {
|
|
23
|
-
Central: null,
|
|
24
|
-
Secondary: null,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Get the brand of the secondary token so we can identify the liquidity pool.
|
|
29
|
-
const secondaryBrand = seat.getProposal().give.Secondary.brand;
|
|
30
|
-
const pool = getPool(secondaryBrand);
|
|
31
|
-
const liquidityIssuer = pool.getLiquidityIssuer();
|
|
32
|
-
const liquidityBrand = zcf.getBrandForIssuer(liquidityIssuer);
|
|
33
|
-
seat.getProposal().want.Liquidity.brand === liquidityBrand ||
|
|
34
|
-
assert.fail(X`liquidity brand must be ${q(liquidityBrand)}`);
|
|
35
|
-
|
|
36
|
-
return pool.addLiquidity(seat);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const makeAddLiquidityInvitation = () =>
|
|
40
|
-
zcf.makeInvitation(addLiquidity, 'multipool amm add liquidity');
|
|
41
|
-
|
|
42
|
-
return makeAddLiquidityInvitation;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// The desired ratio requires a wider range than K would support.
|
|
46
|
-
const imbalancedRequest = (desiredRatio, startK) => {
|
|
47
|
-
if (desiredRatio > 1.0) {
|
|
48
|
-
return desiredRatio > Number(startK);
|
|
49
|
-
} else {
|
|
50
|
-
return Number(startK) * desiredRatio < 1n;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
harden(imbalancedRequest);
|
|
54
|
-
// exported for testing only
|
|
55
|
-
export { imbalancedRequest };
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The pool has poolX and poolY currently. The user wants to add liquidity of
|
|
59
|
-
* giveX and giveY, but the ratios are probably not the same. We want to adjust
|
|
60
|
-
* the pool to have the ratio (poolX + giveX) / (poolY + giveY), without
|
|
61
|
-
* changing K, the product of the two sides.
|
|
62
|
-
*
|
|
63
|
-
* Calculate targetX and targetY, which multiply to the same product as
|
|
64
|
-
* poolX * poolY, but are in the ratio we'll end at. From this we can produce
|
|
65
|
-
* a proposed trade that maintains K, but changes the ratio, so we can add
|
|
66
|
-
* liquidity at the desired ratio.
|
|
67
|
-
*
|
|
68
|
-
* endX = poolX + giveX; endY = poolY + giveY
|
|
69
|
-
* desiredRatio = endX / endY
|
|
70
|
-
* targetY = sqrt(startK / desiredRatio)
|
|
71
|
-
* targetX = desiredRatio * targetY
|
|
72
|
-
* so targetK equals startK because we square targetY
|
|
73
|
-
* targetK = targetX * targetY = desiredRatio * (startK / desiredRatio)
|
|
74
|
-
*
|
|
75
|
-
* Since startK/endK is less than one, and we have to worry about early loss of
|
|
76
|
-
* precision, we round and convert to bigint as the last step
|
|
77
|
-
*
|
|
78
|
-
* If the desired ratio (the ratio of the sums of contributed amounts and pool
|
|
79
|
-
* balances) is larger than K (which most often happens when the pool balances
|
|
80
|
-
* are small) we somewhat arbitrarily refuse the transaction.
|
|
81
|
-
* For instance, if the desired ratio is 1000:1 because one currency uses 12
|
|
82
|
-
* digits and the other 9, and the pool has single digits of liquidity, then
|
|
83
|
-
* it would be better to manually adjust rather than using this approach.
|
|
84
|
-
*
|
|
85
|
-
* @param {Amount<'nat'>} poolX
|
|
86
|
-
* @param {Amount<'nat'>} poolY
|
|
87
|
-
* @param {Amount<'nat'>} giveX
|
|
88
|
-
* @param {Amount<'nat'>} giveY
|
|
89
|
-
* @returns {{targetX: Amount<'nat'>, targetY: Amount<'nat'> }}
|
|
90
|
-
*/
|
|
91
|
-
export const balancesToReachRatio = (poolX, poolY, giveX, giveY) => {
|
|
92
|
-
const startK = multiply(poolX.value, poolY.value);
|
|
93
|
-
const endX = add(poolX.value, giveX.value);
|
|
94
|
-
const endY = add(poolY.value, giveY.value);
|
|
95
|
-
const desiredRatio = Number(endX) / Number(endY);
|
|
96
|
-
|
|
97
|
-
if (imbalancedRequest(desiredRatio, startK)) {
|
|
98
|
-
return {
|
|
99
|
-
targetX: AmountMath.makeEmpty(poolX.brand),
|
|
100
|
-
targetY: AmountMath.makeEmpty(poolY.brand),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const targetY = Math.sqrt(Number(startK) / desiredRatio);
|
|
104
|
-
const targetX = targetY * desiredRatio;
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
targetX: AmountMath.make(poolX.brand, BigInt(Math.trunc(targetX))),
|
|
108
|
-
targetY: AmountMath.make(poolY.brand, BigInt(Math.trunc(targetY))),
|
|
109
|
-
};
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* @param {ZCF} zcf
|
|
114
|
-
* @param {(b: Brand) => XYKPool} getPool
|
|
115
|
-
* @param {(i: Brand) => VirtualPool} provideVPool
|
|
116
|
-
* @param {ZCFSeat} feeSeat
|
|
117
|
-
* @param {(b: Brand) => PoolFacets['helper']} getPoolHelper
|
|
118
|
-
*/
|
|
119
|
-
const makeMakeAddLiquidityAtRateInvitation = (
|
|
120
|
-
zcf,
|
|
121
|
-
getPool,
|
|
122
|
-
provideVPool,
|
|
123
|
-
feeSeat,
|
|
124
|
-
getPoolHelper,
|
|
125
|
-
) => {
|
|
126
|
-
const addLiquidityAtRate = seat => {
|
|
127
|
-
assertProposalShape(seat, {
|
|
128
|
-
give: {
|
|
129
|
-
Central: null,
|
|
130
|
-
Secondary: null,
|
|
131
|
-
},
|
|
132
|
-
want: { Liquidity: null },
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const giveAlloc = seat.getProposal().give;
|
|
136
|
-
const secondaryAmount = giveAlloc.Secondary;
|
|
137
|
-
const secondaryBrand = secondaryAmount.brand;
|
|
138
|
-
const centralBrand = giveAlloc.Central.brand;
|
|
139
|
-
const pool = getPool(secondaryBrand);
|
|
140
|
-
// Step 1: trade to adjust the pool's price
|
|
141
|
-
// A figure out the ratio of the inputs
|
|
142
|
-
// B figure out how X*Y changes to reach that ratio (ignoring fees)
|
|
143
|
-
const centralPoolAmount = pool.getCentralAmount();
|
|
144
|
-
const secondaryPoolAmount = pool.getSecondaryAmount();
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
AmountMath.isEmpty(centralPoolAmount) &&
|
|
148
|
-
AmountMath.isEmpty(secondaryPoolAmount)
|
|
149
|
-
) {
|
|
150
|
-
return pool.addLiquidity(seat);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const { targetX: newCentral, targetY: newSecondary } = balancesToReachRatio(
|
|
154
|
-
centralPoolAmount,
|
|
155
|
-
secondaryPoolAmount,
|
|
156
|
-
giveAlloc.Central,
|
|
157
|
-
giveAlloc.Secondary,
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
const vPool = provideVPool(secondaryBrand);
|
|
161
|
-
const poolSeat = pool.getPoolSeat();
|
|
162
|
-
const transferForTrade = (prices, incrementKey, decrementKey) => {
|
|
163
|
-
seat.decrementBy(harden({ [incrementKey]: prices.swapperGives }));
|
|
164
|
-
poolSeat.decrementBy(harden({ [decrementKey]: prices.yDecrement }));
|
|
165
|
-
seat.incrementBy(harden({ [decrementKey]: prices.swapperGets }));
|
|
166
|
-
feeSeat.incrementBy(harden({ Fee: prices.protocolFee }));
|
|
167
|
-
poolSeat.incrementBy(harden({ [incrementKey]: prices.xIncrement }));
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// 1C Stage the changes for the trade
|
|
171
|
-
if (AmountMath.isGTE(newCentral, centralPoolAmount)) {
|
|
172
|
-
const prices = vPool.getPriceForOutput(
|
|
173
|
-
AmountMath.makeEmpty(centralBrand),
|
|
174
|
-
AmountMath.subtract(secondaryPoolAmount, newSecondary),
|
|
175
|
-
);
|
|
176
|
-
transferForTrade(prices, 'Central', 'Secondary');
|
|
177
|
-
} else {
|
|
178
|
-
const prices = vPool.getPriceForInput(
|
|
179
|
-
AmountMath.subtract(newSecondary, secondaryPoolAmount),
|
|
180
|
-
AmountMath.makeEmpty(centralBrand),
|
|
181
|
-
);
|
|
182
|
-
transferForTrade(prices, 'Secondary', 'Central');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Step 2: add remaining liquidity
|
|
186
|
-
const stagedAllocation = poolSeat.getStagedAllocation();
|
|
187
|
-
const centralPoolAfterTrade = stagedAllocation.Central;
|
|
188
|
-
const secondaryPoolAfterTrade = stagedAllocation.Secondary;
|
|
189
|
-
const userAllocation = seat.getStagedAllocation();
|
|
190
|
-
|
|
191
|
-
const secondaryRequired = AmountMath.make(
|
|
192
|
-
secondaryBrand,
|
|
193
|
-
calcSecondaryRequired(
|
|
194
|
-
userAllocation.Central.value,
|
|
195
|
-
centralPoolAfterTrade.value,
|
|
196
|
-
secondaryPoolAfterTrade.value,
|
|
197
|
-
userAllocation.Secondary.value,
|
|
198
|
-
),
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
return getPoolHelper(secondaryBrand).addLiquidityActual(
|
|
202
|
-
pool,
|
|
203
|
-
seat,
|
|
204
|
-
secondaryRequired,
|
|
205
|
-
poolSeat.getStagedAllocation().Central,
|
|
206
|
-
feeSeat,
|
|
207
|
-
);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const makeAddLiquidityInvitation = () =>
|
|
211
|
-
zcf.makeInvitation(
|
|
212
|
-
addLiquidityAtRate,
|
|
213
|
-
'multipool amm add liquidity at rate',
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
return makeAddLiquidityInvitation;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
harden(makeMakeAddLiquidityInvitation);
|
|
220
|
-
harden(makeMakeAddLiquidityAtRateInvitation);
|
|
221
|
-
|
|
222
|
-
export { makeMakeAddLiquidityInvitation, makeMakeAddLiquidityAtRateInvitation };
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import { E } from '@endo/eventual-send';
|
|
4
|
-
import { AmountMath, AssetKind } from '@agoric/ertp';
|
|
5
|
-
import { assertProposalShape } from '@agoric/zoe/src/contractSupport/index.js';
|
|
6
|
-
|
|
7
|
-
import { definePoolKind } from './pool.js';
|
|
8
|
-
|
|
9
|
-
const { details: X, quote: q } = assert;
|
|
10
|
-
|
|
11
|
-
const DISPLAY_INFO = harden({ decimalPlaces: 6 });
|
|
12
|
-
|
|
13
|
-
/** @typedef {import('@agoric/vat-data').Baggage} Baggage */
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param {ZCF} zcf
|
|
17
|
-
* @param {(Brand) => boolean} isInSecondaries
|
|
18
|
-
* @param {import('@agoric/store/src/stores/store-utils.js').AtomicProvider<Brand, ZCFMint>} brandToLiquidityMintProvider
|
|
19
|
-
* @param {() => (secondaryBrand: Brand) => Promise<void>} getAddIssuerToReserve
|
|
20
|
-
*/
|
|
21
|
-
export const makeAddIssuer = (
|
|
22
|
-
zcf,
|
|
23
|
-
isInSecondaries,
|
|
24
|
-
brandToLiquidityMintProvider,
|
|
25
|
-
getAddIssuerToReserve,
|
|
26
|
-
) => {
|
|
27
|
-
/**
|
|
28
|
-
* Add a new issuer. If we previously received a request for the same issuer,
|
|
29
|
-
* we'll return the issuer or a promise for it.
|
|
30
|
-
*
|
|
31
|
-
* @param {import('./pool.js').MethodContext} _context
|
|
32
|
-
* @param {Issuer} secondaryIssuer
|
|
33
|
-
* @param {string} keyword
|
|
34
|
-
*/
|
|
35
|
-
const addIssuer = async (_context, secondaryIssuer, keyword) => {
|
|
36
|
-
const [secondaryAssetKind, secondaryBrand] = await Promise.all([
|
|
37
|
-
E(secondaryIssuer).getAssetKind(),
|
|
38
|
-
E(secondaryIssuer).getBrand(),
|
|
39
|
-
]);
|
|
40
|
-
// AWAIT ///////////////
|
|
41
|
-
secondaryAssetKind === AssetKind.NAT ||
|
|
42
|
-
assert.fail(X`${q(keyword)} asset not fungible (must use NAT math)`);
|
|
43
|
-
|
|
44
|
-
/** @type {(brand: Brand) => Promise<ZCFMint>} */
|
|
45
|
-
const makeLiquidityMint = brand => {
|
|
46
|
-
!isInSecondaries(brand) ||
|
|
47
|
-
assert.fail(X`issuer ${secondaryIssuer} already has a pool`);
|
|
48
|
-
|
|
49
|
-
const liquidityKeyword = `${keyword}Liquidity`;
|
|
50
|
-
zcf.assertUniqueKeyword(liquidityKeyword);
|
|
51
|
-
|
|
52
|
-
return E.when(
|
|
53
|
-
Promise.all([
|
|
54
|
-
zcf.saveIssuer(secondaryIssuer, keyword),
|
|
55
|
-
zcf.makeZCFMint(liquidityKeyword, AssetKind.NAT, DISPLAY_INFO),
|
|
56
|
-
]),
|
|
57
|
-
([issuer, mint]) => {
|
|
58
|
-
console.log(
|
|
59
|
-
X`Saved issuer ${secondaryIssuer} to keyword ${keyword} and got back ${issuer}`,
|
|
60
|
-
);
|
|
61
|
-
return mint;
|
|
62
|
-
},
|
|
63
|
-
).catch(e => {
|
|
64
|
-
console.error(
|
|
65
|
-
X`Failure Saving issuer ${secondaryIssuer}. Not added to Reserve`,
|
|
66
|
-
);
|
|
67
|
-
throw e;
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/** @type {(brand: Brand) => Promise<void>} */
|
|
72
|
-
const finish = brand => {
|
|
73
|
-
// defer lookup until necessary. more aligned with governed
|
|
74
|
-
// param we expect this to be eventually.
|
|
75
|
-
const addIssuerToReserve = getAddIssuerToReserve();
|
|
76
|
-
|
|
77
|
-
// tell the reserve about this brand, which it will validate by
|
|
78
|
-
// calling back to AMM for the issuer
|
|
79
|
-
return addIssuerToReserve(brand);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return brandToLiquidityMintProvider
|
|
83
|
-
.provideAsync(secondaryBrand, makeLiquidityMint, finish)
|
|
84
|
-
.then(mint => mint.getIssuerRecord().issuer);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
return addIssuer;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @param {Baggage} baggage
|
|
92
|
-
* @param {import('./multipoolMarketMaker.js').AmmPowers} ammPowers
|
|
93
|
-
* @param {(secondaryBrand: Brand, reserveLiquidityTokenSeat: ZCFSeat, liquidityKeyword: Keyword) => Promise<void>} onOfferHandled
|
|
94
|
-
* @param {ERef<StorageNode>} storageNode
|
|
95
|
-
* @param {ERef<Marshaller>} marshaller
|
|
96
|
-
*/
|
|
97
|
-
export const makeAddPoolInvitation = (
|
|
98
|
-
baggage,
|
|
99
|
-
ammPowers,
|
|
100
|
-
onOfferHandled,
|
|
101
|
-
storageNode,
|
|
102
|
-
marshaller,
|
|
103
|
-
) => {
|
|
104
|
-
const { zcf } = ammPowers;
|
|
105
|
-
const makePool = definePoolKind(baggage, ammPowers, storageNode, marshaller);
|
|
106
|
-
|
|
107
|
-
/** @type {(Brand) => Promise<{poolFacets: PoolFacets, liquidityZcfMint: ZCFMint}>} */
|
|
108
|
-
const addPool = async secondaryBrand => {
|
|
109
|
-
const liquidityZcfMint =
|
|
110
|
-
ammPowers.secondaryBrandToLiquidityMint.get(secondaryBrand);
|
|
111
|
-
|
|
112
|
-
const { zcfSeat: poolSeat } = ammPowers.zcf.makeEmptySeatKit();
|
|
113
|
-
const poolFacets = makePool(liquidityZcfMint, poolSeat, secondaryBrand);
|
|
114
|
-
|
|
115
|
-
ammPowers.secondaryBrandToPool.init(secondaryBrand, poolFacets);
|
|
116
|
-
return { liquidityZcfMint, poolFacets };
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/** @param {ZCFSeat} seat */
|
|
120
|
-
const handleAddPoolOffer = async seat => {
|
|
121
|
-
assertProposalShape(seat, {
|
|
122
|
-
give: { Central: null, Secondary: null },
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const {
|
|
126
|
-
give: { Central: centralAmount, Secondary: secondaryAmount },
|
|
127
|
-
want: proposalWant,
|
|
128
|
-
} = seat.getProposal();
|
|
129
|
-
const secondaryBrand = secondaryAmount.brand;
|
|
130
|
-
|
|
131
|
-
const { brand: liquidityBrand, issuer } =
|
|
132
|
-
ammPowers.secondaryBrandToLiquidityMint
|
|
133
|
-
.get(secondaryBrand)
|
|
134
|
-
.getIssuerRecord();
|
|
135
|
-
|
|
136
|
-
const minPoolLiquidity = ammPowers.params.getMinInitialPoolLiquidity();
|
|
137
|
-
|
|
138
|
-
if (proposalWant.Liquidity) {
|
|
139
|
-
const { Liquidity: wantLiquidityAmount } = proposalWant;
|
|
140
|
-
const centralAboveMinimum =
|
|
141
|
-
// @ts-expect-error central is NAT
|
|
142
|
-
centralAmount.value - minPoolLiquidity.value;
|
|
143
|
-
// when providing initial liquidity, the liquidity tokens issued will be
|
|
144
|
-
// equal to the central provided. Here, the reserve gets the minimum
|
|
145
|
-
const funderLiquidityAmount = AmountMath.make(
|
|
146
|
-
liquidityBrand,
|
|
147
|
-
centralAboveMinimum,
|
|
148
|
-
);
|
|
149
|
-
AmountMath.isGTE(funderLiquidityAmount, wantLiquidityAmount) ||
|
|
150
|
-
assert.fail(
|
|
151
|
-
X`Requested too many liquidity tokens (${wantLiquidityAmount}, max: ${funderLiquidityAmount}`,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
AmountMath.isGTE(centralAmount, minPoolLiquidity) ||
|
|
155
|
-
assert.fail(
|
|
156
|
-
X`The minimum initial liquidity is ${minPoolLiquidity}, rejecting ${centralAmount}`,
|
|
157
|
-
);
|
|
158
|
-
const minLiqAmount = AmountMath.make(
|
|
159
|
-
liquidityBrand,
|
|
160
|
-
// @ts-expect-error known nat
|
|
161
|
-
minPoolLiquidity.value,
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// @ts-expect-error find might return undefined
|
|
165
|
-
const [liquidityKeyword] = Object.entries(zcf.getTerms().issuers).find(
|
|
166
|
-
([_, i]) => i === issuer,
|
|
167
|
-
);
|
|
168
|
-
assert(liquidityKeyword, 'liquidity brand required');
|
|
169
|
-
|
|
170
|
-
// COMMIT POINT /////////////////////
|
|
171
|
-
|
|
172
|
-
const {
|
|
173
|
-
poolFacets: { pool, helper },
|
|
174
|
-
} = await addPool(secondaryBrand);
|
|
175
|
-
|
|
176
|
-
// in addLiquidityInternal, funder provides centralAmount & secondaryAmount,
|
|
177
|
-
// and receives liquidity tokens equal to centralAmount. Afterward, we'll
|
|
178
|
-
// transfer minPoolLiquidity in tokens from the funder to the reserve.
|
|
179
|
-
helper.addLiquidityInternal(seat, secondaryAmount, centralAmount);
|
|
180
|
-
|
|
181
|
-
seat.decrementBy({ Liquidity: minLiqAmount });
|
|
182
|
-
const { zcfSeat: reserveLiquidityTokenSeat } = zcf.makeEmptySeatKit();
|
|
183
|
-
reserveLiquidityTokenSeat.incrementBy({ [liquidityKeyword]: minLiqAmount });
|
|
184
|
-
zcf.reallocate(reserveLiquidityTokenSeat, seat);
|
|
185
|
-
seat.exit();
|
|
186
|
-
pool.updateState();
|
|
187
|
-
|
|
188
|
-
await onOfferHandled(
|
|
189
|
-
secondaryBrand,
|
|
190
|
-
reserveLiquidityTokenSeat,
|
|
191
|
-
liquidityKeyword,
|
|
192
|
-
);
|
|
193
|
-
return 'Added liquidity.';
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
return () => zcf.makeInvitation(handleAddPoolOffer, 'Add Pool and Liquidity');
|
|
197
|
-
};
|
|
Binary file
|