@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.1.1-upgrade-19-dev-c605745.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.
Files changed (40) hide show
  1. package/README.md +43 -0
  2. package/package.json +29 -25
  3. package/src/add-operators.core.js +63 -0
  4. package/src/cli/bridge-action.js +40 -0
  5. package/src/cli/cli.js +46 -153
  6. package/src/cli/config-commands.js +108 -0
  7. package/src/cli/config.js +15 -9
  8. package/src/cli/lp-commands.js +160 -0
  9. package/src/cli/operator-commands.js +146 -0
  10. package/src/cli/transfer.js +63 -13
  11. package/src/{util → cli/util}/agoric.js +1 -1
  12. package/src/cli/util/bank.js +12 -0
  13. package/src/{util → cli/util}/cctp.js +1 -1
  14. package/src/{util → cli/util}/file.js +1 -1
  15. package/src/clientSupport.js +98 -0
  16. package/src/constants.js +25 -2
  17. package/src/distribute-fees.core.js +93 -0
  18. package/src/exos/advancer.js +220 -106
  19. package/src/exos/liquidity-pool.js +130 -81
  20. package/src/exos/operator-kit.js +16 -12
  21. package/src/exos/settler.js +360 -64
  22. package/src/exos/status-manager.js +316 -65
  23. package/src/exos/transaction-feed.js +121 -42
  24. package/src/fast-usdc-policy.core.js +65 -0
  25. package/src/fast-usdc.contract.js +165 -84
  26. package/src/fast-usdc.flows.js +10 -0
  27. package/src/main.js +1 -0
  28. package/src/pool-share-math.js +55 -9
  29. package/src/{fast-usdc.start.js → start-fast-usdc.core.js} +48 -86
  30. package/src/type-guards.js +75 -24
  31. package/src/types.ts +89 -14
  32. package/src/utils/chain-policies.js +140 -0
  33. package/src/utils/core-eval.js +73 -0
  34. package/src/utils/deploy-config.js +127 -0
  35. package/src/utils/fees.js +3 -4
  36. package/tools/cli-tools.ts +9 -0
  37. package/tools/mock-io.ts +14 -0
  38. package/src/exos/README.md +0 -26
  39. package/src/utils/address.js +0 -71
  40. /package/src/{util → cli/util}/noble.js +0 -0
@@ -1,41 +1,44 @@
1
1
  import { AssetKind } from '@agoric/ertp';
2
- import {
3
- assertAllDefined,
4
- deeplyFulfilledObject,
5
- makeTracer,
6
- } from '@agoric/internal';
2
+ import { makeTracer } from '@agoric/internal';
7
3
  import { observeIteration, subscribeEach } from '@agoric/notifier';
8
4
  import {
5
+ CosmosChainInfoShape,
6
+ DenomDetailShape,
7
+ DenomShape,
9
8
  OrchestrationPowersShape,
9
+ registerChainsAndAssets,
10
10
  withOrchestration,
11
11
  } from '@agoric/orchestration';
12
+ import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
12
13
  import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js';
13
14
  import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
14
- import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
15
- import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
15
+ import { Fail } from '@endo/errors';
16
16
  import { E } from '@endo/far';
17
- import { M, objectMap } from '@endo/patterns';
17
+ import { M } from '@endo/patterns';
18
18
  import { prepareAdvancer } from './exos/advancer.js';
19
19
  import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js';
20
20
  import { prepareSettler } from './exos/settler.js';
21
21
  import { prepareStatusManager } from './exos/status-manager.js';
22
22
  import { prepareTransactionFeedKit } from './exos/transaction-feed.js';
23
- import { defineInertInvitation } from './utils/zoe.js';
24
- import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
25
23
  import * as flows from './fast-usdc.flows.js';
24
+ import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
26
25
 
27
26
  const trace = makeTracer('FastUsdc');
28
27
 
