@agoric/fast-usdc 0.2.0-upgrade-20-dev-ef71cfd.0 → 0.2.0-upgrade-19-devnet-dev-5428c4d.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.
@@ -0,0 +1,65 @@
1
+ /** @file core-eval to publish update to Fast USDC feedPolicy */
2
+
3
+ import { E } from '@endo/far';
4
+ import { fromExternalConfig } from './utils/config-marshal.js';
5
+ import { FeedPolicyShape } from './type-guards.js';
6
+ import { publishFeedPolicy } from './utils/core-eval.js';
7
+
8
+ /**
9
+ * @import {Issuer} from '@agoric/ertp';
10
+ * @import {Passable} from '@endo/pass-style'
11
+ * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js'
12
+ * @import {LegibleCapData} from './utils/config-marshal.js'
13
+ * @import {FeedPolicy} from './types.js'
14
+ */
15
+
16
+ const contractName = 'fastUsdc';
17
+
18
+ /**
19
+ * @param {BootstrapPowers &
20
+ * { consume: { chainStorage: Promise<StorageNode> }}
21
+ * } powers
22
+ * @param {{ options: LegibleCapData<{feedPolicy: FeedPolicy & Passable}> }} config
23
+ */
24
+ export const updateFastUsdcPolicy = async (
25
+ { consume: { agoricNames, chainStorage } },
26
+ config,
27
+ ) => {
28
+ /** @type {Issuer<'nat'>} */
29
+ const USDCissuer = await E(agoricNames).lookup('issuer', 'USDC');
30
+ const brands = harden({
31
+ USDC: await E(USDCissuer).getBrand(),
32
+ });
33
+ const { feedPolicy } = fromExternalConfig(
34
+ config.options,
35
+ brands,
36
+ harden({ feedPolicy: FeedPolicyShape }),
37
+ );
38
+
39
+ const storageNode = await E(chainStorage).makeChildNode(contractName);
40
+
41
+ await publishFeedPolicy(storageNode, feedPolicy);
42
+ };
43
+
44
+ /**
45
+ * @param {unknown} _utils
46
+ * @param {{
47
+ * options: LegibleCapData<{feedPolicy: FeedPolicy & Passable}>;
48
+ * }} param1
49
+ */
50
+ export const getManifestForUpdateFastUsdcPolicy = (_utils, { options }) => {
51
+ return {
52
+ /** @type {BootstrapManifest} */
53
+ manifest: {
54
+ [updateFastUsdcPolicy.name]: {
55
+ consume: {
56
+ chainStorage: true,
57
+
58
+ // widely shared: name services
59
+ agoricNames: true,
60
+ },
61
+ },
62
+ },
63
+ options,
64
+ };
65
+ };
@@ -0,0 +1,316 @@
1
+ import { AssetKind } from '@agoric/ertp';
2
+ import { makeTracer } from '@agoric/internal';
3
+ import { observeIteration, subscribeEach } from '@agoric/notifier';
4
+ import {
5
+ CosmosChainInfoShape,
6
+ DenomDetailShape,
7
+ DenomShape,
8
+ OrchestrationPowersShape,
9
+ registerChainsAndAssets,
10
+ withOrchestration,
11
+ } from '@agoric/orchestration';
12
+ import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
13
+ import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js';
14
+ import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
15
+ import { Fail } from '@endo/errors';
16
+ import { E } from '@endo/far';
17
+ import { M } from '@endo/patterns';
18
+ import { prepareAdvancer } from './exos/advancer.js';
19
+ import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js';
20
+ import { prepareSettler } from './exos/settler.js';
21
+ import { prepareStatusManager } from './exos/status-manager.js';
22
+ import { prepareTransactionFeedKit } from './exos/transaction-feed.js';
23
+ import * as flows from './fast-usdc.flows.js';
24
+ import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
25
+
26
+ const trace = makeTracer('FastUsdc');
27
+
28
+ const TXNS_NODE = 'txns';
29
+ const FEE_NODE = 'feeConfig';
30
+ const ADDRESSES_BAGGAGE_KEY = 'addresses';
31
+
32
+ /**
33
+ * @import {HostInterface} from '@agoric/async-flow';
34
+ * @import {ChainAddress, CosmosChainInfo, Denom, DenomDetail, OrchestrationAccount} from '@agoric/orchestration';
35
+ * @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js';
36
+ * @import {Remote} from '@agoric/internal';
37
+ * @import {Marshaller, StorageNode} from '@agoric/internal/src/lib-chainStorage.js'
38
+ * @import {Zone} from '@agoric/zone';
39
+ * @import {OperatorOfferResult} from './exos/transaction-feed.js';
40
+ * @import {OperatorKit} from './exos/operator-kit.js';
41
+ * @import {CctpTxEvidence, ContractRecord, FeeConfig} from './types.js';
42
+ */
43
+
44
+ /**
45
+ * @typedef {{
46
+ * usdcDenom: Denom;
47
+ * }} FastUsdcTerms
48
+ */
49
+
50
+ /** @type {ContractMeta<typeof start>} */
51
+ export const meta = {
52
+ // @ts-expect-error TypedPattern not recognized as record
53
+ customTermsShape: FastUSDCTermsShape,
54
+ privateArgsShape: {
55
+ // @ts-expect-error TypedPattern not recognized as record
56
+ ...OrchestrationPowersShape,
57
+ assetInfo: M.arrayOf([DenomShape, DenomDetailShape]),
58
+ chainInfo: M.recordOf(M.string(), CosmosChainInfoShape),
59
+ feeConfig: FeeConfigShape,
60
+ marshaller: M.remotable(),
61
+ poolMetricsNode: M.remotable(),
62
+ },
63
+ };
64
+ harden(meta);
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
+
85
+ /**
86
+ * @param {ZCF<FastUsdcTerms>} zcf
87
+ * @param {OrchestrationPowers & {
88
+ * assetInfo: [Denom, DenomDetail & { brandKey?: string}][];
89
+ * chainInfo: Record<string, CosmosChainInfo>;
90
+ * feeConfig: FeeConfig;
91
+ * marshaller: Marshaller;
92
+ * poolMetricsNode: Remote<StorageNode>;
93
+ * storageNode: Remote<StorageNode>;
94
+ * }} privateArgs
95
+ * @param {Zone} zone
96
+ * @param {OrchestrationTools} tools
97
+ */
98
+ export const contract = async (zcf, privateArgs, zone, tools) => {
99
+ assert(tools, 'no tools');
100
+ const terms = zcf.getTerms();
101
+ assert('USDC' in terms.brands, 'no USDC brand');
102
+ assert('usdcDenom' in terms, 'no usdcDenom');
103
+
104
+ const { feeConfig, marshaller, storageNode } = privateArgs;
105
+ const { makeRecorderKit } = prepareRecorderKitMakers(
106
+ zone.mapStore('vstorage'),
107
+ marshaller,
108
+ );
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);
130
+ const makeAdvancer = prepareAdvancer(zone, {
131
+ chainHub,
132
+ feeConfig,
133
+ usdc: harden({
134
+ brand: terms.brands.USDC,
135
+ denom: terms.usdcDenom,
136
+ }),
137
+ statusManager,
138
+ vowTools,
139
+ zcf,
140
+ zoeTools,
141
+ });
142
+
143
+ const makeFeedKit = prepareTransactionFeedKit(zone, zcf);
144
+
145
+ const makeLiquidityPoolKit = prepareLiquidityPoolKit(
146
+ zone,
147
+ zcf,
148
+ terms.brands.USDC,
149
+ { makeRecorderKit },
150
+ );
151
+
152
+ const { makeLocalAccount, makeNobleAccount } = orchestrateAll(flows, {});
153
+
154
+ const creatorFacet = zone.exo('Fast USDC Creator', undefined, {
155
+ /** @type {(operatorId: string) => Promise<Invitation<OperatorOfferResult>>} */
156
+ async makeOperatorInvitation(operatorId) {
157
+ return feedKit.creator.makeOperatorInvitation(operatorId);
158
+ },
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();
169
+ },
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()]),
188
+ );
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();
199
+ },
200
+ });
201
+
202
+ const publicFacet = zone.exo('Fast USDC Public', undefined, {
203
+ makeDepositInvitation() {
204
+ return poolKit.public.makeDepositInvitation();
205
+ },
206
+ makeWithdrawInvitation() {
207
+ return poolKit.public.makeWithdrawInvitation();
208
+ },
209
+ getPublicTopics() {
210
+ return poolKit.public.getPublicTopics();
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
+ },
219
+ });
220
+
221
+ // ^^^ Define all kinds above this line. Keep remote calls below. vvv
222
+
223
+ // NOTE: Using a ZCFMint is helpful for the usual reasons (
224
+ // synchronous mint/burn, keeping assets out of contract vats, ...).
225
+ // And there's just one pool, which suggests building it with zone.exo().
226
+ //
227
+ // But zone.exo() defines a kind and
228
+ // all kinds have to be defined before any remote calls,
229
+ // such as the one to the zoe vat as part of making a ZCFMint.
230
+ //
231
+ // So we use zone.exoClassKit above to define the liquidity pool kind
232
+ // and pass the shareMint into the maker / init function.
233
+
234
+ void publishFeeConfig(storageNode, marshaller, feeConfig);
235
+
236
+ const shareMint = await provideSingleton(
237
+ zone.mapStore('mint'),
238
+ 'PoolShare',
239
+ () =>
240
+ zcf.makeZCFMint('PoolShares', AssetKind.NAT, {
241
+ decimalPlaces: 6,
242
+ }),
243
+ );
244
+
245
+ const poolKit = zone.makeOnce('Liquidity Pool kit', () =>
246
+ makeLiquidityPoolKit(shareMint, privateArgs.poolMetricsNode),
247
+ );
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
+
263
+ const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit());
264
+
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
+ });
288
+
289
+ const advancer = zone.makeOnce('Advancer', () =>
290
+ makeAdvancer({
291
+ borrower: poolKit.borrower,
292
+ notifier: settlerKit.notifier,
293
+ poolAccount,
294
+ settlementAddress,
295
+ }),
296
+ );
297
+ // Connect evidence stream to advancer
298
+ void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), {
299
+ updateState(evidenceWithRisk) {
300
+ try {
301
+ void advancer.handleTransactionEvent(evidenceWithRisk);
302
+ } catch (err) {
303
+ trace('🚨 Error handling transaction event', err);
304
+ }
305
+ },
306
+ });
307
+
308
+ await settlerKit.creator.monitorMintingDeposits();
309
+
310
+ return harden({ creatorFacet, publicFacet });
311
+ };
312
+ harden(contract);
313
+
314
+ export const start = withOrchestration(contract);
315
+ harden(start);
316
+ /** @typedef {typeof start} FastUsdcSF */
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration';
3
+ */
4
+
5
+ /**
6
+ * @satisfies {OrchestrationFlow}
7
+ * @param {Orchestrator} orch
8
+ */
9
+ export const makeLocalAccount = async orch => {
10
+ const agoricChain = await orch.getChain('agoric');
11
+ return agoricChain.makeAccount();
12
+ };
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);
@@ -4,17 +4,16 @@ import {
4
4
  makeRatio,
5
5
  makeRatioFromAmounts,
6
6
  multiplyBy,
7
- } from '@agoric/ertp/src/ratio.js';
7
+ } from '@agoric/zoe/src/contractSupport/ratio.js';
8
8
  import { Fail, q } from '@endo/errors';
