From d10cfb9e758934f3bd38388c54c8b72d4ee256e2 Mon Sep 17 00:00:00 2001 From: samsiegart Date: Tue, 10 Dec 2024 10:16:01 -0800 Subject: [PATCH] fixup! feat(fast-usdc): cli for lp deposit and withdraw --- packages/fast-usdc/src/cli/lp-commands.js | 72 ++++++++++++------- .../fast-usdc/test/cli/lp-commands.test.ts | 20 ++++-- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/packages/fast-usdc/src/cli/lp-commands.js b/packages/fast-usdc/src/cli/lp-commands.js index d2af69ebcca..4b829cfbfba 100644 --- a/packages/fast-usdc/src/cli/lp-commands.js +++ b/packages/fast-usdc/src/cli/lp-commands.js @@ -2,29 +2,20 @@ * @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 { Nat } from '@endo/nat'; import { AmountMath } from '@agoric/ertp'; import { outputActionAndHint } from './bridge-action.js'; -/** @param {string} arg */ -const parseNat = arg => { - try { - const n = Nat(BigInt(arg)); - return n; - } catch { - throw new InvalidArgumentError('Not a number'); - } -}; - /** @param {string} arg */ const parseDecimal = arg => { try { @@ -36,6 +27,16 @@ const parseDecimal = arg => { } }; +/** + * @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 {{ @@ -79,9 +80,14 @@ export const addLPCommands = ( 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)); + const usdcAmount = parseUSDCAmount(opts.amount, usdc); + + /** @type {USDCProposalShapes['deposit']} */ + const proposal = { + give: { + USDC: usdcAmount, + }, + }; /** @type {OfferSpec} */ const offer = { @@ -91,11 +97,7 @@ export const addLPCommands = ( instancePath: ['fastUsdc'], callPipe: [['makeDepositInvitation', []]], }, - proposal: { - give: { - USDC: usdcAmount, - }, - }, + proposal, }; /** @type {ExecuteOfferAction} */ @@ -114,17 +116,39 @@ export const addLPCommands = ( '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 ', 'FastLP amount', parseNat) + .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 usdc as a Brand type + // @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, @@ -133,11 +157,7 @@ export const addLPCommands = ( instancePath: ['fastUsdc'], callPipe: [['makeWithdrawInvitation', []]], }, - proposal: { - give: { - PoolShare: { brand: poolShare, value: opts.amount }, - }, - }, + proposal, }; outputActionAndHint( diff --git a/packages/fast-usdc/test/cli/lp-commands.test.ts b/packages/fast-usdc/test/cli/lp-commands.test.ts index 06622a122a4..367dc9a78f6 100644 --- a/packages/fast-usdc/test/cli/lp-commands.test.ts +++ b/packages/fast-usdc/test/cli/lp-commands.test.ts @@ -1,6 +1,7 @@ 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'; @@ -35,6 +36,14 @@ const makeTestContext = () => { // @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), @@ -48,7 +57,7 @@ const makeTestContext = () => { const test = anyTest as TestFn>>; test.beforeEach(async t => (t.context = await makeTestContext())); -test('fast-usdc lp deposit sub-command', async t => { +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 })]; @@ -79,8 +88,8 @@ 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; +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); @@ -98,7 +107,10 @@ test('fast-usdc lp withdraw sub-command', async t => { }, proposal: { give: { - PoolShare: { brand: FastLP, value: 100n }, + PoolShare: { brand: FastLP, value: 90_909_091n }, + }, + want: { + USDC: { brand: USDC, value: 100_000_000n }, }, }, },