@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,31 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
import '@agoric/
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
makeRatioFromAmounts,
|
|
7
|
-
ceilMultiplyBy,
|
|
8
|
-
floorMultiplyBy,
|
|
9
|
-
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
10
|
-
import { AmountMath } from '@agoric/ertp';
|
|
11
|
-
import {
|
|
12
|
-
defineDurableKindMulti,
|
|
13
|
-
makeKindHandle,
|
|
14
|
-
pickFacet,
|
|
15
|
-
} from '@agoric/vat-data';
|
|
16
|
-
import { makeTracer } from '../makeTracer.js';
|
|
17
|
-
import { calculateCurrentDebt, reverseInterest } from '../interest-math.js';
|
|
18
|
-
import { makeVaultKit } from './vaultKit.js';
|
|
1
|
+
import { AmountMath, AmountShape } from '@agoric/ertp';
|
|
2
|
+
import { makeTracer, StorageNodeShape } from '@agoric/internal';
|
|
3
|
+
import { M, prepareExoClassKit } from '@agoric/vat-data';
|
|
4
|
+
import { atomicTransfer } from '@agoric/zoe/src/contractSupport/index.js';
|
|
5
|
+
import { SeatShape } from '@agoric/zoe/src/typeGuards.js';
|
|
19
6
|
import {
|
|
20
7
|
addSubtract,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
stageDelta,
|
|
8
|
+
allEmpty,
|
|
9
|
+
makeNatAmountShape,
|
|
24
10
|
} from '../contractSupport.js';
|
|
11
|
+
import { calculateCurrentDebt, reverseInterest } from '../interest-math.js';
|
|
12
|
+
import { UnguardedHelperI } from '../typeGuards.js';
|
|
13
|
+
import { prepareVaultKit } from './vaultKit.js';
|
|
14
|
+
|
|
15
|
+
import '@agoric/zoe/exported.js';
|
|
16
|
+
import { calculateDebtCosts } from './math.js';
|
|
25
17
|
|
|
26
|
-
const {
|
|
18
|
+
const { quote: q, Fail } = assert;
|
|
27
19
|
|
|
28
|
-
const trace = makeTracer('
|
|
20
|
+
const trace = makeTracer('Vault', true);
|
|
29
21
|
|
|
30
22
|
/** @typedef {import('./storeUtils.js').NormalizedDebt} NormalizedDebt */
|
|
31
23
|
|
|
@@ -70,7 +62,7 @@ export const Phase = /** @type {const} */ ({
|
|
|
70
62
|
*/
|
|
71
63
|
const validTransitions = {
|
|
72
64
|
[Phase.ACTIVE]: [Phase.LIQUIDATING, Phase.CLOSED],
|
|
73
|
-
[Phase.LIQUIDATING]: [Phase.LIQUIDATED],
|
|
65
|
+
[Phase.LIQUIDATING]: [Phase.LIQUIDATED, Phase.ACTIVE],
|
|
74
66
|
[Phase.LIQUIDATED]: [Phase.CLOSED],
|
|
75
67
|
[Phase.CLOSED]: [],
|
|
76
68
|
};
|
|
@@ -88,11 +80,12 @@ const validTransitions = {
|
|
|
88
80
|
/**
|
|
89
81
|
* @typedef {object} VaultManager
|
|
90
82
|
* @property {() => Subscriber<import('./vaultManager').AssetState>} getAssetSubscriber
|
|
91
|
-
* @property {(collateralAmount: Amount) =>
|
|
83
|
+
* @property {(collateralAmount: Amount) => Amount<'nat'>} maxDebtFor
|
|
92
84
|
* @property {() => Brand} getCollateralBrand
|
|
85
|
+
* @property {(base: string) => string} scopeDescription
|
|
93
86
|
* @property {() => Brand<'nat'>} getDebtBrand
|
|
94
|
-
* @property {
|
|
95
|
-
* @property {(amount: Amount, seat: ZCFSeat) => void}
|
|
87
|
+
* @property {MintAndTransfer} mintAndTransfer
|
|
88
|
+
* @property {(amount: Amount, seat: ZCFSeat) => void} burn
|
|
96
89
|
* @property {() => Ratio} getCompoundedInterest
|
|
97
90
|
* @property {(oldDebt: import('./storeUtils.js').NormalizedDebt, oldCollateral: Amount<'nat'>, vaultId: VaultId, vaultPhase: VaultPhase, vault: Vault) => void} handleBalanceChange
|
|
98
91
|
* @property {() => import('./vaultManager.js').GovernedParamGetters} getGovernedParams
|
|
@@ -102,6 +95,7 @@ const validTransitions = {
|
|
|
102
95
|
* @typedef {Readonly<{
|
|
103
96
|
* idInManager: VaultId,
|
|
104
97
|
* manager: VaultManager,
|
|
98
|
+
* storageNode: StorageNode,
|
|
105
99
|
* vaultSeat: ZCFSeat,
|
|
106
100
|
* }>} ImmutableState
|
|
107
101
|
*/
|
|
@@ -113,696 +107,765 @@ const validTransitions = {
|
|
|
113
107
|
* interestSnapshot: Ratio,
|
|
114
108
|
* phase: VaultPhase,
|
|
115
109
|
* debtSnapshot: Amount<'nat'>,
|
|
110
|
+
* outerUpdater: import('@agoric/zoe/src/contractSupport/recorder.js').Recorder<VaultNotification> | null,
|
|
116
111
|
* }} MutableState
|
|
117
112
|
*/
|
|
118
113
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
export const VaultI = M.interface('Vault', {
|
|
115
|
+
getCollateralAmount: M.call().returns(AmountShape),
|
|
116
|
+
getCurrentDebt: M.call().returns(AmountShape),
|
|
117
|
+
getNormalizedDebt: M.call().returns(AmountShape),
|
|
118
|
+
getVaultSeat: M.call().returns(SeatShape),
|
|
119
|
+
initVaultKit: M.call(SeatShape, StorageNodeShape).returns(M.promise()),
|
|
120
|
+
liquidated: M.call().returns(undefined),
|
|
121
|
+
liquidating: M.call().returns(undefined),
|
|
122
|
+
makeAdjustBalancesInvitation: M.call().returns(M.promise()),
|
|
123
|
+
makeCloseInvitation: M.call().returns(M.promise()),
|
|
124
|
+
makeTransferInvitation: M.call().returns(M.promise()),
|
|
125
|
+
abortLiquidation: M.call().returns(M.string()),
|
|
126
|
+
});
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* zcf: ZCF,
|
|
140
|
-
* }} */
|
|
141
|
-
// @ts-expect-error not yet defined
|
|
142
|
-
const provideEphemera = makeEphemeraProvider(() => ({}));
|
|
128
|
+
const VaultStateShape = harden({
|
|
129
|
+
idInManager: M.any(),
|
|
130
|
+
manager: M.any(),
|
|
131
|
+
outerUpdater: M.any(),
|
|
132
|
+
phase: M.any(),
|
|
133
|
+
storageNode: M.any(),
|
|
134
|
+
vaultSeat: M.any(),
|
|
135
|
+
interestSnapshot: M.any(),
|
|
136
|
+
debtSnapshot: M.any(),
|
|
137
|
+
});
|
|
143
138
|
|
|
144
139
|
/**
|
|
140
|
+
* @param {import('@agoric/ertp').Baggage} baggage
|
|
141
|
+
* @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit} makeRecorderKit
|
|
145
142
|
* @param {ZCF} zcf
|
|
146
|
-
* @param {VaultManager} manager
|
|
147
|
-
* @param {VaultId} idInManager
|
|
148
|
-
* @param {ERef<StorageNode>} storageNode
|
|
149
|
-
* @param {ERef<Marshaller>} marshaller
|
|
150
143
|
*/
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
* @returns {boolean}
|
|
187
|
-
*/
|
|
188
|
-
const checkRestart = (newCollateralPre, maxDebtPre, newCollateral, newDebt) => {
|
|
189
|
-
if (AmountMath.isGTE(newCollateralPre, newCollateral)) {
|
|
190
|
-
// The collateral did not go up. If the collateral decreased, we pro-rate maxDebt.
|
|
191
|
-
// We can pro-rate maxDebt because the quote is either linear (price is
|
|
192
|
-
// unchanging) or super-linear (also called "convex"). Super-linear is from
|
|
193
|
-
// AMMs: selling less collateral would mean an even smaller price impact, so
|
|
194
|
-
// this is a conservative choice.
|
|
195
|
-
const debtPerCollateral = makeRatioFromAmounts(
|
|
196
|
-
maxDebtPre,
|
|
197
|
-
newCollateralPre,
|
|
198
|
-
);
|
|
199
|
-
// `floorMultiply` because the debt ceiling should be tight
|
|
200
|
-
const maxDebtAfter = floorMultiplyBy(newCollateral, debtPerCollateral);
|
|
201
|
-
assert(
|
|
202
|
-
AmountMath.isGTE(maxDebtAfter, newDebt),
|
|
203
|
-
X`The requested debt ${q(
|
|
204
|
-
newDebt,
|
|
205
|
-
)} is more than the collateralization ratio allows: ${q(maxDebtAfter)}`,
|
|
206
|
-
);
|
|
207
|
-
// The `collateralAfter` can still cover the `newDebt`, so don't restart.
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
// The collateral went up. Restart if the debt *also* went up because
|
|
211
|
-
// the price quote might not apply at the higher numbers.
|
|
212
|
-
return !AmountMath.isGTE(maxDebtPre, newDebt);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const helperBehavior = {
|
|
216
|
-
// #region Computed constants
|
|
217
|
-
collateralBrand: ({ state }) => state.manager.getCollateralBrand(),
|
|
218
|
-
debtBrand: ({ state }) => state.manager.getDebtBrand(),
|
|
219
|
-
|
|
220
|
-
emptyCollateral: ({ facets }) =>
|
|
221
|
-
AmountMath.makeEmpty(facets.helper.collateralBrand()),
|
|
222
|
-
emptyDebt: ({ facets }) => AmountMath.makeEmpty(facets.helper.debtBrand()),
|
|
223
|
-
// #endregion
|
|
224
|
-
|
|
225
|
-
// #region Phase logic
|
|
226
|
-
/**
|
|
227
|
-
* @param {MethodContext} context
|
|
228
|
-
* @param {VaultPhase} newPhase
|
|
229
|
-
*/
|
|
230
|
-
assignPhase: ({ state }, newPhase) => {
|
|
231
|
-
const { phase } = state;
|
|
232
|
-
const validNewPhases = validTransitions[phase];
|
|
233
|
-
validNewPhases.includes(newPhase) ||
|
|
234
|
-
assert.fail(
|
|
235
|
-
X`Vault cannot transition from ${q(phase)} to ${q(newPhase)}`,
|
|
236
|
-
);
|
|
237
|
-
state.phase = newPhase;
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
assertActive: ({ state }) => {
|
|
241
|
-
const { phase } = state;
|
|
242
|
-
assert(phase === Phase.ACTIVE);
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
assertCloseable: ({ state }) => {
|
|
246
|
-
const { phase } = state;
|
|
247
|
-
phase === Phase.ACTIVE ||
|
|
248
|
-
phase === Phase.LIQUIDATED ||
|
|
249
|
-
assert.fail(
|
|
250
|
-
X`to be closed a vault must be active or liquidated, not ${phase}`,
|
|
251
|
-
);
|
|
252
|
-
},
|
|
253
|
-
// #endregion
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Called whenever the debt is paid or created through a transaction,
|
|
257
|
-
* but not for interest accrual.
|
|
258
|
-
*
|
|
259
|
-
* @param {MethodContext} context
|
|
260
|
-
* @param {Amount} newDebt - principal and all accrued interest
|
|
261
|
-
*/
|
|
262
|
-
updateDebtSnapshot: ({ state }, newDebt) => {
|
|
263
|
-
// update local state
|
|
264
|
-
state.debtSnapshot = newDebt;
|
|
265
|
-
state.interestSnapshot = state.manager.getCompoundedInterest();
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Update the debt balance and propagate upwards to
|
|
270
|
-
* maintain aggregate debt and liquidation order.
|
|
271
|
-
*
|
|
272
|
-
* @param {MethodContext} context
|
|
273
|
-
* @param {NormalizedDebt} oldDebtNormalized - prior principal and all accrued interest, normalized to the launch of the vaultManager
|
|
274
|
-
* @param {Amount<'nat'>} oldCollateral - actual collateral
|
|
275
|
-
* @param {Amount<'nat'>} newDebtActual - actual principal and all accrued interest
|
|
276
|
-
*/
|
|
277
|
-
updateDebtAccounting: (
|
|
278
|
-
{ state, facets },
|
|
279
|
-
oldDebtNormalized,
|
|
280
|
-
oldCollateral,
|
|
281
|
-
newDebtActual,
|
|
282
|
-
) => {
|
|
283
|
-
const { helper } = facets;
|
|
284
|
-
helper.updateDebtSnapshot(newDebtActual);
|
|
285
|
-
// notify manager so it can notify and clean up as appropriate
|
|
286
|
-
state.manager.handleBalanceChange(
|
|
287
|
-
oldDebtNormalized,
|
|
288
|
-
oldCollateral,
|
|
289
|
-
state.idInManager,
|
|
290
|
-
state.phase,
|
|
291
|
-
facets.self,
|
|
292
|
-
);
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
*
|
|
297
|
-
* @param {MethodContext} context
|
|
298
|
-
* @param {ZCFSeat} seat
|
|
299
|
-
*/
|
|
300
|
-
getCollateralAllocated: ({ facets }, seat) =>
|
|
301
|
-
seat.getAmountAllocated('Collateral', facets.helper.collateralBrand()),
|
|
302
|
-
getMintedAllocated: ({ facets }, seat) =>
|
|
303
|
-
seat.getAmountAllocated('Minted', facets.helper.debtBrand()),
|
|
304
|
-
|
|
305
|
-
assertVaultHoldsNoMinted: ({ state, facets }) => {
|
|
306
|
-
const { vaultSeat } = state;
|
|
307
|
-
AmountMath.isEmpty(facets.helper.getMintedAllocated(vaultSeat)) ||
|
|
308
|
-
assert.fail(X`Vault should be empty of Minted`);
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
*
|
|
313
|
-
* @param {MethodContext} context
|
|
314
|
-
* @param {Amount<'nat'>} collateralAmount
|
|
315
|
-
* @param {Amount<'nat'>} proposedRunDebt
|
|
316
|
-
*/
|
|
317
|
-
assertSufficientCollateral: async (
|
|
318
|
-
{ state, facets },
|
|
319
|
-
collateralAmount,
|
|
320
|
-
proposedRunDebt,
|
|
321
|
-
) => {
|
|
322
|
-
const maxRun = await state.manager.maxDebtFor(collateralAmount);
|
|
323
|
-
assert(
|
|
324
|
-
AmountMath.isGTE(maxRun, proposedRunDebt, facets.helper.debtBrand()),
|
|
325
|
-
X`Requested ${q(proposedRunDebt)} exceeds max ${q(maxRun)} for ${q(
|
|
326
|
-
collateralAmount,
|
|
327
|
-
)} collateral`,
|
|
328
|
-
);
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
*
|
|
333
|
-
* @param {MethodContext} context
|
|
334
|
-
* @param {HolderPhase} newPhase
|
|
335
|
-
*/
|
|
336
|
-
getStateSnapshot: ({ state, facets }, newPhase) => {
|
|
337
|
-
const { debtSnapshot: debt, interestSnapshot: interest } = state;
|
|
338
|
-
/** @type {VaultNotification} */
|
|
339
|
-
return harden({
|
|
340
|
-
debtSnapshot: { debt, interest },
|
|
341
|
-
locked: facets.self.getCollateralAmount(),
|
|
342
|
-
// newPhase param is so that makeTransferInvitation can finish without setting the vault's phase
|
|
343
|
-
// TODO refactor https://github.com/Agoric/agoric-sdk/issues/4415
|
|
344
|
-
vaultState: newPhase,
|
|
345
|
-
});
|
|
346
|
-
},
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* call this whenever anything changes!
|
|
350
|
-
*
|
|
351
|
-
* @param {MethodContext} context
|
|
352
|
-
*/
|
|
353
|
-
updateUiState: ({ state, facets }) => {
|
|
354
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
355
|
-
const { outerUpdater } = ephemera;
|
|
356
|
-
if (!outerUpdater) {
|
|
357
|
-
// It's not an error to change to liquidating during transfer
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const { phase } = state;
|
|
361
|
-
const uiState = facets.helper.getStateSnapshot(phase);
|
|
362
|
-
trace('updateUiState', state.idInManager, uiState);
|
|
363
|
-
|
|
364
|
-
switch (phase) {
|
|
365
|
-
case Phase.ACTIVE:
|
|
366
|
-
case Phase.LIQUIDATING:
|
|
367
|
-
case Phase.LIQUIDATED:
|
|
368
|
-
outerUpdater.publish(uiState);
|
|
369
|
-
break;
|
|
370
|
-
case Phase.CLOSED:
|
|
371
|
-
outerUpdater.finish(uiState);
|
|
372
|
-
ephemera.outerUpdater = null;
|
|
373
|
-
break;
|
|
374
|
-
default:
|
|
375
|
-
throw Error(`unreachable vault phase: ${phase}`);
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* @param {MethodContext} context
|
|
381
|
-
* @param {ZCFSeat} seat
|
|
382
|
-
*/
|
|
383
|
-
closeHook: async ({ state, facets }, seat) => {
|
|
384
|
-
const { self, helper } = facets;
|
|
385
|
-
helper.assertCloseable();
|
|
386
|
-
const { phase, vaultSeat } = state;
|
|
387
|
-
const { zcf } = provideEphemera(state.idInManager);
|
|
388
|
-
|
|
389
|
-
// Held as keys for cleanup in the manager
|
|
390
|
-
const oldDebtNormalized = self.getNormalizedDebt();
|
|
391
|
-
const oldCollateral = self.getCollateralAmount();
|
|
392
|
-
|
|
393
|
-
if (phase === Phase.ACTIVE) {
|
|
394
|
-
assertProposalShape(seat, {
|
|
395
|
-
give: { Minted: null },
|
|
144
|
+
export const prepareVault = (baggage, makeRecorderKit, zcf) => {
|
|
145
|
+
const makeVaultKit = prepareVaultKit(baggage, makeRecorderKit);
|
|
146
|
+
|
|
147
|
+
const maker = prepareExoClassKit(
|
|
148
|
+
baggage,
|
|
149
|
+
'Vault',
|
|
150
|
+
{
|
|
151
|
+
helper: UnguardedHelperI,
|
|
152
|
+
self: VaultI,
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* @param {VaultManager} manager
|
|
156
|
+
* @param {VaultId} idInManager
|
|
157
|
+
* @param {StorageNode} storageNode
|
|
158
|
+
* @returns {ImmutableState & MutableState}
|
|
159
|
+
*/
|
|
160
|
+
(manager, idInManager, storageNode) => {
|
|
161
|
+
return harden({
|
|
162
|
+
idInManager,
|
|
163
|
+
manager,
|
|
164
|
+
outerUpdater: null,
|
|
165
|
+
phase: Phase.ACTIVE,
|
|
166
|
+
|
|
167
|
+
storageNode,
|
|
168
|
+
|
|
169
|
+
// vaultSeat will hold the collateral until the position is retired. The
|
|
170
|
+
// payout from it will be handed to the user: if the vault dies early
|
|
171
|
+
// (because the vaultFactory vat died), they'll get all their
|
|
172
|
+
// collateral back. If that happens, the issuer for the Minted will be dead,
|
|
173
|
+
// so their position will be worthless.
|
|
174
|
+
vaultSeat: zcf.makeEmptySeatKit().zcfSeat,
|
|
175
|
+
|
|
176
|
+
// Two values from the same moment
|
|
177
|
+
interestSnapshot: manager.getCompoundedInterest(),
|
|
178
|
+
debtSnapshot: AmountMath.makeEmpty(manager.getDebtBrand()),
|
|
396
179
|
});
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
helper: {
|
|
183
|
+
//#region Computed constants
|
|
184
|
+
collateralBrand() {
|
|
185
|
+
return this.state.manager.getCollateralBrand();
|
|
186
|
+
},
|
|
187
|
+
debtBrand() {
|
|
188
|
+
return this.state.manager.getDebtBrand();
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
emptyCollateral() {
|
|
192
|
+
return AmountMath.makeEmpty(this.facets.helper.collateralBrand());
|
|
193
|
+
},
|
|
194
|
+
emptyDebt() {
|
|
195
|
+
return AmountMath.makeEmpty(this.facets.helper.debtBrand());
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* @typedef {{ give: { Collateral: Amount<'nat'>, Minted: Amount<'nat'> }, want: { Collateral: Amount<'nat'>, Minted: Amount<'nat'> } }} FullProposal
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* @param {ProposalRecord} partial
|
|
202
|
+
* @returns {FullProposal}
|
|
203
|
+
*/
|
|
204
|
+
fullProposal(partial) {
|
|
205
|
+
return {
|
|
206
|
+
give: {
|
|
207
|
+
Collateral:
|
|
208
|
+
partial.give?.Collateral ||
|
|
209
|
+
this.facets.helper.emptyCollateral(),
|
|
210
|
+
Minted: partial.give?.Minted || this.facets.helper.emptyDebt(),
|
|
211
|
+
},
|
|
212
|
+
want: {
|
|
213
|
+
Collateral:
|
|
214
|
+
partial.want?.Collateral ||
|
|
215
|
+
this.facets.helper.emptyCollateral(),
|
|
216
|
+
Minted: partial.want?.Minted || this.facets.helper.emptyDebt(),
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
//#endregion
|
|
221
|
+
|
|
222
|
+
//#region Phase logic
|
|
223
|
+
/**
|
|
224
|
+
* @param {VaultPhase} newPhase
|
|
225
|
+
*/
|
|
226
|
+
assignPhase(newPhase) {
|
|
227
|
+
const { state } = this;
|
|
228
|
+
|
|
229
|
+
const { phase } = state;
|
|
230
|
+
const validNewPhases = validTransitions[phase];
|
|
231
|
+
validNewPhases.includes(newPhase) ||
|
|
232
|
+
Fail`Vault cannot transition from ${q(phase)} to ${q(newPhase)}`;
|
|
233
|
+
state.phase = newPhase;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
assertActive() {
|
|
237
|
+
const { phase } = this.state;
|
|
238
|
+
phase === Phase.ACTIVE || Fail`vault not active`;
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
assertCloseable() {
|
|
242
|
+
const { phase } = this.state;
|
|
243
|
+
phase === Phase.ACTIVE ||
|
|
244
|
+
phase === Phase.LIQUIDATED ||
|
|
245
|
+
Fail`to be closed a vault must be active or liquidated, not ${phase}`;
|
|
246
|
+
},
|
|
247
|
+
//#endregion
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Called whenever the debt is paid or created through a transaction,
|
|
251
|
+
* but not for interest accrual.
|
|
252
|
+
*
|
|
253
|
+
* @param {Amount<'nat'>} newDebt - principal and all accrued interest
|
|
254
|
+
*/
|
|
255
|
+
updateDebtSnapshot(newDebt) {
|
|
256
|
+
const { state } = this;
|
|
257
|
+
|
|
258
|
+
// update local state
|
|
259
|
+
state.debtSnapshot = newDebt;
|
|
260
|
+
state.interestSnapshot = state.manager.getCompoundedInterest();
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Update the debt balance and propagate upwards to
|
|
265
|
+
* maintain aggregate debt and liquidation order.
|
|
266
|
+
*
|
|
267
|
+
* @param {NormalizedDebt} oldDebtNormalized - prior principal and all accrued interest, normalized to the launch of the vaultManager
|
|
268
|
+
* @param {Amount<'nat'>} oldCollateral - actual collateral
|
|
269
|
+
* @param {Amount<'nat'>} newDebtActual - actual principal and all accrued interest
|
|
270
|
+
*/
|
|
271
|
+
updateDebtAccounting(oldDebtNormalized, oldCollateral, newDebtActual) {
|
|
272
|
+
const { state, facets } = this;
|
|
273
|
+
const { helper } = facets;
|
|
274
|
+
helper.updateDebtSnapshot(newDebtActual);
|
|
275
|
+
// notify manager so it can notify and clean up as appropriate
|
|
276
|
+
state.manager.handleBalanceChange(
|
|
277
|
+
oldDebtNormalized,
|
|
278
|
+
oldCollateral,
|
|
279
|
+
state.idInManager,
|
|
280
|
+
state.phase,
|
|
281
|
+
facets.self,
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
*
|
|
287
|
+
* @param {ZCFSeat} seat
|
|
288
|
+
*/
|
|
289
|
+
getCollateralAllocated(seat) {
|
|
290
|
+
return seat.getAmountAllocated(
|
|
291
|
+
'Collateral',
|
|
292
|
+
this.facets.helper.collateralBrand(),
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
getMintedAllocated(seat) {
|
|
296
|
+
return seat.getAmountAllocated(
|
|
297
|
+
'Minted',
|
|
298
|
+
this.facets.helper.debtBrand(),
|
|
299
|
+
);
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
assertVaultHoldsNoMinted() {
|
|
303
|
+
const { state, facets } = this;
|
|
304
|
+
const { vaultSeat } = state;
|
|
305
|
+
AmountMath.isEmpty(facets.helper.getMintedAllocated(vaultSeat)) ||
|
|
306
|
+
Fail`Vault should be empty of Minted`;
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
*
|
|
311
|
+
* @param {Amount<'nat'>} collateralAmount
|
|
312
|
+
* @param {Amount<'nat'>} proposedDebt
|
|
313
|
+
*/
|
|
314
|
+
assertSufficientCollateral(collateralAmount, proposedDebt) {
|
|
315
|
+
const { state, facets } = this;
|
|
316
|
+
const maxDebt = state.manager.maxDebtFor(collateralAmount);
|
|
317
|
+
AmountMath.isGTE(maxDebt, proposedDebt, facets.helper.debtBrand()) ||
|
|
318
|
+
Fail`Proposed debt ${q(proposedDebt)} exceeds max ${q(
|
|
319
|
+
maxDebt,
|
|
320
|
+
)} for ${q(collateralAmount)} collateral`;
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
*
|
|
325
|
+
* @param {HolderPhase} newPhase
|
|
326
|
+
*/
|
|
327
|
+
getStateSnapshot(newPhase) {
|
|
328
|
+
const { state, facets } = this;
|
|
329
|
+
|
|
330
|
+
const { debtSnapshot: debt, interestSnapshot: interest } = state;
|
|
331
|
+
/** @type {VaultNotification} */
|
|
332
|
+
return harden({
|
|
333
|
+
debtSnapshot: { debt, interest },
|
|
334
|
+
locked: facets.self.getCollateralAmount(),
|
|
335
|
+
// newPhase param is so that makeTransferInvitation can finish without setting the vault's phase
|
|
336
|
+
// TODO refactor https://github.com/Agoric/agoric-sdk/issues/4415
|
|
337
|
+
vaultState: newPhase,
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* call this whenever anything changes!
|
|
343
|
+
*/
|
|
344
|
+
updateUiState() {
|
|
345
|
+
const { state, facets } = this;
|
|
346
|
+
const { outerUpdater } = state;
|
|
347
|
+
if (!outerUpdater) {
|
|
348
|
+
// It's not an error to change to liquidating during transfer
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const { phase } = state;
|
|
352
|
+
const uiState = facets.helper.getStateSnapshot(phase);
|
|
353
|
+
const brand = facets.helper.collateralBrand();
|
|
354
|
+
trace(brand, 'updateUiState', state.idInManager, uiState);
|
|
355
|
+
|
|
356
|
+
switch (phase) {
|
|
357
|
+
case Phase.ACTIVE:
|
|
358
|
+
case Phase.LIQUIDATING:
|
|
359
|
+
case Phase.LIQUIDATED:
|
|
360
|
+
void outerUpdater.write(uiState);
|
|
361
|
+
break;
|
|
362
|
+
case Phase.CLOSED:
|
|
363
|
+
void outerUpdater.writeFinal(uiState);
|
|
364
|
+
state.outerUpdater = null;
|
|
365
|
+
break;
|
|
366
|
+
default:
|
|
367
|
+
throw Error(`unreachable vault phase: ${phase}`);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @param {ZCFSeat} seat
|
|
373
|
+
*/
|
|
374
|
+
async closeHook(seat) {
|
|
375
|
+
const { state, facets } = this;
|
|
376
|
+
|
|
377
|
+
const { self, helper } = facets;
|
|
378
|
+
helper.assertCloseable();
|
|
379
|
+
const { phase, vaultSeat } = state;
|
|
380
|
+
|
|
381
|
+
// Held as keys for cleanup in the manager
|
|
382
|
+
const oldDebtNormalized = self.getNormalizedDebt();
|
|
383
|
+
const oldCollateral = self.getCollateralAmount();
|
|
384
|
+
|
|
385
|
+
if (phase === Phase.ACTIVE) {
|
|
386
|
+
// you're paying off the debt, you get everything back.
|
|
387
|
+
const debt = self.getCurrentDebt();
|
|
388
|
+
const {
|
|
389
|
+
give: { Minted: given },
|
|
390
|
+
} = seat.getProposal();
|
|
391
|
+
given || Fail`closing an active vault requires a give`;
|
|
392
|
+
|
|
393
|
+
// you must pay off the entire remainder but if you offer too much, we won't
|
|
394
|
+
// take more than you owe
|
|
395
|
+
AmountMath.isGTE(given, debt) ||
|
|
396
|
+
Fail`Offer ${q(given)} is not sufficient to pay off debt ${q(
|
|
397
|
+
debt,
|
|
398
|
+
)}`;
|
|
399
|
+
|
|
400
|
+
// Return any overpayment
|
|
401
|
+
atomicTransfer(
|
|
402
|
+
zcf,
|
|
403
|
+
vaultSeat,
|
|
404
|
+
seat,
|
|
405
|
+
vaultSeat.getCurrentAllocation(),
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
state.manager.burn(debt, seat);
|
|
409
|
+
} else if (phase === Phase.LIQUIDATED) {
|
|
410
|
+
// Simply reallocate vault assets to the offer seat.
|
|
411
|
+
// Don't take anything from the offer, even if vault is underwater.
|
|
412
|
+
// TODO verify that returning Minted here doesn't mess up debt limits
|
|
413
|
+
|
|
414
|
+
atomicTransfer(
|
|
415
|
+
zcf,
|
|
416
|
+
vaultSeat,
|
|
417
|
+
seat,
|
|
418
|
+
vaultSeat.getCurrentAllocation(),
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
throw Error('only active and liquidated vaults can be closed');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
seat.exit();
|
|
425
|
+
helper.assignPhase(Phase.CLOSED);
|
|
426
|
+
helper.updateDebtSnapshot(helper.emptyDebt());
|
|
427
|
+
helper.updateUiState();
|
|
428
|
+
helper.assertVaultHoldsNoMinted();
|
|
429
|
+
vaultSeat.exit();
|
|
430
|
+
|
|
431
|
+
state.manager.handleBalanceChange(
|
|
432
|
+
oldDebtNormalized,
|
|
433
|
+
oldCollateral,
|
|
434
|
+
state.idInManager,
|
|
435
|
+
state.phase,
|
|
436
|
+
facets.self,
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
return 'your vault is closed, thank you for your business';
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Calculate the fee, the amount to mint and the resulting debt.
|
|
444
|
+
* The give and the want together reflect a delta, where typically
|
|
445
|
+
* one is zero because they come from the gave/want of an offer
|
|
446
|
+
* proposal. If the `want` is zero, the `fee` will also be zero,
|
|
447
|
+
* so the simple math works.
|
|
448
|
+
*
|
|
449
|
+
* @param {Amount<'nat'>} currentDebt
|
|
450
|
+
* @param {Amount<'nat'>} giveAmount
|
|
451
|
+
* @param {Amount<'nat'>} wantAmount
|
|
452
|
+
*/
|
|
453
|
+
debtFee(currentDebt, giveAmount, wantAmount) {
|
|
454
|
+
const { state } = this;
|
|
455
|
+
|
|
456
|
+
return calculateDebtCosts(
|
|
457
|
+
currentDebt,
|
|
458
|
+
giveAmount,
|
|
459
|
+
wantAmount,
|
|
460
|
+
state.manager.getGovernedParams().getMintFee(),
|
|
461
|
+
);
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Adjust principal and collateral (atomically for offer safety)
|
|
466
|
+
*
|
|
467
|
+
* @param {ZCFSeat} clientSeat
|
|
468
|
+
* @returns {string} success message
|
|
469
|
+
*/
|
|
470
|
+
adjustBalancesHook(clientSeat) {
|
|
471
|
+
const { state, facets } = this;
|
|
472
|
+
|
|
473
|
+
const { self, helper } = facets;
|
|
474
|
+
const { vaultSeat } = state;
|
|
475
|
+
const fp = helper.fullProposal(clientSeat.getProposal());
|
|
476
|
+
|
|
477
|
+
if (
|
|
478
|
+
allEmpty([
|
|
479
|
+
fp.give.Collateral,
|
|
480
|
+
fp.give.Minted,
|
|
481
|
+
fp.want.Collateral,
|
|
482
|
+
fp.want.Minted,
|
|
483
|
+
])
|
|
484
|
+
) {
|
|
485
|
+
clientSeat.exit();
|
|
486
|
+
return 'no transaction, as requested';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Calculate the fee, the amount to mint and the resulting debt. We'll
|
|
490
|
+
// verify that the target debt doesn't violate the collateralization ratio,
|
|
491
|
+
// then mint, reallocate, and burn.
|
|
492
|
+
const { newDebt, fee, surplus, toMint } = helper.debtFee(
|
|
493
|
+
self.getCurrentDebt(),
|
|
494
|
+
fp.give.Minted,
|
|
495
|
+
fp.want.Minted,
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
const normalizedDebtPre = self.getNormalizedDebt();
|
|
499
|
+
const collateralPre = helper.getCollateralAllocated(vaultSeat);
|
|
500
|
+
|
|
501
|
+
const hasWants = !allEmpty([fp.want.Collateral, fp.want.Minted]);
|
|
502
|
+
if (hasWants) {
|
|
503
|
+
const newCollateral = addSubtract(
|
|
504
|
+
collateralPre,
|
|
505
|
+
fp.give.Collateral,
|
|
506
|
+
fp.want.Collateral,
|
|
507
|
+
);
|
|
508
|
+
helper.assertSufficientCollateral(newCollateral, newDebt);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return helper.commitBalanceAdjustment(
|
|
512
|
+
clientSeat,
|
|
513
|
+
fp,
|
|
514
|
+
{
|
|
515
|
+
newDebt,
|
|
516
|
+
fee,
|
|
517
|
+
surplus,
|
|
518
|
+
toMint,
|
|
519
|
+
},
|
|
520
|
+
{ normalizedDebtPre, collateralPre },
|
|
521
|
+
);
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
*
|
|
526
|
+
* @param {ZCFSeat} clientSeat
|
|
527
|
+
* @param {FullProposal} fp
|
|
528
|
+
* @param {ReturnType<typeof calculateDebtCosts>} costs
|
|
529
|
+
* @param {object} accounting
|
|
530
|
+
* @param {NormalizedDebt} accounting.normalizedDebtPre
|
|
531
|
+
* @param {Amount<'nat'>} accounting.collateralPre
|
|
532
|
+
* @returns {string} success message
|
|
533
|
+
*/
|
|
534
|
+
commitBalanceAdjustment(
|
|
535
|
+
clientSeat,
|
|
536
|
+
fp,
|
|
537
|
+
{ newDebt, fee, surplus, toMint },
|
|
538
|
+
{ normalizedDebtPre, collateralPre },
|
|
539
|
+
) {
|
|
540
|
+
const { state, facets } = this;
|
|
541
|
+
const { helper } = facets;
|
|
542
|
+
const { vaultSeat } = state;
|
|
543
|
+
|
|
544
|
+
const giveMintedTaken = AmountMath.subtract(fp.give.Minted, surplus);
|
|
545
|
+
|
|
546
|
+
/** @type {import('@agoric/zoe/src/contractSupport/atomicTransfer.js').TransferPart[]} */
|
|
547
|
+
const transfers = harden([
|
|
548
|
+
[clientSeat, vaultSeat, { Collateral: fp.give.Collateral }],
|
|
549
|
+
[vaultSeat, clientSeat, { Collateral: fp.want.Collateral }],
|
|
550
|
+
[clientSeat, vaultSeat, { Minted: giveMintedTaken }],
|
|
551
|
+
// Minted into vaultSeat requires minting and so is done by mintAndTransfer
|
|
552
|
+
]);
|
|
553
|
+
|
|
554
|
+
state.manager.mintAndTransfer(clientSeat, toMint, fee, transfers);
|
|
555
|
+
|
|
556
|
+
// parent needs to know about the change in debt
|
|
557
|
+
helper.updateDebtAccounting(
|
|
558
|
+
normalizedDebtPre,
|
|
559
|
+
collateralPre,
|
|
560
|
+
newDebt,
|
|
561
|
+
);
|
|
562
|
+
state.manager.burn(giveMintedTaken, vaultSeat);
|
|
563
|
+
helper.assertVaultHoldsNoMinted();
|
|
564
|
+
|
|
565
|
+
helper.updateUiState();
|
|
566
|
+
clientSeat.exit();
|
|
567
|
+
return 'We have adjusted your balances, thank you for your business';
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
*
|
|
572
|
+
* @param {ZCFSeat} seat
|
|
573
|
+
* @returns {VaultKit}
|
|
574
|
+
*/
|
|
575
|
+
makeTransferInvitationHook(seat) {
|
|
576
|
+
const { state, facets } = this;
|
|
577
|
+
|
|
578
|
+
const { self, helper } = facets;
|
|
579
|
+
helper.assertCloseable();
|
|
580
|
+
seat.exit();
|
|
581
|
+
|
|
582
|
+
// eslint-disable-next-line no-use-before-define
|
|
583
|
+
const vaultKit = makeVaultKit(self, state.storageNode);
|
|
584
|
+
state.outerUpdater = vaultKit.vaultUpdater;
|
|
585
|
+
helper.updateUiState();
|
|
586
|
+
|
|
587
|
+
return vaultKit;
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
self: {
|
|
591
|
+
getVaultSeat() {
|
|
592
|
+
return this.state.vaultSeat;
|
|
593
|
+
},
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* @param {ZCFSeat} seat
|
|
597
|
+
* @param {StorageNode} storageNode
|
|
598
|
+
*/
|
|
599
|
+
async initVaultKit(seat, storageNode) {
|
|
600
|
+
const { state, facets } = this;
|
|
601
|
+
|
|
602
|
+
const { self, helper } = facets;
|
|
603
|
+
|
|
604
|
+
const normalizedDebtPre = self.getNormalizedDebt();
|
|
605
|
+
const actualDebtPre = self.getCurrentDebt();
|
|
606
|
+
(AmountMath.isEmpty(normalizedDebtPre) &&
|
|
607
|
+
AmountMath.isEmpty(actualDebtPre)) ||
|
|
608
|
+
Fail`vault must be empty initially`;
|
|
609
|
+
|
|
610
|
+
const collateralPre = self.getCollateralAmount();
|
|
611
|
+
trace('initVaultKit start: collateral', state.idInManager, {
|
|
612
|
+
actualDebtPre,
|
|
613
|
+
collateralPre,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// get the payout to provide access to the collateral if the
|
|
617
|
+
// contract abandons
|
|
618
|
+
const {
|
|
619
|
+
give: { Collateral: giveCollateral },
|
|
620
|
+
want: { Minted: wantMinted },
|
|
621
|
+
} = seat.getProposal();
|
|
622
|
+
|
|
623
|
+
const minInitialDebt = state.manager
|
|
624
|
+
.getGovernedParams()
|
|
625
|
+
.getMinInitialDebt();
|
|
626
|
+
AmountMath.isGTE(wantMinted, minInitialDebt) ||
|
|
627
|
+
Fail`Vault creation requires a minInitialDebt of ${q(
|
|
628
|
+
minInitialDebt,
|
|
629
|
+
)}`;
|
|
630
|
+
|
|
631
|
+
const {
|
|
632
|
+
newDebt: newDebtPre,
|
|
633
|
+
fee,
|
|
634
|
+
toMint,
|
|
635
|
+
} = helper.debtFee(actualDebtPre, helper.emptyDebt(), wantMinted);
|
|
636
|
+
!AmountMath.isEmpty(fee) ||
|
|
637
|
+
Fail`debt requested (${wantMinted}) too small to accrue interest`;
|
|
638
|
+
AmountMath.isEqual(newDebtPre, toMint) ||
|
|
639
|
+
Fail`fee mismatch for vault`;
|
|
640
|
+
trace(
|
|
641
|
+
'initVault',
|
|
642
|
+
state.idInManager,
|
|
643
|
+
{ wantedRun: wantMinted, fee },
|
|
644
|
+
self.getCollateralAmount(),
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
helper.assertSufficientCollateral(giveCollateral, newDebtPre);
|
|
648
|
+
|
|
649
|
+
const { vaultSeat } = state;
|
|
650
|
+
state.manager.mintAndTransfer(
|
|
651
|
+
seat,
|
|
652
|
+
toMint,
|
|
653
|
+
fee,
|
|
654
|
+
harden([[seat, vaultSeat, { Collateral: giveCollateral }]]),
|
|
655
|
+
);
|
|
656
|
+
seat.exit();
|
|
657
|
+
|
|
658
|
+
helper.updateDebtAccounting(
|
|
659
|
+
normalizedDebtPre,
|
|
660
|
+
collateralPre,
|
|
661
|
+
newDebtPre,
|
|
662
|
+
);
|
|
663
|
+
trace('initVault updateDebtAccounting fired');
|
|
664
|
+
|
|
665
|
+
// So that makeVaultKit can be synchronous
|
|
666
|
+
const vaultKit = makeVaultKit(self, storageNode);
|
|
667
|
+
state.outerUpdater = vaultKit.vaultUpdater;
|
|
668
|
+
helper.updateUiState();
|
|
669
|
+
return vaultKit;
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Called by manager at start of liquidation.
|
|
674
|
+
*/
|
|
675
|
+
liquidating() {
|
|
676
|
+
const { facets } = this;
|
|
677
|
+
|
|
678
|
+
const { helper } = facets;
|
|
679
|
+
helper.assignPhase(Phase.LIQUIDATING);
|
|
680
|
+
helper.updateUiState();
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Called by manager at end of liquidation, at which point all debts have been
|
|
685
|
+
* covered.
|
|
686
|
+
*/
|
|
687
|
+
liquidated() {
|
|
688
|
+
const { facets } = this;
|
|
689
|
+
|
|
690
|
+
const { helper } = facets;
|
|
691
|
+
helper.updateDebtSnapshot(
|
|
692
|
+
// liquidated vaults retain no debt
|
|
693
|
+
AmountMath.makeEmpty(helper.debtBrand()),
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
helper.assignPhase(Phase.LIQUIDATED);
|
|
697
|
+
helper.updateUiState();
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Called by vaultManager when the auction wasn't able to sell the
|
|
702
|
+
* collateral. The liquidation fee was charged against the collateral,
|
|
703
|
+
* but the debt will be restored and the vault will be active again.
|
|
704
|
+
* Liquidation.md has details on the liquidation approach.
|
|
705
|
+
*/
|
|
706
|
+
abortLiquidation() {
|
|
707
|
+
const { state, facets } = this;
|
|
708
|
+
|
|
709
|
+
const { helper } = facets;
|
|
710
|
+
|
|
711
|
+
helper.assignPhase(Phase.ACTIVE);
|
|
712
|
+
helper.updateUiState();
|
|
713
|
+
return state.idInManager;
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
makeAdjustBalancesInvitation() {
|
|
717
|
+
const { state, facets } = this;
|
|
718
|
+
const { helper } = facets;
|
|
719
|
+
helper.assertActive();
|
|
720
|
+
|
|
721
|
+
return zcf.makeInvitation(
|
|
722
|
+
seat => helper.adjustBalancesHook(seat),
|
|
723
|
+
state.manager.scopeDescription('AdjustBalances'),
|
|
724
|
+
undefined,
|
|
725
|
+
M.splitRecord({
|
|
726
|
+
give: M.splitRecord(
|
|
727
|
+
{},
|
|
728
|
+
{
|
|
729
|
+
// It may seem odd to give both at once but there is use case:
|
|
730
|
+
// To rescue a vault that's on the verge of being liquidated
|
|
731
|
+
// when you have limited resources, you might add collateral
|
|
732
|
+
// at the same time that you're repaying IST.
|
|
733
|
+
Collateral: makeNatAmountShape(helper.collateralBrand()),
|
|
734
|
+
Minted: makeNatAmountShape(helper.debtBrand()),
|
|
735
|
+
},
|
|
736
|
+
{},
|
|
737
|
+
),
|
|
738
|
+
want: M.splitRecord(
|
|
739
|
+
{},
|
|
740
|
+
{
|
|
741
|
+
Collateral: makeNatAmountShape(helper.collateralBrand()),
|
|
742
|
+
Minted: makeNatAmountShape(helper.debtBrand()),
|
|
743
|
+
},
|
|
744
|
+
{},
|
|
745
|
+
),
|
|
746
|
+
}),
|
|
747
|
+
);
|
|
748
|
+
},
|
|
749
|
+
|
|
750
|
+
makeCloseInvitation() {
|
|
751
|
+
const { state, facets } = this;
|
|
752
|
+
const { helper } = facets;
|
|
753
|
+
helper.assertCloseable();
|
|
754
|
+
return zcf.makeInvitation(
|
|
755
|
+
seat => helper.closeHook(seat),
|
|
756
|
+
state.manager.scopeDescription('CloseVault'),
|
|
757
|
+
undefined,
|
|
758
|
+
M.splitRecord({
|
|
759
|
+
give: M.splitRecord(
|
|
760
|
+
{},
|
|
761
|
+
{
|
|
762
|
+
Minted: makeNatAmountShape(helper.debtBrand()),
|
|
763
|
+
},
|
|
764
|
+
{},
|
|
765
|
+
),
|
|
766
|
+
want: M.splitRecord(
|
|
767
|
+
{},
|
|
768
|
+
{
|
|
769
|
+
Collateral: makeNatAmountShape(helper.collateralBrand()),
|
|
770
|
+
},
|
|
771
|
+
{},
|
|
772
|
+
),
|
|
773
|
+
}),
|
|
774
|
+
);
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* @returns {Promise<Invitation>}
|
|
779
|
+
*/
|
|
780
|
+
makeTransferInvitation() {
|
|
781
|
+
const { state, facets } = this;
|
|
782
|
+
const { outerUpdater } = state;
|
|
783
|
+
const { self, helper } = facets;
|
|
784
|
+
// Bring the debt snapshot current for the final report before transfer
|
|
785
|
+
helper.updateDebtSnapshot(self.getCurrentDebt());
|
|
786
|
+
const {
|
|
787
|
+
debtSnapshot: debt,
|
|
788
|
+
interestSnapshot: interest,
|
|
789
|
+
phase,
|
|
790
|
+
} = state;
|
|
791
|
+
if (outerUpdater) {
|
|
792
|
+
void outerUpdater.writeFinal(
|
|
793
|
+
helper.getStateSnapshot(Phase.TRANSFER),
|
|
794
|
+
);
|
|
795
|
+
state.outerUpdater = null;
|
|
796
|
+
}
|
|
797
|
+
const transferState = {
|
|
798
|
+
debtSnapshot: { debt, interest },
|
|
799
|
+
locked: self.getCollateralAmount(),
|
|
800
|
+
vaultState: phase,
|
|
801
|
+
};
|
|
802
|
+
return zcf.makeInvitation(
|
|
803
|
+
seat => helper.makeTransferInvitationHook(seat),
|
|
804
|
+
state.manager.scopeDescription('TransferVault'),
|
|
805
|
+
transferState,
|
|
806
|
+
);
|
|
807
|
+
},
|
|
808
|
+
|
|
809
|
+
// for status/debugging
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
*
|
|
813
|
+
* @returns {Amount<'nat'>}
|
|
814
|
+
*/
|
|
815
|
+
getCollateralAmount() {
|
|
816
|
+
const { state, facets } = this;
|
|
817
|
+
const { vaultSeat } = state;
|
|
818
|
+
const { helper } = facets;
|
|
819
|
+
// getCollateralAllocated would return final allocations
|
|
820
|
+
return vaultSeat.hasExited()
|
|
821
|
+
? helper.emptyCollateral()
|
|
822
|
+
: helper.getCollateralAllocated(vaultSeat);
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* The actual current debt, including accrued interest.
|
|
827
|
+
*
|
|
828
|
+
* This looks like a simple getter but it does a lot of the heavy lifting for
|
|
829
|
+
* interest accrual. Rather than updating all records when interest accrues,
|
|
830
|
+
* the vault manager updates just its rolling compounded interest. Here we
|
|
831
|
+
* calculate what the current debt is given what's recorded in this vault and
|
|
832
|
+
* what interest has compounded since this vault record was written.
|
|
833
|
+
*
|
|
834
|
+
* @see getNormalizedDebt
|
|
835
|
+
*
|
|
836
|
+
* @returns {Amount<'nat'>}
|
|
837
|
+
*/
|
|
838
|
+
getCurrentDebt() {
|
|
839
|
+
const { state } = this;
|
|
840
|
+
return calculateCurrentDebt(
|
|
841
|
+
state.debtSnapshot,
|
|
842
|
+
state.interestSnapshot,
|
|
843
|
+
state.manager.getCompoundedInterest(),
|
|
844
|
+
);
|
|
845
|
+
},
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* The normalization puts all debts on a common time-independent scale since
|
|
849
|
+
* the launch of this vault manager. This allows the manager to order vaults
|
|
850
|
+
* by their debt-to-collateral ratios without having to mutate the debts as
|
|
851
|
+
* the interest accrues.
|
|
852
|
+
*
|
|
853
|
+
* @see getActualDebAmount
|
|
854
|
+
*
|
|
855
|
+
* @returns {import('./storeUtils.js').NormalizedDebt} as if the vault was open at the launch of this manager, before any interest accrued
|
|
856
|
+
*/
|
|
857
|
+
getNormalizedDebt() {
|
|
858
|
+
const { state } = this;
|
|
859
|
+
// @ts-expect-error cast
|
|
860
|
+
return reverseInterest(state.debtSnapshot, state.interestSnapshot);
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
stateShape: VaultStateShape,
|
|
866
|
+
},
|
|
867
|
+
);
|
|
868
|
+
return maker;
|
|
580
869
|
};
|
|
581
870
|
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* @param {MethodContext} context
|
|
585
|
-
*/
|
|
586
|
-
getVaultSeat: ({ state }) => state.vaultSeat,
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* @param {MethodContext} context
|
|
590
|
-
* @param {ZCFSeat} seat
|
|
591
|
-
* @param {ERef<StorageNode>} storageNode
|
|
592
|
-
* @param {ERef<Marshaller>} marshaller
|
|
593
|
-
*/
|
|
594
|
-
initVaultKit: async ({ state, facets }, seat, storageNode, marshaller) => {
|
|
595
|
-
assert(seat && storageNode && marshaller, 'initVaultKit missing argument');
|
|
596
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
597
|
-
|
|
598
|
-
const { self, helper } = facets;
|
|
599
|
-
|
|
600
|
-
const normalizedDebtPre = self.getNormalizedDebt();
|
|
601
|
-
const actualDebtPre = self.getCurrentDebt();
|
|
602
|
-
assert(
|
|
603
|
-
AmountMath.isEmpty(normalizedDebtPre) &&
|
|
604
|
-
AmountMath.isEmpty(actualDebtPre),
|
|
605
|
-
X`vault must be empty initially`,
|
|
606
|
-
);
|
|
607
|
-
|
|
608
|
-
const collateralPre = self.getCollateralAmount();
|
|
609
|
-
trace('initVaultKit start: collateral', state.idInManager, {
|
|
610
|
-
actualDebtPre,
|
|
611
|
-
collateralPre,
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
// get the payout to provide access to the collateral if the
|
|
615
|
-
// contract abandons
|
|
616
|
-
const {
|
|
617
|
-
give: { Collateral: giveCollateral },
|
|
618
|
-
want: { Minted: wantMinted },
|
|
619
|
-
} = seat.getProposal();
|
|
620
|
-
|
|
621
|
-
const {
|
|
622
|
-
newDebt: newDebtPre,
|
|
623
|
-
fee,
|
|
624
|
-
toMint,
|
|
625
|
-
} = helper.loanFee(actualDebtPre, helper.emptyDebt(), wantMinted);
|
|
626
|
-
!AmountMath.isEmpty(fee) ||
|
|
627
|
-
assert.fail(
|
|
628
|
-
X`loan requested (${wantMinted}) is too small; cannot accrue interest`,
|
|
629
|
-
);
|
|
630
|
-
assert(AmountMath.isEqual(newDebtPre, toMint), X`fee mismatch for vault`);
|
|
631
|
-
trace(
|
|
632
|
-
'initVault',
|
|
633
|
-
state.idInManager,
|
|
634
|
-
{ wantedRun: wantMinted, fee },
|
|
635
|
-
self.getCollateralAmount(),
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
await helper.assertSufficientCollateral(giveCollateral, newDebtPre);
|
|
639
|
-
|
|
640
|
-
const { vaultSeat } = state;
|
|
641
|
-
vaultSeat.incrementBy(
|
|
642
|
-
seat.decrementBy(harden({ Collateral: giveCollateral })),
|
|
643
|
-
);
|
|
644
|
-
state.manager.mintAndReallocate(toMint, fee, seat, vaultSeat);
|
|
645
|
-
helper.updateDebtAccounting(normalizedDebtPre, collateralPre, newDebtPre);
|
|
646
|
-
|
|
647
|
-
const vaultKit = makeVaultKit(
|
|
648
|
-
self,
|
|
649
|
-
storageNode,
|
|
650
|
-
marshaller,
|
|
651
|
-
state.manager.getAssetSubscriber(),
|
|
652
|
-
);
|
|
653
|
-
ephemera.outerUpdater = vaultKit.vaultUpdater;
|
|
654
|
-
helper.updateUiState();
|
|
655
|
-
return vaultKit;
|
|
656
|
-
},
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Called by manager at start of liquidation.
|
|
660
|
-
*
|
|
661
|
-
* @param {MethodContext} context
|
|
662
|
-
*/
|
|
663
|
-
liquidating: ({ facets }) => {
|
|
664
|
-
const { helper } = facets;
|
|
665
|
-
helper.assignPhase(Phase.LIQUIDATING);
|
|
666
|
-
helper.updateUiState();
|
|
667
|
-
},
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Called by manager at end of liquidation, at which point all debts have been
|
|
671
|
-
* covered.
|
|
672
|
-
*
|
|
673
|
-
* @param {MethodContext} context
|
|
674
|
-
*/
|
|
675
|
-
liquidated: ({ facets }) => {
|
|
676
|
-
const { helper } = facets;
|
|
677
|
-
helper.updateDebtSnapshot(
|
|
678
|
-
// liquidated vaults retain no debt
|
|
679
|
-
AmountMath.makeEmpty(helper.debtBrand()),
|
|
680
|
-
);
|
|
681
|
-
|
|
682
|
-
helper.assignPhase(Phase.LIQUIDATED);
|
|
683
|
-
helper.updateUiState();
|
|
684
|
-
},
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* @param {MethodContext} context
|
|
688
|
-
*/
|
|
689
|
-
makeAdjustBalancesInvitation: ({ state, facets }) => {
|
|
690
|
-
const { helper } = facets;
|
|
691
|
-
helper.assertActive();
|
|
692
|
-
const { zcf } = provideEphemera(state.idInManager);
|
|
693
|
-
return zcf.makeInvitation(
|
|
694
|
-
seat => helper.adjustBalancesHook(seat),
|
|
695
|
-
'AdjustBalances',
|
|
696
|
-
);
|
|
697
|
-
},
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* @param {MethodContext} context
|
|
701
|
-
*/
|
|
702
|
-
makeCloseInvitation: ({ state, facets }) => {
|
|
703
|
-
const { helper } = facets;
|
|
704
|
-
helper.assertCloseable();
|
|
705
|
-
const { zcf } = provideEphemera(state.idInManager);
|
|
706
|
-
return zcf.makeInvitation(seat => helper.closeHook(seat), 'CloseVault');
|
|
707
|
-
},
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* @param {MethodContext} context
|
|
711
|
-
* @returns {Promise<Invitation>}
|
|
712
|
-
*/
|
|
713
|
-
makeTransferInvitation: ({ state, facets }) => {
|
|
714
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
715
|
-
const { outerUpdater } = ephemera;
|
|
716
|
-
const { self, helper } = facets;
|
|
717
|
-
// Bring the debt snapshot current for the final report before transfer
|
|
718
|
-
helper.updateDebtSnapshot(self.getCurrentDebt());
|
|
719
|
-
const { debtSnapshot: debt, interestSnapshot: interest, phase } = state;
|
|
720
|
-
if (outerUpdater) {
|
|
721
|
-
outerUpdater.finish(helper.getStateSnapshot(Phase.TRANSFER));
|
|
722
|
-
ephemera.outerUpdater = null;
|
|
723
|
-
}
|
|
724
|
-
const transferState = {
|
|
725
|
-
debtSnapshot: { debt, interest },
|
|
726
|
-
locked: self.getCollateralAmount(),
|
|
727
|
-
vaultState: phase,
|
|
728
|
-
};
|
|
729
|
-
const { zcf } = provideEphemera(state.idInManager);
|
|
730
|
-
return zcf.makeInvitation(
|
|
731
|
-
seat => helper.makeTransferInvitationHook(seat),
|
|
732
|
-
'TransferVault',
|
|
733
|
-
transferState,
|
|
734
|
-
);
|
|
735
|
-
},
|
|
736
|
-
|
|
737
|
-
// for status/debugging
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
*
|
|
741
|
-
* @param {MethodContext} context
|
|
742
|
-
* @returns {Amount<'nat'>}
|
|
743
|
-
*/
|
|
744
|
-
getCollateralAmount: ({ state, facets }) => {
|
|
745
|
-
const { vaultSeat } = state;
|
|
746
|
-
const { helper } = facets;
|
|
747
|
-
// getCollateralAllocated would return final allocations
|
|
748
|
-
return vaultSeat.hasExited()
|
|
749
|
-
? helper.emptyCollateral()
|
|
750
|
-
: helper.getCollateralAllocated(vaultSeat);
|
|
751
|
-
},
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* The actual current debt, including accrued interest.
|
|
755
|
-
*
|
|
756
|
-
* This looks like a simple getter but it does a lot of the heavy lifting for
|
|
757
|
-
* interest accrual. Rather than updating all records when interest accrues,
|
|
758
|
-
* the vault manager updates just its rolling compounded interest. Here we
|
|
759
|
-
* calculate what the current debt is given what's recorded in this vault and
|
|
760
|
-
* what interest has compounded since this vault record was written.
|
|
761
|
-
*
|
|
762
|
-
* @see getNormalizedDebt
|
|
763
|
-
*
|
|
764
|
-
* @param {MethodContext} context
|
|
765
|
-
* @returns {Amount<'nat'>}
|
|
766
|
-
*/
|
|
767
|
-
getCurrentDebt: ({ state }) => {
|
|
768
|
-
return calculateCurrentDebt(
|
|
769
|
-
state.debtSnapshot,
|
|
770
|
-
state.interestSnapshot,
|
|
771
|
-
state.manager.getCompoundedInterest(),
|
|
772
|
-
);
|
|
773
|
-
},
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* The normalization puts all debts on a common time-independent scale since
|
|
777
|
-
* the launch of this vault manager. This allows the manager to order vaults
|
|
778
|
-
* by their debt-to-collateral ratios without having to mutate the debts as
|
|
779
|
-
* the interest accrues.
|
|
780
|
-
*
|
|
781
|
-
* @see getActualDebAmount
|
|
782
|
-
*
|
|
783
|
-
* @param {MethodContext} context
|
|
784
|
-
* @returns {import('./storeUtils.js').NormalizedDebt} as if the vault was open at the launch of this manager, before any interest accrued
|
|
785
|
-
*/
|
|
786
|
-
getNormalizedDebt: ({ state }) => {
|
|
787
|
-
// @ts-expect-error cast
|
|
788
|
-
return reverseInterest(state.debtSnapshot, state.interestSnapshot);
|
|
789
|
-
},
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
const makeVaultBase = defineDurableKindMulti(
|
|
793
|
-
makeKindHandle('Vault'),
|
|
794
|
-
initState,
|
|
795
|
-
{
|
|
796
|
-
self: selfBehavior,
|
|
797
|
-
helper: helperBehavior,
|
|
798
|
-
},
|
|
799
|
-
);
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* @param {ZCF} zcf
|
|
803
|
-
* @param {VaultManager} manager
|
|
804
|
-
* @param {VaultId} idInManager
|
|
805
|
-
*/
|
|
806
|
-
export const makeVault = pickFacet(makeVaultBase, 'self');
|
|
807
|
-
|
|
808
|
-
/** @typedef {ReturnType<typeof makeVault>} Vault */
|
|
871
|
+
/** @typedef {ReturnType<ReturnType<typeof prepareVault>>['self']} Vault */
|