9
9
 
10
10
  const { keys } = Object;
11
11
  const { add, isEmpty, isEqual, isGTE, make, makeEmpty, subtract } = AmountMath;
12
12
 
13
13
  /**
14
- * @import {Amount, Brand, DepositFacet, NatValue, Payment, Ratio} from '@agoric/ertp';
15
- * @import {Allocation} from '@agoric/zoe';
16
- * @import {PoolStats} from './types.js';
17
- * @import {RepayAmountKWR} from './utils/fees.js';
14
+ * @import {Amount, Brand, DepositFacet, NatValue, Payment} from '@agoric/ertp';
15
+ * @import {PoolStats} from './types';
16
+ * @import {RepayAmountKWR} from './exos/liquidity-pool';
18
17
  */
19
18
 
20
19
  /**
@@ -202,26 +201,35 @@ export const borrowCalc = (
202
201
 
203
202
  /**
204
203
  * @param {ShareWorth} shareWorth
205
- * @param {RepayAmountKWR} split
204
+ * @param {Allocation} fromSeatAllocation
205
+ * @param {RepayAmountKWR} amounts
206
206
  * @param {Amount<'nat'>} encumberedBalance aka 'outstanding borrows'
207
207
  * @param {PoolStats} poolStats
208
- * @throws {Error} if Principal exceeds encumberedBalance
208
+ * @throws {Error} if allocations do not match amounts or Principal exceeds encumberedBalance
209
209
  */