28
+ const TXNS_NODE = 'txns';
29
+ const FEE_NODE = 'feeConfig';
30
+ const ADDRESSES_BAGGAGE_KEY = 'addresses';
31
+
29
32
  /**
30
- * @import {Denom} from '@agoric/orchestration';
31
33
  * @import {HostInterface} from '@agoric/async-flow';
32
- * @import {OrchestrationAccount} from '@agoric/orchestration';
34
+ * @import {ChainAddress, CosmosChainInfo, Denom, DenomDetail, OrchestrationAccount} from '@agoric/orchestration';
33
35
  * @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js';
34
- * @import {Vow} from '@agoric/vow';
36
+ * @import {Remote} from '@agoric/internal';
37
+ * @import {Marshaller, StorageNode} from '@agoric/internal/src/lib-chainStorage.js'
35
38
  * @import {Zone} from '@agoric/zone';
39
+ * @import {OperatorOfferResult} from './exos/transaction-feed.js';
36
40
  * @import {OperatorKit} from './exos/operator-kit.js';
37
- * @import {CctpTxEvidence, FeeConfig} from './types.js';
38
- * @import {RepayAmountKWR, RepayPaymentKWR} from './exos/liquidity-pool.js';
41
+ * @import {CctpTxEvidence, ContractRecord, FeeConfig} from './types.js';
39
42
  */
40
43
 
41
44
  /**
@@ -51,17 +54,43 @@ export const meta = {
51
54
  privateArgsShape: {
52
55
  // @ts-expect-error TypedPattern not recognized as record
53
56
  ...OrchestrationPowersShape,
57
+ assetInfo: M.arrayOf([DenomShape, DenomDetailShape]),
58
+ chainInfo: M.recordOf(M.string(), CosmosChainInfoShape),
54
59
  feeConfig: FeeConfigShape,
55
60
  marshaller: M.remotable(),
61
+ poolMetricsNode: M.remotable(),
56
62
  },
57
63
  };
58
64
  harden(meta);
59
65
 
66
+ /**
67
+ * @param {Remote<StorageNode>} node
68
+ * @param {ERef<Marshaller>} marshaller
69
+ * @param {FeeConfig} feeConfig
70
+ */
71
+ const publishFeeConfig = async (node, marshaller, feeConfig) => {
72
+ const feeNode = E(node).makeChildNode(FEE_NODE);
73
+ const value = await E(marshaller).toCapData(feeConfig);
74
+ return E(feeNode).setValue(JSON.stringify(value));
75
+ };
76
+
77
+ /**
78
+ * @param {Remote<StorageNode>} contractNode
79
+ * @param {ContractRecord} addresses
80
+ */
81
+ const publishAddresses = (contractNode, addresses) => {
82
+ return E(contractNode).setValue(JSON.stringify(addresses));
83
+ };
84
+
60
85
  /**
61
86
  * @param {ZCF<FastUsdcTerms>} zcf
62
87
  * @param {OrchestrationPowers & {
63
- * marshaller: Marshaller;
88
+ * assetInfo: [Denom, DenomDetail & { brandKey?: string}][];
89
+ * chainInfo: Record<string, CosmosChainInfo>;
64
90
  * feeConfig: FeeConfig;
91
+ * marshaller: Marshaller;
92
+ * poolMetricsNode: Remote<StorageNode>;
93
+ * storageNode: Remote<StorageNode>;
65
94
  * }} privateArgs
66
95
  * @param {Zone} zone
67
96
  * @param {OrchestrationTools} tools
@@ -71,19 +100,36 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
71
100
  const terms = zcf.getTerms();
72
101
  assert('USDC' in terms.brands, 'no USDC brand');
73
102
  assert('usdcDenom' in terms, 'no usdcDenom');
74
- const { feeConfig, marshaller } = privateArgs;
103
+
104
+ const { feeConfig, marshaller, storageNode } = privateArgs;
75
105
  const { makeRecorderKit } = prepareRecorderKitMakers(
76
106
  zone.mapStore('vstorage'),
77
107
  marshaller,
78
108
  );
79
- const statusManager = prepareStatusManager(zone);
80
- const makeSettler = prepareSettler(zone, { statusManager });
81
- const { chainHub, orchestrateAll, vowTools } = tools;
82
- const { localTransfer } = makeZoeTools(zcf, vowTools);
109
+
110
+ const statusManager = prepareStatusManager(
111
+ zone,
112
+ E(storageNode).makeChildNode(TXNS_NODE),
113
+ { marshaller },
114
+ );
115
+
116
+ const { USDC } = terms.brands;
117
+ const { withdrawToSeat } = tools.zoeTools;
118
+ const { baggage, chainHub, orchestrateAll, vowTools } = tools;
119
+ const makeSettler = prepareSettler(zone, {
120
+ statusManager,
121
+ USDC,
122
+ withdrawToSeat,
123
+ feeConfig,
124
+ vowTools: tools.vowTools,
125
+ zcf,
126
+ chainHub,
127
+ });
128
+
129
+ const zoeTools = makeZoeTools(zcf, vowTools);
83
130
  const makeAdvancer = prepareAdvancer(zone, {
84
131
  chainHub,
85
132
  feeConfig,
86
- localTransfer,
87
133
  usdc: harden({
88
134
  brand: terms.brands.USDC,
89
135
  denom: terms.usdcDenom,
@@ -91,9 +137,11 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
91
137
  statusManager,
92
138
  vowTools,
93
139
  zcf,
140
+ zoeTools,
94
141
  });
142
+
95
143
  const makeFeedKit = prepareTransactionFeedKit(zone, zcf);
96
- assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager });
144
+
97
145
  const makeLiquidityPoolKit = prepareLiquidityPoolKit(
98
146
  zone,
99
147
  zcf,
@@ -101,66 +149,57 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
101
149
  { makeRecorderKit },
102
150
  );
103
151
 
104
- const makeTestInvitation = defineInertInvitation(
105
- zcf,
106
- 'test of forcing evidence',
107
- );
108
-
109
- const { makeLocalAccount } = orchestrateAll(flows, {});
152
+ const { makeLocalAccount, makeNobleAccount } = orchestrateAll(flows, {});
110
153
 
111
154
  const creatorFacet = zone.exo('Fast USDC Creator', undefined, {
112
- /** @type {(operatorId: string) => Promise<Invitation<OperatorKit>>} */
155
+ /** @type {(operatorId: string) => Promise<Invitation<OperatorOfferResult>>} */
113
156
  async makeOperatorInvitation(operatorId) {
114
- // eslint-disable-next-line no-use-before-define
115
157
  return feedKit.creator.makeOperatorInvitation(operatorId);
116
158
  },
