From 1f47164a792b64f5b4a27992156646d87670782c Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 5 Dec 2024 16:52:59 -0800 Subject: [PATCH 01/11] fix: vstorage fastUsdc path --- packages/fast-usdc/src/util/agoric.js | 2 +- packages/fast-usdc/test/cli/transfer.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/fast-usdc/src/util/agoric.js b/packages/fast-usdc/src/util/agoric.js index 1439f60ffda..4f7e4d65254 100644 --- a/packages/fast-usdc/src/util/agoric.js +++ b/packages/fast-usdc/src/util/agoric.js @@ -5,7 +5,7 @@ export const queryFastUSDCLocalChainAccount = async ( out = console, ) => { const agoricAddr = await vstorage.readLatest( - 'published.fastUSDC.settlementAccount', + 'published.fastUsdc.settlementAccount', ); out.log(`Got Fast USDC Local Chain Account ${agoricAddr}`); return agoricAddr; diff --git a/packages/fast-usdc/test/cli/transfer.test.ts b/packages/fast-usdc/test/cli/transfer.test.ts index d3c319f1f6b..729f40f63cc 100644 --- a/packages/fast-usdc/test/cli/transfer.test.ts +++ b/packages/fast-usdc/test/cli/transfer.test.ts @@ -64,7 +64,7 @@ test('Transfer registers the noble forwarding account if it does not exist', asy const out = mockOut(); const file = mockFile(path, JSON.stringify(config)); const agoricSettlementAccount = 'agoric123456'; - const settlementAccountVstoragePath = 'published.fastUSDC.settlementAccount'; + const settlementAccountVstoragePath = 'published.fastUsdc.settlementAccount'; const vstorageMock = makeVstorageMock({ [settlementAccountVstoragePath]: agoricSettlementAccount, }); @@ -115,7 +115,7 @@ test('Transfer signs and broadcasts the depositForBurn message on Ethereum', asy const out = mockOut(); const file = mockFile(path, JSON.stringify(config)); const agoricSettlementAccount = 'agoric123456'; - const settlementAccountVstoragePath = 'published.fastUSDC.settlementAccount'; + const settlementAccountVstoragePath = 'published.fastUsdc.settlementAccount'; const vstorageMock = makeVstorageMock({ [settlementAccountVstoragePath]: agoricSettlementAccount, }); From fe99b81a0fc4a868485475ca0cc2b623c44a2d19 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 5 Dec 2024 16:47:37 -0800 Subject: [PATCH 02/11] refactor: makeStatusNode to statusNode eref --- packages/fast-usdc/src/exos/status-manager.js | 7 ++--- packages/fast-usdc/src/fast-usdc.contract.js | 4 +-- packages/fast-usdc/test/exos/advancer.test.ts | 2 +- packages/fast-usdc/test/exos/settler.test.ts | 2 +- .../test/exos/status-manager.test.ts | 31 +++++++++---------- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index ad4c33c44d1..7cb2ad6df51 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -68,12 +68,12 @@ const seenTxKeyOf = evidence => { * XXX consider separate facets for `Advancing` and `Settling` capabilities. * * @param {Zone} zone - * @param {() => Promise} makeStatusNode + * @param {ERef} transactionsNode * @param {StatusManagerPowers} caps */ export const prepareStatusManager = ( zone, - makeStatusNode, + transactionsNode, { log = makeTracer('Advancer', true), } = /** @type {StatusManagerPowers} */ ({}), @@ -94,8 +94,7 @@ export const prepareStatusManager = ( * @param {TxStatus} status */ const recordStatus = (hash, status) => { - const statusNodeP = makeStatusNode(); - const txnNodeP = E(statusNodeP).makeChildNode(hash); + const txnNodeP = E(transactionsNode).makeChildNode(hash); // Don't await, just writing to vstorage. void E(txnNodeP).setValue(status); }; diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 91dbdebdc2c..734bd423a3c 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -95,8 +95,8 @@ export const contract = async (zcf, privateArgs, zone, tools) => { marshaller, ); - const makeStatusNode = () => E(storageNode).makeChildNode(STATUS_NODE); - const statusManager = prepareStatusManager(zone, makeStatusNode); + const statusNode = E(storageNode).makeChildNode(STATUS_NODE); + const statusManager = prepareStatusManager(zone, statusNode); const { USDC } = terms.brands; const { withdrawToSeat } = tools.zoeTools; diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index 818554dacca..0c8d984c839 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -44,7 +44,7 @@ const createTestExtensions = (t, common: CommonSetup) => { const statusManager = prepareStatusManager( rootZone.subZone('status-manager'), - async () => storageNode.makeChildNode('status'), + storageNode.makeChildNode('status'), ); const mockAccounts = prepareMockOrchAccounts(rootZone.subZone('accounts'), { diff --git a/packages/fast-usdc/test/exos/settler.test.ts b/packages/fast-usdc/test/exos/settler.test.ts index f900c7cd191..2c8075e9e2f 100644 --- a/packages/fast-usdc/test/exos/settler.test.ts +++ b/packages/fast-usdc/test/exos/settler.test.ts @@ -47,7 +47,7 @@ const makeTestContext = async t => { const { log, inspectLogs } = makeTestLogger(t.log); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - async () => common.commonPrivateArgs.storageNode.makeChildNode('status'), + common.commonPrivateArgs.storageNode.makeChildNode('status'), { log }, ); const { zcf, callLog } = mockZcf(zone.subZone('Mock ZCF')); diff --git a/packages/fast-usdc/test/exos/status-manager.test.ts b/packages/fast-usdc/test/exos/status-manager.test.ts index d37015f0d57..7932d81e630 100644 --- a/packages/fast-usdc/test/exos/status-manager.test.ts +++ b/packages/fast-usdc/test/exos/status-manager.test.ts @@ -10,7 +10,7 @@ import type { CctpTxEvidence } from '../../src/types.js'; type Common = Awaited>; type TestContext = { - makeStatusNode: () => Promise; + statusNode: ERef; storage: Common['bootstrap']['storage']; }; @@ -19,8 +19,7 @@ const test = anyTest as TestFn; test.beforeEach(async t => { const common = await commonSetup(t); t.context = { - makeStatusNode: async () => - common.commonPrivateArgs.storageNode.makeChildNode('status'), + statusNode: common.commonPrivateArgs.storageNode.makeChildNode('status'), storage: common.bootstrap.storage, }; }); @@ -29,7 +28,7 @@ test('advance creates new entry with ADVANCED status', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -47,7 +46,7 @@ test('ADVANCED transactions are published to vstorage', async t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -65,7 +64,7 @@ test('observe creates new entry with OBSERVED status', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); statusManager.observe(evidence); @@ -82,7 +81,7 @@ test('OBSERVED transactions are published to vstorage', async t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -100,7 +99,7 @@ test('cannot process same tx twice', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); statusManager.advance(evidence); @@ -125,7 +124,7 @@ test('isSeen checks if a tx has been processed', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -143,7 +142,7 @@ test('dequeueStatus removes entries from PendingTxs', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); const e2 = MockCctpTxEvidences.AGORIC_PLUS_DYDX(); @@ -195,7 +194,7 @@ test('cannot advanceOutcome without ADVANCING entry', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); const advanceOutcomeFn = () => @@ -224,7 +223,7 @@ test('advanceOutcome transitions to ADVANCED and ADVANCE_FAILED', async t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); const e2 = MockCctpTxEvidences.AGORIC_PLUS_DYDX(); @@ -260,7 +259,7 @@ test('dequeueStatus returns undefined when nothing is settleable', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -274,7 +273,7 @@ test('dequeueStatus returns first (earliest) matched entry', async t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -371,7 +370,7 @@ test('lookupPending returns empty array when presented a key it has not seen', t const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); t.deepEqual(statusManager.lookupPending('noble123', 1n), []); }); @@ -380,7 +379,7 @@ test('StatusManagerKey logic handles addresses with hyphens', t => { const zone = provideDurableZone('status-test'); const statusManager = prepareStatusManager( zone.subZone('status-manager'), - t.context.makeStatusNode, + t.context.statusNode, ); const evidence: CctpTxEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); evidence.tx.forwardingAddress = 'noble1-foo'; From 057977f53612ef925e35ea13c0b61b03f084f8bd Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 14:34:19 -0800 Subject: [PATCH 03/11] docs: composite keys --- packages/fast-usdc/src/exos/status-manager.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index 7cb2ad6df51..653808d1389 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -19,8 +19,7 @@ import { PendingTxStatus, TxStatus } from '../constants.js'; /** * Create the key for the pendingTxs MapStore. * - * The key is a composite of `txHash` and `chainId` and not meant to be - * parsable. + * The key is a composite but not meant to be parsable. * * @param {NobleAddress} addr * @param {bigint} amount @@ -43,8 +42,7 @@ const pendingTxKeyOf = evidence => { /** * Get the key for the seenTxs SetStore. * - * The key is a composite of `NobleAddress` and transaction `amount` and not - * meant to be parsable. + * The key is a composite but not meant to be parsable. * * @param {CctpTxEvidence} evidence * @returns {SeenTxKey} From 6ddc959c3ea83ae5f05495a8f80a88ff72ecde44 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 15:01:27 -0800 Subject: [PATCH 04/11] test: replace lookupPending with vstorage test --- packages/fast-usdc/test/exos/advancer.test.ts | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index 0c8d984c839..908880d3fb4 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -162,6 +162,7 @@ test('updates status to ADVANCING in happy path', async t => { mocks: { mockPoolAccount, resolveLocalTransferV }, }, brands: { usdc }, + bootstrap: { storage }, } = t.context; const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -174,14 +175,9 @@ test('updates status to ADVANCING in happy path', async t => { // wait for handleTransactionEvent to do work await eventLoopIteration(); - const entries = statusManager.lookupPending( - mockEvidence.tx.forwardingAddress, - mockEvidence.tx.amount, - ); - t.deepEqual( - entries, - [{ ...mockEvidence, status: PendingTxStatus.Advancing }], + storage.data.get(`mockChainStorageRoot.status.${mockEvidence.txHash}`), + PendingTxStatus.Advancing, 'ADVANCED status in happy path', ); @@ -210,6 +206,7 @@ test('updates status to ADVANCING in happy path', async t => { test('updates status to OBSERVED on insufficient pool funds', async t => { const { + bootstrap: { storage }, extensions: { services: { makeAdvancer, statusManager }, helpers: { inspectLogs }, @@ -229,14 +226,9 @@ test('updates status to OBSERVED on insufficient pool funds', async t => { void advancer.handleTransactionEvent(mockEvidence); await eventLoopIteration(); - const entries = statusManager.lookupPending( - mockEvidence.tx.forwardingAddress, - mockEvidence.tx.amount, - ); - t.deepEqual( - entries, - [{ ...mockEvidence, status: PendingTxStatus.Observed }], + storage.data.get(`mockChainStorageRoot.status.${mockEvidence.txHash}`), + PendingTxStatus.Observed, 'OBSERVED status on insufficient pool funds', ); @@ -248,6 +240,7 @@ test('updates status to OBSERVED on insufficient pool funds', async t => { test('updates status to OBSERVED if makeChainAddress fails', async t => { const { + bootstrap: { storage }, extensions: { services: { advancer, statusManager }, helpers: { inspectLogs }, @@ -257,14 +250,9 @@ test('updates status to OBSERVED if makeChainAddress fails', async t => { const mockEvidence = MockCctpTxEvidences.AGORIC_UNKNOWN_EUD(); await advancer.handleTransactionEvent(mockEvidence); - const entries = statusManager.lookupPending( - mockEvidence.tx.forwardingAddress, - mockEvidence.tx.amount, - ); - t.deepEqual( - entries, - [{ ...mockEvidence, status: PendingTxStatus.Observed }], + storage.data.get(`mockChainStorageRoot.status.${mockEvidence.txHash}`), + PendingTxStatus.Observed, 'OBSERVED status on makeChainAddress failure', ); @@ -276,6 +264,7 @@ test('updates status to OBSERVED if makeChainAddress fails', async t => { test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t => { const { + bootstrap: { storage }, extensions: { services: { advancer, feeTools, statusManager }, helpers: { inspectLogs, inspectNotifyCalls }, @@ -291,14 +280,9 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t resolveLocalTransferV(); await eventLoopIteration(); - const entries = statusManager.lookupPending( - mockEvidence.tx.forwardingAddress, - mockEvidence.tx.amount, - ); - t.deepEqual( - entries, - [{ ...mockEvidence, status: PendingTxStatus.Advancing }], + storage.data.get(`mockChainStorageRoot.status.${mockEvidence.txHash}`), + PendingTxStatus.Advancing, 'tx is Advancing', ); @@ -333,6 +317,7 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t test('updates status to OBSERVED if pre-condition checks fail', async t => { const { + bootstrap: { storage }, extensions: { services: { advancer, statusManager }, helpers: { inspectLogs }, @@ -343,14 +328,9 @@ test('updates status to OBSERVED if pre-condition checks fail', async t => { await advancer.handleTransactionEvent(mockEvidence); - const entries = statusManager.lookupPending( - mockEvidence.tx.forwardingAddress, - mockEvidence.tx.amount, - ); - t.deepEqual( - entries, - [{ ...mockEvidence, status: PendingTxStatus.Observed }], + storage.data.get(`mockChainStorageRoot.status.${mockEvidence.txHash}`), + PendingTxStatus.Observed, 'tx is recorded as OBSERVED', ); From b04cb9825efb09c3c21d4cf246c0dd0f75512970 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 15:14:55 -0800 Subject: [PATCH 05/11] refactor: recordStatus -> publishStatus "record" suggests updating local data. "publish" is our term for going out over vstorage. --- packages/fast-usdc/src/exos/status-manager.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index 653808d1389..b5afe7e71db 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -91,7 +91,7 @@ export const prepareStatusManager = ( * @param {CctpTxEvidence['txHash']} hash * @param {TxStatus} status */ - const recordStatus = (hash, status) => { + const publishStatus = (hash, status) => { const txnNodeP = E(transactionsNode).makeChildNode(hash); // Don't await, just writing to vstorage. void E(txnNodeP).setValue(status); @@ -118,7 +118,7 @@ export const prepareStatusManager = ( pendingTxKeyOf(evidence), harden({ ...evidence, status }), ); - recordStatus(evidence.txHash, status); + publishStatus(evidence.txHash, status); }; return zone.exo( @@ -185,7 +185,7 @@ export const prepareStatusManager = ( : PendingTxStatus.AdvanceFailed; const txpost = { ...tx, status }; pendingTxs.set(key, harden([...prefix, txpost, ...suffix])); - recordStatus(tx.txHash, status); + publishStatus(tx.txHash, status); }, /** @@ -244,7 +244,7 @@ export const prepareStatusManager = ( * @param {EvmHash} txHash */ disbursed(txHash) { - recordStatus(txHash, TxStatus.Disbursed); + publishStatus(txHash, TxStatus.Disbursed); }, /** @@ -256,7 +256,7 @@ export const prepareStatusManager = ( */ forwarded(txHash, address, amount) { if (txHash) { - recordStatus(txHash, TxStatus.Forwarded); + publishStatus(txHash, TxStatus.Forwarded); } else { // TODO store (early) `Minted` transactions to check against incoming evidence log( From a4dc42f4b76d1f046daf32c850a1f60946bb1a65 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 16:25:58 -0800 Subject: [PATCH 06/11] docs: advance() skips OBSERVED --- packages/fast-usdc/src/exos/status-manager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index b5afe7e71db..df0a8979890 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -153,6 +153,9 @@ export const prepareStatusManager = ( { /** * Add a new transaction with ADVANCING status + * + * NB: this acts like observe() but skips recording the OBSERVED state + * * @param {CctpTxEvidence} evidence */ advance(evidence) { From b42455078bb83131f337f66fd999f29f30bef7c0 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 16:46:57 -0800 Subject: [PATCH 07/11] refactor: initPendingTx and setPendingTxStatus --- packages/fast-usdc/src/exos/status-manager.js | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index df0a8979890..ffe742ecd83 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -106,7 +106,7 @@ export const prepareStatusManager = ( * @param {CctpTxEvidence} evidence * @param {PendingTxStatus} status */ - const recordPendingTx = (evidence, status) => { + const initPendingTx = (evidence, status) => { const seenKey = seenTxKeyOf(evidence); if (seenTxs.has(seenKey)) { throw makeError(`Transaction already seen: ${q(seenKey)}`); @@ -121,6 +121,28 @@ export const prepareStatusManager = ( publishStatus(evidence.txHash, status); }; + /** + * Update the pending transaction status. + * + * @param {{sender: NobleAddress, amount: bigint}} keyParts + * @param {PendingTxStatus} status + */ + function setPendingTxStatus({ sender, amount }, status) { + const key = makePendingTxKey(sender, amount); + pendingTxs.has(key) || Fail`no advancing tx with ${{ sender, amount }}`; + const pending = pendingTxs.get(key); + const ix = pending.findIndex(tx => tx.status === PendingTxStatus.Advancing); + ix >= 0 || Fail`no advancing tx with ${{ sender, amount }}`; + const [prefix, tx, suffix] = [ + pending.slice(0, ix), + pending[ix], + pending.slice(ix + 1), + ]; + const txpost = { ...tx, status }; + pendingTxs.set(key, harden([...prefix, txpost, ...suffix])); + publishStatus(tx.txHash, status); + } + return zone.exo( 'Fast USDC Status Manager', M.interface('StatusManagerI', { @@ -159,7 +181,7 @@ export const prepareStatusManager = ( * @param {CctpTxEvidence} evidence */ advance(evidence) { - recordPendingTx(evidence, PendingTxStatus.Advancing); + initPendingTx(evidence, PendingTxStatus.Advancing); }, /** @@ -171,24 +193,10 @@ export const prepareStatusManager = ( * @throws {Error} if nothing to advance */ advanceOutcome(sender, amount, success) { - const key = makePendingTxKey(sender, amount); - pendingTxs.has(key) || Fail`no advancing tx with ${{ sender, amount }}`; - const pending = pendingTxs.get(key); - const ix = pending.findIndex( - tx => tx.status === PendingTxStatus.Advancing, + setPendingTxStatus( + { sender, amount }, + success ? PendingTxStatus.Advanced : PendingTxStatus.AdvanceFailed, ); - ix >= 0 || Fail`no advancing tx with ${{ sender, amount }}`; - const [prefix, tx, suffix] = [ - pending.slice(0, ix), - pending[ix], - pending.slice(ix + 1), - ]; - const status = success - ? PendingTxStatus.Advanced - : PendingTxStatus.AdvanceFailed; - const txpost = { ...tx, status }; - pendingTxs.set(key, harden([...prefix, txpost, ...suffix])); - publishStatus(tx.txHash, status); }, /** @@ -196,7 +204,7 @@ export const prepareStatusManager = ( * @param {CctpTxEvidence} evidence */ observe(evidence) { - recordPendingTx(evidence, PendingTxStatus.Observed); + initPendingTx(evidence, PendingTxStatus.Observed); }, /** From c7a31676f5d5eab68c51f6af890dd590a9c4a87d Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 17:07:07 -0800 Subject: [PATCH 08/11] refactor(types): don't export internal keys --- packages/fast-usdc/src/exos/status-manager.js | 7 ++++++- packages/fast-usdc/src/types.ts | 6 ------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index ffe742ecd83..c49e955b25d 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -13,7 +13,12 @@ import { PendingTxStatus, TxStatus } from '../constants.js'; /** * @import {MapStore, SetStore} from '@agoric/store'; * @import {Zone} from '@agoric/zone'; - * @import {CctpTxEvidence, NobleAddress, SeenTxKey, PendingTxKey, PendingTx, EvmHash, LogFn} from '../types.js'; + * @import {CctpTxEvidence, NobleAddress, PendingTx, EvmHash, LogFn} from '../types.js'; + */ + +/** + * @typedef {`pendingTx:${string}`} PendingTxKey + * @typedef {`seenTx:${string}`} SeenTxKey */ /** diff --git a/packages/fast-usdc/src/types.ts b/packages/fast-usdc/src/types.ts index cedb38b9b98..ee9a90efffd 100644 --- a/packages/fast-usdc/src/types.ts +++ b/packages/fast-usdc/src/types.ts @@ -38,12 +38,6 @@ export interface PendingTx extends CctpTxEvidence { status: PendingTxStatus; } -/** internal key for `StatusManager` exo */ -export type PendingTxKey = `pendingTx:${string}`; - -/** internal key for `StatusManager` exo */ -export type SeenTxKey = `seenTx:${string}`; - export type FeeConfig = { flat: Amount<'nat'>; variableRate: Ratio; From 5751bf5fbe55e92bbafbfe83fd91a080df7213e3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Dec 2024 17:11:57 -0800 Subject: [PATCH 09/11] refactor: simplify db keys --- packages/fast-usdc/src/exos/status-manager.js | 13 +++++++++---- packages/fast-usdc/test/exos/status-manager.test.ts | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index c49e955b25d..ca17487e01b 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -17,8 +17,11 @@ import { PendingTxStatus, TxStatus } from '../constants.js'; */ /** - * @typedef {`pendingTx:${string}`} PendingTxKey - * @typedef {`seenTx:${string}`} SeenTxKey + * @typedef {`pendingTx:${bigint}:${NobleAddress}`} PendingTxKey + * The string template is for developer visibility but not meant to ever be parsed. + * + * @typedef {`seenTx:${string}:${EvmHash}`} SeenTxKey + * The string template is for developer visibility but not meant to ever be parsed. */ /** @@ -31,7 +34,8 @@ import { PendingTxStatus, TxStatus } from '../constants.js'; * @returns {PendingTxKey} */ const makePendingTxKey = (addr, amount) => - `pendingTx:${JSON.stringify([addr, String(amount)])}`; + // amount can't contain colon + `pendingTx:${amount}:${addr}`; /** * Get the key for the pendingTxs MapStore. @@ -54,7 +58,8 @@ const pendingTxKeyOf = evidence => { */ const seenTxKeyOf = evidence => { const { txHash, chainId } = evidence; - return `seenTx:${JSON.stringify([txHash, chainId])}`; + // chainId can't contain colon + return `seenTx:${chainId}:${txHash}`; }; /** diff --git a/packages/fast-usdc/test/exos/status-manager.test.ts b/packages/fast-usdc/test/exos/status-manager.test.ts index 7932d81e630..71757ea731a 100644 --- a/packages/fast-usdc/test/exos/status-manager.test.ts +++ b/packages/fast-usdc/test/exos/status-manager.test.ts @@ -106,12 +106,12 @@ test('cannot process same tx twice', t => { t.throws(() => statusManager.advance(evidence), { message: - 'Transaction already seen: "seenTx:[\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\",1]"', + 'Transaction already seen: "seenTx:1:0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702"', }); t.throws(() => statusManager.observe(evidence), { message: - 'Transaction already seen: "seenTx:[\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\",1]"', + 'Transaction already seen: "seenTx:1:0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702"', }); // new txHash should not throw From f77b0ec1bd2328aff175c92237bbfd528720ff66 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 9 Dec 2024 17:17:07 -0800 Subject: [PATCH 10/11] lint: cleanup unused eslint-disable --- packages/boot/test/bootstrapTests/vaults-upgrade.test.ts | 1 - packages/boot/test/bootstrapTests/vtransfer.test.ts | 1 - packages/boot/test/upgrading/upgrade-vats.test.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/packages/boot/test/bootstrapTests/vaults-upgrade.test.ts b/packages/boot/test/bootstrapTests/vaults-upgrade.test.ts index 8d1787a1ab1..940339eec69 100644 --- a/packages/boot/test/bootstrapTests/vaults-upgrade.test.ts +++ b/packages/boot/test/bootstrapTests/vaults-upgrade.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @jessie.js/safe-await-separator */ /** * @file Bootstrap test integration vaults with smart-wallet. The tests in this * file are NOT independent; a single `test.before()` handler creates shared diff --git a/packages/boot/test/bootstrapTests/vtransfer.test.ts b/packages/boot/test/bootstrapTests/vtransfer.test.ts index fd0bec558ac..7f865e596fe 100644 --- a/packages/boot/test/bootstrapTests/vtransfer.test.ts +++ b/packages/boot/test/bootstrapTests/vtransfer.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @jessie.js/safe-await-separator -- confused by casting 'as' */ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import type { TestFn } from 'ava'; diff --git a/packages/boot/test/upgrading/upgrade-vats.test.ts b/packages/boot/test/upgrading/upgrade-vats.test.ts index d5a3d22c14f..9711c7dd121 100644 --- a/packages/boot/test/upgrading/upgrade-vats.test.ts +++ b/packages/boot/test/upgrading/upgrade-vats.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @jessie.js/safe-await-separator -- test */ import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; import { BridgeId, deepCopyJsonable } from '@agoric/internal'; From 4c0c37234fc2ddd1e83c0d0f3693c33a05163f5b Mon Sep 17 00:00:00 2001 From: samsiegart Date: Thu, 5 Dec 2024 20:13:24 -0800 Subject: [PATCH 11/11] feat(fast-usdc): cli for lp deposit and withdraw --- packages/fast-usdc/src/cli/bridge-action.js | 16 +- packages/fast-usdc/src/cli/cli.js | 54 +----- packages/fast-usdc/src/cli/lp-commands.js | 171 ++++++++++++++++++ .../fast-usdc/src/cli/operator-commands.js | 4 + packages/fast-usdc/test/cli/cli.test.ts | 24 +-- .../fast-usdc/test/cli/lp-commands.test.ts | 123 +++++++++++++ .../test/cli/snapshots/cli.test.ts.md | 94 ++-------- .../test/cli/snapshots/cli.test.ts.snap | Bin 1731 -> 1559 bytes 8 files changed, 330 insertions(+), 156 deletions(-) create mode 100644 packages/fast-usdc/src/cli/lp-commands.js create mode 100644 packages/fast-usdc/test/cli/lp-commands.test.ts diff --git a/packages/fast-usdc/src/cli/bridge-action.js b/packages/fast-usdc/src/cli/bridge-action.js index 582028f23bf..45a53351b8c 100644 --- a/packages/fast-usdc/src/cli/bridge-action.js +++ b/packages/fast-usdc/src/cli/bridge-action.js @@ -4,13 +4,16 @@ import { boardSlottingMarshaller } from '@agoric/client-utils'; * @import {BridgeAction} from '@agoric/smart-wallet/src/smartWallet.js'; */ -const marshaller = boardSlottingMarshaller(); +const defaultMarshaller = boardSlottingMarshaller(); + +/** @typedef {ReturnType} BoardSlottingMarshaller */ /** * @param {BridgeAction} bridgeAction * @param {Pick} stdout + * @param {BoardSlottingMarshaller} marshaller */ -const outputAction = (bridgeAction, stdout) => { +const outputAction = (bridgeAction, stdout, marshaller) => { const capData = marshaller.toCapData(harden(bridgeAction)); stdout.write(JSON.stringify(capData)); stdout.write('\n'); @@ -25,8 +28,13 @@ export const sendHint = * stdout: Pick, * stderr: Pick, * }} io + * @param {BoardSlottingMarshaller | undefined} marshaller */ -export const outputActionAndHint = (bridgeAction, { stdout, stderr }) => { - outputAction(bridgeAction, stdout); +export const outputActionAndHint = ( + bridgeAction, + { stdout, stderr }, + marshaller = defaultMarshaller, +) => { + outputAction(bridgeAction, stdout, marshaller); stderr.write(sendHint); }; diff --git a/packages/fast-usdc/src/cli/cli.js b/packages/fast-usdc/src/cli/cli.js index 9488bf574c2..8676ef955e2 100644 --- a/packages/fast-usdc/src/cli/cli.js +++ b/packages/fast-usdc/src/cli/cli.js @@ -1,11 +1,6 @@ /* eslint-env node */ /* global globalThis */ -import { assertParsableNumber } from '@agoric/zoe/src/contractSupport/ratio.js'; -import { - Command, - InvalidArgumentError, - InvalidOptionArgumentError, -} from 'commander'; +import { Command } from 'commander'; import { existsSync, mkdirSync, readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; @@ -19,6 +14,7 @@ import { addOperatorCommands } from './operator-commands.js'; import * as configLib from './config.js'; import transferLib from './transfer.js'; import { makeFile } from '../util/file.js'; +import { addLPCommands } from './lp-commands.js'; const packageJson = JSON.parse( readFileSync( @@ -83,51 +79,7 @@ export const initProgram = ( env, now, }); - - /** @param {string} value */ - const parseDecimal = value => { - try { - assertParsableNumber(value); - } catch { - throw new InvalidArgumentError('Not a decimal number.'); - } - return value; - }; - - /** - * @param {string} str - * @returns {'auto' | number} - */ - const parseFee = str => { - if (str === 'auto') return 'auto'; - const num = parseFloat(str); - if (Number.isNaN(num)) { - throw new InvalidOptionArgumentError('Fee must be a number.'); - } - return num; - }; - - program - .command('deposit') - .description('Offer assets to the liquidity pool') - .argument('', 'USDC to give', parseDecimal) - .option('--id [offer-id]', 'Offer ID') - .option('--fee [fee]', 'Cosmos fee', parseFee) - .action(() => { - console.error('TODO actually send deposit'); - // TODO: Implement deposit logic - }); - - program - .command('withdraw') - .description('Withdraw assets from the liquidity pool') - .argument('', 'USDC to withdraw', parseDecimal) - .option('--id [offer-id]', 'Offer ID') - .option('--fee [fee]', 'Cosmos fee', parseFee) - .action(() => { - console.error('TODO actually send withdrawal'); - // TODO: Implement withdraw logic - }); + addLPCommands(program, { fetch, stdout, stderr, env, now }); program .command('transfer') diff --git a/packages/fast-usdc/src/cli/lp-commands.js b/packages/fast-usdc/src/cli/lp-commands.js new file mode 100644 index 00000000000..4b829cfbfba --- /dev/null +++ b/packages/fast-usdc/src/cli/lp-commands.js @@ -0,0 +1,171 @@ +/** + * @import {Command} from 'commander'; + * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js'; + * @import {ExecuteOfferAction} from '@agoric/smart-wallet/src/smartWallet.js'; + * @import {USDCProposalShapes} from '../pool-share-math.js'; + */ + +import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; +import { InvalidArgumentError } from 'commander'; +import { + assertParsableNumber, + ceilDivideBy, + multiplyBy, + parseRatio, +} from '@agoric/zoe/src/contractSupport/ratio.js'; +import { AmountMath } from '@agoric/ertp'; +import { outputActionAndHint } from './bridge-action.js'; + +/** @param {string} arg */ +const parseDecimal = arg => { + try { + assertParsableNumber(arg); + const n = Number(arg); + return n; + } catch { + throw new InvalidArgumentError('Not a number'); + } +}; + +/** + * @param {string} amountString + * @param {Brand} usdc + */ +const parseUSDCAmount = (amountString, usdc) => { + const USDC_DECIMALS = 6; + const unit = AmountMath.make(usdc, 10n ** BigInt(USDC_DECIMALS)); + return multiplyBy(unit, parseRatio(amountString, usdc)); +}; + +/** + * @param {Command} program + * @param {{ + * fetch?: Window['fetch']; + * vstorageKit?: Awaited>; + * stdout: typeof process.stdout; + * stderr: typeof process.stderr; + * env: typeof process.env; + * now: typeof Date.now; + * }} io + */ +export const addLPCommands = ( + program, + { fetch, vstorageKit, stderr, stdout, env, now }, +) => { + const loadVsk = async () => { + if (vstorageKit) { + return vstorageKit; + } + assert(fetch); + const networkConfig = await fetchEnvNetworkConfig({ env, fetch }); + return makeVstorageKit({ fetch }, networkConfig); + }; + /** @type {undefined | ReturnType} */ + let vskP; + + program + .command('deposit') + .description('Deposit USDC into pool') + .addHelpText( + 'after', + '\nPipe the STDOUT to a file such as deposit.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer deposit.json --from gov1 --keyring-backend="test"', + ) + .requiredOption('--amount ', 'USDC amount', parseDecimal) + .option('--offerId ', 'Offer id', String, `lpDeposit-${now()}`) + .action(async opts => { + vskP ||= loadVsk(); + const vsk = await vskP; + /** @type {Brand<'nat'>} */ + // @ts-expect-error it doesnt recognize usdc as a Brand type + const usdc = vsk.agoricNames.brand.USDC; + assert(usdc, 'USDC brand not in agoricNames'); + + const usdcAmount = parseUSDCAmount(opts.amount, usdc); + + /** @type {USDCProposalShapes['deposit']} */ + const proposal = { + give: { + USDC: usdcAmount, + }, + }; + + /** @type {OfferSpec} */ + const offer = { + id: opts.offerId, + invitationSpec: { + source: 'agoricContract', + instancePath: ['fastUsdc'], + callPipe: [['makeDepositInvitation', []]], + }, + proposal, + }; + + /** @type {ExecuteOfferAction} */ + const bridgeAction = { + method: 'executeOffer', + offer, + }; + + outputActionAndHint(bridgeAction, { stderr, stdout }, vsk.marshaller); + }); + + program + .command('withdraw') + .description("Withdraw USDC from the LP's pool share") + .addHelpText( + 'after', + '\nPipe the STDOUT to a file such as withdraw.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer withdraw.json --from gov1 --keyring-backend="test"', + ) + .requiredOption('--amount ', 'USDC amount', parseDecimal) + .option('--offerId ', 'Offer id', String, `lpWithdraw-${now()}`) + .action(async opts => { + vskP ||= loadVsk(); + const vsk = await vskP; + + /** @type {Brand<'nat'>} */ + // @ts-expect-error it doesnt recognize FastLP as a Brand type + const poolShare = vsk.agoricNames.brand.FastLP; + assert(poolShare, 'FastLP brand not in agoricNames'); + + /** @type {Brand<'nat'>} */ + // @ts-expect-error it doesnt recognize usdc as a Brand type + const usdc = vsk.agoricNames.brand.USDC; + assert(usdc, 'USDC brand not in agoricNames'); + + const usdcAmount = parseUSDCAmount(opts.amount, usdc); + + /** @type {import('../types.js').PoolMetrics} */ + // @ts-expect-error it treats this as "unknown" + const metrics = await vsk.readPublished('fastUsdc.poolMetrics'); + const fastLPAmount = ceilDivideBy(usdcAmount, metrics.shareWorth); + + /** @type {USDCProposalShapes['withdraw']} */ + const proposal = { + give: { + PoolShare: fastLPAmount, + }, + want: { + USDC: usdcAmount, + }, + }; + + /** @type {OfferSpec} */ + const offer = { + id: opts.offerId, + invitationSpec: { + source: 'agoricContract', + instancePath: ['fastUsdc'], + callPipe: [['makeWithdrawInvitation', []]], + }, + proposal, + }; + + outputActionAndHint( + { method: 'executeOffer', offer }, + { stderr, stdout }, + vsk.marshaller, + ); + }); + + return program; +}; diff --git a/packages/fast-usdc/src/cli/operator-commands.js b/packages/fast-usdc/src/cli/operator-commands.js index 5bb19882232..196a48af8a4 100644 --- a/packages/fast-usdc/src/cli/operator-commands.js +++ b/packages/fast-usdc/src/cli/operator-commands.js @@ -80,6 +80,10 @@ export const addOperatorCommands = ( operator .command('attest') .description('Attest to an observed Fast USDC transfer') + .addHelpText( + 'after', + '\nPipe the STDOUT to a file such as attest.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer attest.json --from gov1 --keyring-backend="test"', + ) .requiredOption('--previousOfferId ', 'Offer id', String) .requiredOption('--forwardingChannel ', 'Channel id', String) .requiredOption('--recipientAddress ', 'bech32 address', String) diff --git a/packages/fast-usdc/test/cli/cli.test.ts b/packages/fast-usdc/test/cli/cli.test.ts index cfde8387677..026c8d01dac 100644 --- a/packages/fast-usdc/test/cli/cli.test.ts +++ b/packages/fast-usdc/test/cli/cli.test.ts @@ -81,28 +81,13 @@ test('shows help for config show command', async t => { t.snapshot(output); }); -test('shows help for deposit command', async t => { - const output = await runCli(['deposit', '-h']); - t.snapshot(output); -}); - -test('shows help for withdraw command', async t => { - const output = await runCli(['withdraw', '-h']); - t.snapshot(output); -}); - test('shows error when deposit command is run without options', async t => { const output = await runCli(['deposit']); t.snapshot(output); }); test('shows error when deposit command is run with invalid amount', async t => { - const output = await runCli(['deposit', 'not-a-number']); - t.snapshot(output); -}); - -test('shows error when deposit command is run with invalid fee', async t => { - const output = await runCli(['deposit', '50', '--fee', 'not-a-number']); + const output = await runCli(['deposit', '--amount', 'not-a-number']); t.snapshot(output); }); @@ -112,12 +97,7 @@ test('shows error when withdraw command is run without options', async t => { }); test('shows error when withdraw command is run with invalid amount', async t => { - const output = await runCli(['withdraw', 'not-a-number']); - t.snapshot(output); -}); - -test('shows error when withdraw command is run with invalid fee', async t => { - const output = await runCli(['withdraw', '50', '--fee', 'not-a-number']); + const output = await runCli(['withdraw', '--amount', 'not-a-number']); t.snapshot(output); }); diff --git a/packages/fast-usdc/test/cli/lp-commands.test.ts b/packages/fast-usdc/test/cli/lp-commands.test.ts new file mode 100644 index 00000000000..367dc9a78f6 --- /dev/null +++ b/packages/fast-usdc/test/cli/lp-commands.test.ts @@ -0,0 +1,123 @@ +import { Far, makeMarshal } from '@endo/marshal'; +import anyTest, { type TestFn } from 'ava'; +import { Command } from 'commander'; +import { makeRatio } from '@agoric/zoe/src/contractSupport/ratio.js'; +import { flags } from '../../tools/cli-tools.js'; +import { mockStream } from '../../tools/mock-io.js'; +import { addLPCommands } from '../../src/cli/lp-commands.js'; + +const makeTestContext = () => { + const program = new Command(); + program.exitOverride(); + const out = [] as string[]; + const err = [] as string[]; + + const USDC = Far('usdcbrand'); + const FastLP = Far('fastlpbrand'); + const slotToVal = { + '0': USDC, + '1': FastLP, + }; + const valToSlot = val => { + if (val === USDC) { + return '0'; + } + if (val === FastLP) { + return '1'; + } + return 'none'; + }; + + const marshaller = makeMarshal(valToSlot, slot => slotToVal[slot]); + const now = () => 1234; + + addLPCommands(program, { + vstorageKit: { + // @ts-expect-error fake brands + agoricNames: { brand: { FastLP, USDC } }, + marshaller, + // @ts-expect-error ignore fancy return type + readPublished: async (path: string) => { + if (path === 'fastUsdc.poolMetrics') { + // @ts-expect-error not real brands + return { shareWorth: makeRatio(110n, USDC, 100n, FastLP) }; + } + return {}; + }, + }, + stdout: mockStream(out), + stderr: mockStream(err), + env: {}, + now, + }); + + return { program, marshaller, out, err, USDC, FastLP, now }; +}; + +const test = anyTest as TestFn>>; +test.beforeEach(async t => (t.context = await makeTestContext())); + +test('fast-usdc deposit command', async t => { + const { program, marshaller, out, err, USDC } = t.context; + const amount = 100.05; + const argv = [...`node fast-usdc deposit`.split(' '), ...flags({ amount })]; + t.log(...argv); + await program.parseAsync(argv); + + const action = marshaller.fromCapData(JSON.parse(out.join(''))); + t.deepEqual(action, { + method: 'executeOffer', + offer: { + id: `lpDeposit-1234`, + invitationSpec: { + source: 'agoricContract', + instancePath: ['fastUsdc'], + callPipe: [['makeDepositInvitation', []]], + }, + proposal: { + give: { + USDC: { brand: USDC, value: 100_050_000n }, + }, + }, + }, + }); + + t.is( + err.join(''), + 'Now use `agoric wallet send ...` to sign and broadcast the offer.\n', + ); +}); + +test('fast-usdc withdraw command', async t => { + const { program, marshaller, out, err, FastLP, USDC } = t.context; + const amount = 100; + const argv = [...`node fast-usdc withdraw`.split(' '), ...flags({ amount })]; + t.log(...argv); + await program.parseAsync(argv); + + const action = marshaller.fromCapData(JSON.parse(out.join(''))); + t.deepEqual(action, { + method: 'executeOffer', + offer: { + id: `lpWithdraw-1234`, + invitationSpec: { + source: 'agoricContract', + instancePath: ['fastUsdc'], + callPipe: [['makeWithdrawInvitation', []]], + }, + proposal: { + give: { + PoolShare: { brand: FastLP, value: 90_909_091n }, + }, + want: { + USDC: { brand: USDC, value: 100_000_000n }, + }, + }, + }, + }); + + t.is( + err.join(''), + 'Now use `agoric wallet send ...` to sign and broadcast the offer.\n', + ); +}); diff --git a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md index 07607d1eab1..f16dfdfe658 100644 --- a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md +++ b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md @@ -13,19 +13,19 @@ Generated by [AVA](https://avajs.dev). CLI to interact with Fast USDC liquidity pool␊ ␊ Options:␊ - -V, --version output the version number␊ - --home Home directory to use for config (default:␊ - "~/.fast-usdc")␊ - -h, --help display help for command␊ + -V, --version output the version number␊ + --home Home directory to use for config (default:␊ + "~/.fast-usdc")␊ + -h, --help display help for command␊ ␊ Commands:␊ - config Manage config␊ - operator Oracle operator commands␊ - deposit [options] Offer assets to the liquidity pool␊ - withdraw [options] Withdraw assets from the liquidity pool␊ - transfer Transfer USDC from Ethereum/L2 to Cosmos via Fast␊ - USDC␊ - help [command] display help for command␊ + config Manage config␊ + operator Oracle operator commands␊ + deposit [options] Deposit USDC into pool␊ + withdraw [options] Withdraw USDC from the LP's pool share␊ + transfer Transfer USDC from Ethereum/L2 to Cosmos via Fast␊ + USDC␊ + help [command] display help for command␊ ␊ Agoric test networks provide configuration info at, for example,␊ ␊ @@ -193,93 +193,29 @@ Generated by [AVA](https://avajs.dev). Use AGORIC_NET=local or leave it unset to use localhost and chain id agoriclocal.␊ ` -## shows help for deposit command - -> Snapshot 1 - - `Usage: fast-usdc deposit [options] ␊ - ␊ - Offer assets to the liquidity pool␊ - ␊ - Arguments:␊ - give USDC to give␊ - ␊ - Options:␊ - --id [offer-id] Offer ID␊ - --fee [fee] Cosmos fee␊ - -h, --help display help for command␊ - ␊ - Agoric test networks provide configuration info at, for example,␊ - ␊ - https://devnet.agoric.net/network-config␊ - ␊ - To use RPC endpoints from such a configuration, use:␊ - export AGORIC_NET=devnet␊ - ␊ - Use AGORIC_NET=local or leave it unset to use localhost and chain id agoriclocal.␊ - ` - -## shows help for withdraw command - -> Snapshot 1 - - `Usage: fast-usdc withdraw [options] ␊ - ␊ - Withdraw assets from the liquidity pool␊ - ␊ - Arguments:␊ - want USDC to withdraw␊ - ␊ - Options:␊ - --id [offer-id] Offer ID␊ - --fee [fee] Cosmos fee␊ - -h, --help display help for command␊ - ␊ - Agoric test networks provide configuration info at, for example,␊ - ␊ - https://devnet.agoric.net/network-config␊ - ␊ - To use RPC endpoints from such a configuration, use:␊ - export AGORIC_NET=devnet␊ - ␊ - Use AGORIC_NET=local or leave it unset to use localhost and chain id agoriclocal.␊ - ` - ## shows error when deposit command is run without options > Snapshot 1 - 'error: missing required argument \'give\'' + 'error: required option \'--amount \' not specified' ## shows error when deposit command is run with invalid amount > Snapshot 1 - 'error: command-argument value \'not-a-number\' is invalid for argument \'give\'. Not a decimal number.' - -## shows error when deposit command is run with invalid fee - -> Snapshot 1 - - 'error: option \'--fee [fee]\' argument \'not-a-number\' is invalid. Fee must be a number.' + 'error: option \'--amount \' argument \'not-a-number\' is invalid. Not a number' ## shows error when withdraw command is run without options > Snapshot 1 - 'error: missing required argument \'want\'' + 'error: required option \'--amount \' not specified' ## shows error when withdraw command is run with invalid amount > Snapshot 1 - 'error: command-argument value \'not-a-number\' is invalid for argument \'want\'. Not a decimal number.' - -## shows error when withdraw command is run with invalid fee - -> Snapshot 1 - - 'error: option \'--fee [fee]\' argument \'not-a-number\' is invalid. Fee must be a number.' + 'error: option \'--amount \' argument \'not-a-number\' is invalid. Not a number' ## shows error when config init command is run without options diff --git a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap index 2d0eac4dcb4aaa55da830deb8489ef90b7b6a80e..568f68f3b33dbd8d30bd233c44020c558d50abb6 100644 GIT binary patch literal 1559 zcmV+y2I%=gRzVmU&-n|69z3uelw9}&XdNWS+;YQM#3Pa zQvY<7$0~8$?4cq{xEXlDfJFW{uNg4x=Z8&zD z!Z6wuIT9D4cA4Fwe=Y@j>an-p)A#5b^a=U~eT!nrrA3OODk*M~gZCf^+}+u4zWH`` zxP05|hSGbjfBOT8Q;3%jbRaJ@1Q-}&tB-}g>5>*FT$m?k&Yz6Yl=A})nKgHulq22A*8j?>xO1(Lu8 zp<*5-E5S3S2~J)<=|ITe#N6j}1(B4Y;~byJc|Qb5xp30r@6liedr^5VCF& z54xz#EH{t?gsq5Cuw9t-+&uTWLXV`nGN59O(31*0DSEsQU-{T$aY$zI>bCgvO6}U` z#fN6ezyM7M9cez-QOK@RveT6kUL2>B{z)9Q%a3UF`HEeX=X{^_By)O74w5kJ9WLo)GU$LQbO1;( zp?j!ciw$HfXdAYC)U*w7ob?P%Jf#2?#)Fl;rin{O>Ved7&RL?AiQzCt97q|ymfdtQ z0NCkE#XTU@1On+os@EDKCC|B^u^uaC7yvGM5*RHfCh(FCq7avjjesc9n=30mo(rTJ zOH9@T(u%d{+VD95$EoIy4m*Itk0ckQQwDVG1;C1HE}OwdAb1%`MX>Yj!O?E#$Ni_r zU#E*1lP4PU9ijAC2vR|a>>Pm;#6lxwYEQZYsR@|y!3!7{z2p49^5S@iEA4}X^kkvab{LXssd*x%U*`G)M%=Ko%BD+;qpETIpBg3 z`oIOm23)dPG!3_hCNsi>pZUH*t zJ(-HhwJMymByt14n4MmlCtj=+3L2(yE=JSwOQhr}XJLlCN&!oT_baqaTcMtX_*Eo4|Bo+D)3lyHZifp><@en~@X~IE*3n`e4s*Eek z=-1GuMU;6k0ot^tavr2InCQf)2X+VT9e!LcsK|rUScPX}Mq9}FO*m!Qn`?_^+i$L{ zG@9$RdaYh-Y&Ki1wN_dSXDC?2-#3cJYGd7kNcYJkChWfWKbRX+E`mF_>%z}=T6?Aq?S&mT>CN?NLKH*TN@h6dNug1A{TcylhrF=QQN{O=qv7%17|5u?-`SX5N zoibW`I?mYIv$9Az)ug!Smom?XEmEpb-4-eD&4n3@lo>0F{DC@uJuv?v{!T~l%^a9F zdg9%{S#x_Be5hB}{`b}kf34|oj5qiAG(a(TA zo&^|ian3^HWn#Mw?xzB{BMWW~;zE^~a76 zOz753$UYuM%R(4)S2i`N&kL|Ot4S?gg;mg64*2M2jH$7$7qC6F*alo{F8V`^Ln2d- zm#QN?1ye`?82{cueb?K~ literal 1731 zcmV;!20ZyeRzV=})o&-5b0}&8)7|yur7LrC z{uX~r6d#KS00000000B+S}d< z$j^6I?%ew9mL4njZhdk4-#aIg^l%4UB2_t(jtw0iD$1EWg^taGfH3FOvf3{WKyjc< zp&+)3HNi6?6`Z`;Z-Y>%Q{L#>{tNhAJ?EDGsq1dsW4KYJcM+f zML`#Zey!~B0AV*I%6pQ3?YW+Cs6bnBaj9WN62avHY|dQTgKr&lN#rZ7e!^J&b-gk$ zd-ai4>S?U8Plty?W$2U3#Nmmq*f*BdPX5Hk8UW+QOTtJG%?b;EhbV~RV$9MbL2Mtd zRv1;%0&p|80%exLrEd!3zshAeAh^NX!fl;vjqOM`JxeU03J|!`KCWlB%u^@5! zfQ?he1z@ko1+_t;RKQT3b8#ji6ueKJG!T&>+6_RN%Ymr1SOi{>AoOuf%kY#6<<9!L zgMEgoLSm5>hHBkNESrd00LKXvU%zStGAHDerEZjw?E%T_T+@p+Y49TCLc!h-N3Rds zzZ^b2{w`6bMV?5^PWap=K5zj(l0E`e5HX1=-2?Ho$E5#nVuHom?(;NN7j)Xv_t};7ixdg4(p^BeU;-{GV#E(#C^eA+Q z!tDB!izu~V0<>vGWh_W&Fwux%3(O9B@#@nuL4-DRBH^EoS#2O^H{rC! z@YPt=+kR(#y;k3GmgpZ zvf1$%(dBy0fKdDlSvf!{k@ZlN)0(bh{+t5rnMd4(W4-!ix>#prr9W(^RX;o4>P6St za7nAyZM3>%yG~m5+D5aw(dyP4#6f$j+H4Ycqt`8%*Gbd|LTuaBV3~ZSTB&l{= z&F$@)-K^JI-HoPQb86epCaL2#cB?jSG^XS9Tl_)$4R{qQ#1;4Kp9R;zN>?iA(tBvKI*usPQ)lg8= z`~Bieu`hE0y*qj-R@5S}tSLGK`Ab&)UT*uEogYsOl?Bv+4%Z6GRNqb|x(@c^ITsN+ zE~3ek=6csf9G5nhY+Bfj{-E1aOs5AqI(BC4y=w1W*LooY%cj>og$ z0LuEr*G9$tp;0Vh4olZyF_Ovy2=P}Clo(fKQcfDwC9NkFuX!+pw*rS;0RabX8W7*K zwJWI^t+~u_5l3yXb2gk98xr?JDYHXL8H`1yRNxsR1d&wGMIhJ19!@YHjs`kw3}~=B z6OfzXK@9jB9&FEN$Bh$VbQ_N8pG?h#=Q-p%2Kj&kS0;?KM@qoB0vV!BU5ZXIPlsFE zQ)Ao8VS98vPZN$!VCoc}Lgj(BZxqh69Gq6MtA;u@1&;cD9(2hOQ2_8D2k>z*z|p&o Z0*p`Q!5A~80OG>v_8-Ax@1c|>004MELcIV0