210
- export const repayCalc = (shareWorth, split, encumberedBalance, poolStats) => {
211
- isGTE(encumberedBalance, split.Principal) ||
212
- Fail`Cannot repay. Principal ${q(split.Principal)} exceeds encumberedBalance ${q(encumberedBalance)}.`;
210
+ export const repayCalc = (
211
+ shareWorth,
212
+ fromSeatAllocation,
213
+ amounts,
214
+ encumberedBalance,
215
+ poolStats,
216
+ ) => {
217
+ (isEqual(fromSeatAllocation.Principal, amounts.Principal) &&
218
+ isEqual(fromSeatAllocation.PoolFee, amounts.PoolFee) &&
219
+ isEqual(fromSeatAllocation.ContractFee, amounts.ContractFee)) ||
220
+ Fail`Cannot repay. From seat allocation ${q(fromSeatAllocation)} does not equal amounts ${q(amounts)}.`;
221
+
222
+ isGTE(encumberedBalance, amounts.Principal) ||
223
+ Fail`Cannot repay. Principal ${q(amounts.Principal)} exceeds encumberedBalance ${q(encumberedBalance)}.`;
213
224
 
214
225
  return harden({
215
- shareWorth: withFees(shareWorth, split.PoolFee),
216
- encumberedBalance: subtract(encumberedBalance, split.Principal),
226
+ shareWorth: withFees(shareWorth, amounts.PoolFee),
227
+ encumberedBalance: subtract(encumberedBalance, amounts.Principal),
217
228
  poolStats: {
218
229
  ...poolStats,
219
- totalRepays: add(poolStats.totalRepays, split.Principal),
220
- totalPoolFees: add(poolStats.totalPoolFees, split.PoolFee),
221
- totalContractFees: add(
222
- add(poolStats.totalContractFees, split.ContractFee),
223
- split.RelayFee,
224
- ),
230
+ totalRepays: add(poolStats.totalRepays, amounts.Principal),
231
+ totalPoolFees: add(poolStats.totalPoolFees, amounts.PoolFee),
232
+ totalContractFees: add(poolStats.totalContractFees, amounts.ContractFee),
225
233
  },
226
234
  });
227
235
  };