117
- /**
118
- * @param {{ USDC: Amount<'nat'>}} amounts
119
- */
120
- testBorrow(amounts) {
121
- console.log('🚧🚧 UNTIL: borrow is integrated (#10388) 🚧🚧', amounts);
122
- const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit();
123
- poolKit.borrower.borrow(tmpAssetManagerSeat, amounts);
124
- return tmpAssetManagerSeat.getCurrentAllocation();
159
+ /** @type {(operatorId: string) => void} */
160
+ removeOperator(operatorId) {
161
+ return feedKit.creator.removeOperator(operatorId);
162
+ },
163
+ async getContractFeeBalance() {
164
+ return poolKit.feeRecipient.getContractFeeBalance();
165
+ },
166
+ /** @type {() => Promise<Invitation<unknown>>} */
167
+ async makeWithdrawFeesInvitation() {
168
+ return poolKit.feeRecipient.makeWithdrawFeesInvitation();
125
169
  },
126
- /**
127
- *
128
- * @param {RepayAmountKWR} amounts
129
- * @param {RepayPaymentKWR} payments
130
- * @returns {Promise<AmountKeywordRecord>}
131
- */
132
- async testRepay(amounts, payments) {
133
- console.log('🚧🚧 UNTIL: repay is integrated (#10388) 🚧🚧', amounts);
134
- const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit();
135
- await depositToSeat(
136
- zcf,
137
- tmpAssetManagerSeat,
138
- await deeplyFulfilledObject(
139
- objectMap(payments, pmt => E(terms.issuers.USDC).getAmountOf(pmt)),
140
- ),
141
- payments,
170
+ async connectToNoble() {
171
+ return vowTools.when(nobleAccountV, nobleAccount => {
172
+ trace('nobleAccount', nobleAccount);
173
+ return vowTools.when(
174
+ E(nobleAccount).getAddress(),
175
+ intermediateRecipient => {
176
+ trace('intermediateRecipient', intermediateRecipient);
177
+ advancer.setIntermediateRecipient(intermediateRecipient);
178
+ settlerKit.creator.setIntermediateRecipient(intermediateRecipient);
179
+ return intermediateRecipient;
180
+ },
181
+ );
182
+ });
183
+ },
184
+ async publishAddresses() {
185
+ !baggage.has(ADDRESSES_BAGGAGE_KEY) || Fail`Addresses already published`;
186
+ const [poolAccountAddress] = await vowTools.when(
187
+ vowTools.all([E(poolAccount).getAddress()]),
142
188
  );
143
- poolKit.repayer.repay(tmpAssetManagerSeat, amounts);
144
- return tmpAssetManagerSeat.getCurrentAllocation();
189
+ const addresses = harden({
190
+ poolAccount: poolAccountAddress.value,
191
+ settlementAccount: settlementAddress.value,
192
+ });
193
+ baggage.init(ADDRESSES_BAGGAGE_KEY, addresses);
194
+ await publishAddresses(storageNode, addresses);
195
+ return addresses;
196
+ },
197
+ deleteCompletedTxs() {
198
+ return statusManager.deleteCompletedTxs();
145
199
  },
146
200
  });
147
201
 
148
202
  const publicFacet = zone.exo('Fast USDC Public', undefined, {
149
- // XXX to be removed before production
150
- /**
151
- * NB: Any caller with access to this invitation maker has the ability to
152
- * force handling of evidence.
153
- *
154
- * Provide an API call in the form of an invitation maker, so that the
155
- * capability is available in the smart-wallet bridge during UI testing.
156
- *
157
- * @param {CctpTxEvidence} evidence
158
- */
159
- makeTestPushInvitation(evidence) {
160
- // eslint-disable-next-line no-use-before-define
161
- void advancer.handleTransactionEvent(evidence);
162
- return makeTestInvitation();
163
- },
164
203
  makeDepositInvitation() {
165
204
  return poolKit.public.makeDepositInvitation();
166
205
  },
@@ -170,6 +209,13 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
170
209
  getPublicTopics() {
171
210
  return poolKit.public.getPublicTopics();
172
211
  },
212
+ getStaticInfo() {
213
+ baggage.has(ADDRESSES_BAGGAGE_KEY) ||
214
+ Fail`no addresses. creator must 'publishAddresses' first`;
215
+ return harden({
216
+ [ADDRESSES_BAGGAGE_KEY]: baggage.get(ADDRESSES_BAGGAGE_KEY),
217
+ });
218
+ },
173
219
  });
174
220
 
175
221
  // ^^^ Define all kinds above this line. Keep remote calls below. vvv
@@ -185,6 +231,8 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
185
231
  // So we use zone.exoClassKit above to define the liquidity pool kind
186
232
  // and pass the shareMint into the maker / init function.
187
233
 
234
+ void publishFeeConfig(storageNode, marshaller, feeConfig);
235
+
188
236
  const shareMint = await provideSingleton(
189
237
  zone.mapStore('mint'),
190
238
  'PoolShare',
@@ -195,37 +243,70 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
195
243
  );
196
244
 
197
245
  const poolKit = zone.makeOnce('Liquidity Pool kit', () =>
198
- makeLiquidityPoolKit(shareMint, privateArgs.storageNode),
246
+ makeLiquidityPoolKit(shareMint, privateArgs.poolMetricsNode),
199
247
  );
200
248
 
249
+ /** Chain, connection, and asset info can only be registered once */
250
+ const firstIncarnationKey = 'firstIncarnationKey';
251
+ if (!baggage.has(firstIncarnationKey)) {
252
+ baggage.init(firstIncarnationKey, true);
253
+ registerChainsAndAssets(
254
+ chainHub,
255
+ terms.brands,
256
+ privateArgs.chainInfo,
257
+ privateArgs.assetInfo,
258
+ );
259
+ }
260
+
261
+ const nobleAccountV = zone.makeOnce('NobleAccount', () => makeNobleAccount());
262
+
201
263
  const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit());
