diff --git a/packages/fast-usdc/src/cli/lp-commands.js b/packages/fast-usdc/src/cli/lp-commands.js index 4947d02d7ac..d2af69ebcca 100644 --- a/packages/fast-usdc/src/cli/lp-commands.js +++ b/packages/fast-usdc/src/cli/lp-commands.js @@ -5,8 +5,14 @@ */ import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; -import { Nat } from '@endo/nat'; import { InvalidArgumentError } from 'commander'; +import { + assertParsableNumber, + multiplyBy, + parseRatio, +} from '@agoric/zoe/src/contractSupport/ratio.js'; +import { Nat } from '@endo/nat'; +import { AmountMath } from '@agoric/ertp'; import { outputActionAndHint } from './bridge-action.js'; /** @param {string} arg */ @@ -19,6 +25,17 @@ const parseNat = arg => { } }; +/** @param {string} arg */ +const parseDecimal = arg => { + try { + assertParsableNumber(arg); + const n = Number(arg); + return n; + } catch { + throw new InvalidArgumentError('Not a number'); + } +}; + /** * @param {Command} program * @param {{ @@ -34,10 +51,6 @@ export const addLPCommands = ( program, { fetch, vstorageKit, stderr, stdout, env, now }, ) => { - const operator = program - .command('lp') - .description('Liquidity Provider commands'); - const loadVsk = async () => { if (vstorageKit) { return vstorageKit; @@ -49,24 +62,27 @@ export const addLPCommands = ( /** @type {undefined | ReturnType} */ let vskP; - operator + 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 accept.json --from gov1 --keyring-backend="test"', + '\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 ', 'uUSDC amount', parseNat) + .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 USDC_DECIMALS = 6; + const unit = AmountMath.make(usdc, 10n ** BigInt(USDC_DECIMALS)); + const usdcAmount = multiplyBy(unit, parseRatio(opts.amount, usdc)); + /** @type {OfferSpec} */ const offer = { id: opts.offerId, @@ -77,7 +93,7 @@ export const addLPCommands = ( }, proposal: { give: { - USDC: { brand: usdc, value: opts.amount }, + USDC: usdcAmount, }, }, }; @@ -91,12 +107,12 @@ export const addLPCommands = ( outputActionAndHint(bridgeAction, { stderr, stdout }, vsk.marshaller); }); - operator + 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 accept.json --from gov1 --keyring-backend="test"', + '\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 ', 'FastLP amount', parseNat) .option('--offerId ', 'Offer id', String, `lpWithdraw-${now()}`) @@ -131,5 +147,5 @@ export const addLPCommands = ( ); }); - return operator; + 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 7eae770761e..026c8d01dac 100644 --- a/packages/fast-usdc/test/cli/cli.test.ts +++ b/packages/fast-usdc/test/cli/cli.test.ts @@ -82,22 +82,22 @@ test('shows help for config show command', async t => { }); test('shows error when deposit command is run without options', async t => { - const output = await runCli(['lp', 'deposit']); + 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(['lp', 'deposit', '--amount', 'not-a-number']); + const output = await runCli(['deposit', '--amount', 'not-a-number']); t.snapshot(output); }); test('shows error when withdraw command is run without options', async t => { - const output = await runCli(['lp', 'withdraw']); + const output = await runCli(['withdraw']); t.snapshot(output); }); test('shows error when withdraw command is run with invalid amount', async t => { - const output = await runCli(['lp', 'withdraw', '--amount', '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 index b87c8281faf..06622a122a4 100644 --- a/packages/fast-usdc/test/cli/lp-commands.test.ts +++ b/packages/fast-usdc/test/cli/lp-commands.test.ts @@ -1,4 +1,4 @@ -import { makeMarshal } from '@endo/marshal'; +import { Far, makeMarshal } from '@endo/marshal'; import anyTest, { type TestFn } from 'ava'; import { Command } from 'commander'; import { flags } from '../../tools/cli-tools.js'; @@ -11,20 +11,23 @@ const makeTestContext = () => { const out = [] as string[]; const err = [] as string[]; - const USDC = 'usdcbrand'; - const FastLP = 'fastlpbrand'; + const USDC = Far('usdcbrand'); + const FastLP = Far('fastlpbrand'); const slotToVal = { '0': USDC, '1': FastLP, }; - const valToSlot = { - USDC: '0', - FastLP: '1', + const valToSlot = val => { + if (val === USDC) { + return '0'; + } + if (val === FastLP) { + return '1'; + } + return 'none'; }; - const marshaller = makeMarshal( - val => valToSlot[val], - slot => slotToVal[slot], - ); + + const marshaller = makeMarshal(valToSlot, slot => slotToVal[slot]); const now = () => 1234; addLPCommands(program, { @@ -46,12 +49,9 @@ const test = anyTest as TestFn>>; test.beforeEach(async t => (t.context = await makeTestContext())); test('fast-usdc lp deposit sub-command', async t => { - const { program, marshaller, out, err, USDC, now } = t.context; - const amount = 100; - const argv = [ - ...`node fast-usdc lp deposit`.split(' '), - ...flags({ amount }), - ]; + 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); @@ -59,7 +59,7 @@ test('fast-usdc lp deposit sub-command', async t => { t.deepEqual(action, { method: 'executeOffer', offer: { - id: `lpDeposit-${now()}`, + id: `lpDeposit-1234`, invitationSpec: { source: 'agoricContract', instancePath: ['fastUsdc'], @@ -67,7 +67,7 @@ test('fast-usdc lp deposit sub-command', async t => { }, proposal: { give: { - USDC: { brand: USDC, value: BigInt(amount) }, + USDC: { brand: USDC, value: 100_050_000n }, }, }, }, @@ -82,10 +82,7 @@ test('fast-usdc lp deposit sub-command', async t => { test('fast-usdc lp withdraw sub-command', async t => { const { program, marshaller, out, err, FastLP, now } = t.context; const amount = 100; - const argv = [ - ...`node fast-usdc lp withdraw`.split(' '), - ...flags({ amount }), - ]; + const argv = [...`node fast-usdc withdraw`.split(' '), ...flags({ amount })]; t.log(...argv); await program.parseAsync(argv); @@ -93,7 +90,7 @@ test('fast-usdc lp withdraw sub-command', async t => { t.deepEqual(action, { method: 'executeOffer', offer: { - id: `lpWithdraw-${now()}`, + id: `lpWithdraw-1234`, invitationSpec: { source: 'agoricContract', instancePath: ['fastUsdc'], @@ -101,7 +98,7 @@ test('fast-usdc lp withdraw sub-command', async t => { }, proposal: { give: { - PoolShare: { brand: FastLP, value: BigInt(amount) }, + PoolShare: { brand: FastLP, value: 100n }, }, }, }, 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 855006de77e..f16dfdfe658 100644 --- a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md +++ b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md @@ -21,7 +21,8 @@ Generated by [AVA](https://avajs.dev). Commands:␊ config Manage config␊ operator Oracle operator commands␊ - lp Liquidity Provider 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␊ 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 eee94a8427c..568f68f3b33 100644 Binary files a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap and b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap differ