202
264
 
203
- const poolAccountV =
204
- // cast to HostInterface
205
- /** @type { Vow<HostInterface<OrchestrationAccount<{chainId: 'agoric';}>>>} */ (
206
- /** @type {unknown}*/ (
207
- zone.makeOnce('Pool Local Orch Account', () => makeLocalAccount())
208
- )
209
- );
210
- const poolAccount = await vowTools.when(poolAccountV);
265
+ const poolAccountV = zone.makeOnce('PoolAccount', () => makeLocalAccount());
266
+ const settleAccountV = zone.makeOnce('SettleAccount', () =>
267
+ makeLocalAccount(),
268
+ );
269
+ // when() is OK here since this clearly resolves promptly.
270
+ /** @type {[HostInterface<OrchestrationAccount<{chainId: 'agoric-3';}>>, HostInterface<OrchestrationAccount<{chainId: 'agoric-3';}>>]} */
271
+ const [poolAccount, settlementAccount] = await vowTools.when(
272
+ vowTools.all([poolAccountV, settleAccountV]),
273
+ );
274
+ trace('settlementAccount', settlementAccount);
275
+ trace('poolAccount', poolAccount);
276
+ const settlementAddress = await E(settlementAccount).getAddress();
277
+ trace('settlementAddress', settlementAddress);
278
+
279
+ const [_agoric, _noble, agToNoble] = await vowTools.when(
280
+ chainHub.getChainsAndConnection('agoric', 'noble'),
281
+ );
282
+ const settlerKit = makeSettler({
283
+ repayer: poolKit.repayer,
284
+ sourceChannel: agToNoble.transferChannel.counterPartyChannelId,
285
+ remoteDenom: 'uusdc',
286
+ settlementAccount,
287
+ });
211
288
 
212
289
  const advancer = zone.makeOnce('Advancer', () =>
213
290
  makeAdvancer({
214
- borrowerFacet: poolKit.borrower,
291
+ borrower: poolKit.borrower,
292
+ notifier: settlerKit.notifier,
215
293
  poolAccount,
294
+ settlementAddress,
216
295
  }),
217
296
  );
218
297
  // Connect evidence stream to advancer
219
298
  void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), {
220
- updateState(evidence) {
299
+ updateState(evidenceWithRisk) {
221
300
  try {
222
- void advancer.handleTransactionEvent(evidence);
301
+ void advancer.handleTransactionEvent(evidenceWithRisk);
223
302
  } catch (err) {
224
303
  trace('🚨 Error handling transaction event', err);
225
304
  }
226
305
  },
227
306
  });
228
307
 
308
+ await settlerKit.creator.monitorMintingDeposits();
309
+
229
310
  return harden({ creatorFacet, publicFacet });
230
311
  };
231
312
  harden(contract);
@@ -11,3 +11,13 @@ export const makeLocalAccount = async orch => {
11
11
  return agoricChain.makeAccount();
12
12
  };
13
13
  harden(makeLocalAccount);
14
+
15
+ /**
16
+ * @satisfies {OrchestrationFlow}
17
+ * @param {Orchestrator} orch
18
+ */
19
+ export const makeNobleAccount = async orch => {
20
+ const nobleChain = await orch.getChain('noble');
21
+ return nobleChain.makeAccount();
22
+ };
23
+ harden(makeNobleAccount);
package/src/main.js ADDED
@@ -0,0 +1 @@
1
+ export * from './types.js';
@@ -7,9 +7,11 @@ import {
7
7
  } from '@agoric/zoe/src/contractSupport/ratio.js';
8
8
  import { Fail, q } from '@endo/errors';
9
9
 
10
- const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath;
10
+ const { keys } = Object;
11
+ const { add, isEmpty, isEqual, isGTE, make, makeEmpty, subtract } = AmountMath;
11
12
 
12
13
  /**
14
+ * @import {Amount, Brand, DepositFacet, NatValue, Payment} from '@agoric/ertp';
13
15
  * @import {PoolStats} from './types';
14
16
  * @import {RepayAmountKWR} from './exos/liquidity-pool';
15
17
  */
@@ -17,8 +19,7 @@ const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath;
17
19
  /**
18
20
  * Invariant: shareWorth is the pool balance divided by shares outstanding.
19
21
  *
20
- * Use `makeParity(make(USDC, epsilon), PoolShares)` for an initial
21
- * value, for some negligible `epsilon` such as 1n.
22
+ * Use `makeParity(USDC, PoolShares)` for an initial value.
22
23
  *
23
24
  * @typedef {Ratio} ShareWorth
24
25
  */
@@ -26,23 +27,26 @@ const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath;
26
27
  /**
27
28
  * Make a 1-to-1 ratio between amounts of 2 brands.
28
29
  *
29
- * @param {Amount<'nat'>} numerator
30
+ * @param {Brand<'nat'>} numeratorBrand
30
31
  * @param {Brand<'nat'>} denominatorBrand
31
32
  */
32
- export const makeParity = (numerator, denominatorBrand) => {
33
- const value = getValue(numerator.brand, numerator);
34
- return makeRatio(value, numerator.brand, value, denominatorBrand);
33
+ export const makeParity = (numeratorBrand, denominatorBrand) => {
34
+ const dust = 1n;
35
+ return makeRatio(dust, numeratorBrand, dust, denominatorBrand);
35
36
  };
36
37
 
37
38
  /**
38
39
  * @typedef {{
39
40
  * deposit: {
40
41
  * give: { USDC: Amount<'nat'> },
41
- * want?: { PoolShare: Amount<'nat'> }
42
+ * want: { PoolShare: Amount<'nat'> }
42
43
  * },
43
44
  * withdraw: {
44
45
  * give: { PoolShare: Amount<'nat'> }
45
46
  * want: { USDC: Amount<'nat'> },
47
+ * },
48
+ * withdrawFees: {
49
+ * want: { USDC: Amount<'nat'> }
46
50
  * }
47
51
  * }} USDCProposalShapes
48
52
  */
@@ -91,14 +95,54 @@ export const depositCalc = (shareWorth, { give, want }) => {
91
95
  });
92
96
  };
93
97
 
98
+ /**
99
+ * Verifies that the total pool balance (unencumbered + encumbered) matches the
100
+ * shareWorth numerator. The total pool balance consists of:
101
+ * 1. unencumbered balance - USDC available in the pool for borrowing
102
+ * 2. encumbered balance - USDC currently lent out
103
+ *
104
+ * A negligible `dust` amount is used to initialize shareWorth with a non-zero
105
+ * denominator. It must remain in the pool at all times.
106
+ *
107
+ * @param {Allocation} poolAlloc
108
+ * @param {ShareWorth} shareWorth
109
+ * @param {Amount<'nat'>} encumberedBalance
110
+ */
111
+ export const checkPoolBalance = (poolAlloc, shareWorth, encumberedBalance) => {
112
+ const { brand: usdcBrand } = encumberedBalance;
113
+ const unencumberedBalance = poolAlloc.USDC || makeEmpty(usdcBrand);
114
+ const kwds = keys(poolAlloc);
115
+ kwds.length === 0 ||
116
+ (kwds.length === 1 && kwds[0] === 'USDC') ||
117
+ Fail`unexpected pool allocations: ${poolAlloc}`;
118
+ const dust = make(usdcBrand, 1n);
119
+ const grossBalance = add(add(unencumberedBalance, dust), encumberedBalance);
120
+ isEqual(grossBalance, shareWorth.numerator) ||
121
+ Fail`🚨 pool balance ${q(unencumberedBalance)} and encumbered balance ${q(encumberedBalance)} inconsistent with shareWorth ${q(shareWorth)}`;
122
+ return harden({ unencumberedBalance, grossBalance });
123
+ };
124
+
94
125
  /**
95
126
  * Compute payout from a withdraw proposal, along with updated shareWorth
96
127
  *
97
128
  * @param {ShareWorth} shareWorth
98
129
  * @param {USDCProposalShapes['withdraw']} proposal
130
+ * @param {Allocation} poolAlloc
131
+ * @param {Amount<'nat'>} [encumberedBalance]
99
132
  * @returns {{ shareWorth: ShareWorth, payouts: { USDC: Amount<'nat'> }}}
100
133
  */
101
- export const withdrawCalc = (shareWorth, { give, want }) => {
134
+ export const withdrawCalc = (
135
+ shareWorth,
136
+ { give, want },
137
+ poolAlloc,
138
+ encumberedBalance = makeEmpty(shareWorth.numerator.brand),
139
+ ) => {
140
+ const { unencumberedBalance } = checkPoolBalance(
141
+ poolAlloc,
142
+ shareWorth,
143
+ encumberedBalance,
144
+ );
145
+
102
146
  assert(!isEmpty(give.PoolShare));
103
147
  assert(!isEmpty(want.USDC));
104
148
 
@@ -108,6 +152,8 @@ export const withdrawCalc = (shareWorth, { give, want }) => {
108
152
  const { denominator: sharesOutstanding, numerator: poolBalance } = shareWorth;
109
153
  !isGTE(want.USDC, poolBalance) ||
110
154
  Fail`cannot withdraw ${q(want.USDC)}; only ${q(poolBalance)} in pool`;
155
+ isGTE(unencumberedBalance, want.USDC) ||
156
+ Fail`cannot withdraw ${q(want.USDC)}; ${q(encumberedBalance)} is in use; stand by for pool to return to ${q(poolBalance)}`;
111
157
  const balancePost = subtract(poolBalance, payout);
112
158
  // giving more shares than are outstanding is impossible,
113
159
  // so it's not worth a custom diagnostic. subtract will fail