From 2411b6afbac235e8b0965cfda1a4da5b85846e21 Mon Sep 17 00:00:00 2001 From: samsiegart Date: Tue, 10 Dec 2024 00:23:24 -0800 Subject: [PATCH] fixup! feat(fast-usdc): cli for lp deposit and withdraw --- packages/fast-usdc/src/cli/lp-commands.js | 42 +++++++++++----- .../fast-usdc/src/cli/operator-commands.js | 4 ++ packages/fast-usdc/test/cli/cli.test.ts | 8 ++-- .../fast-usdc/test/cli/lp-commands.test.ts | 45 ++++++++---------- .../test/cli/snapshots/cli.test.ts.md | 3 +- .../test/cli/snapshots/cli.test.ts.snap | Bin 1532 -> 1559 bytes 6 files changed, 60 insertions(+), 42 deletions(-) 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 eee94a8427c4e8aa85f5807af8ec38ebe1b02d8c..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 1532 zcmVF z!-Kyb-23dF9%~QpeSZIs2j`OXu?0OMl^sggfeRie%9*@`3x^K|gt?cN)j2!>#ep(~ zf;cK#30@GX;QaNo4*2wKNL{LKA>iD%tmD{3%L1^!-vrwpqL7p`NDm&WAXK0{gyc30 z2VE5UDcj=%guQ?$Z$IbYi$v20pos$&Ocwc(JTb{3mkMEI(zuB8>FWOt1iWfPyG4@-vSGaeN$4 zFbhcwAP*}Uha>V&1RqitGtVhOnCzi|Ju=`SQ~R*zqE!0;XUUx?ggwCr@LYK)FdUQ* zYns^(mjfMx1gaop5>=wt=(fkD z0>WHyJVF^z7h=%S&5{L>B>R`CWY=zwVrnSNr}_QuOqxHwpG)(Rjd8wDD8#ZBR!FP@ ztDV#zOZwBEj$(3>f3#cDgvsI8M%o3Su;Z^@R&qHr!!iZ%Rfb$YF@IH3^8)VL5+MpX)v zo`)*BN{OFf9uhx9T}i{hB?{B?&*LkT6Z19jPK!%xaecZLqtF!!@=-q{$TyFQ1esNU zWfjLiqf49du8+1vbYV~bW_y%aa{6ZA31ZrlBmD>v$I&IQ1fBNz*}?JA>@Fp3PH}-Q zgbbM@ZUa*;d(=mew^HFC!iCD4jVg~TspunU^CC(-m;r5GQz;K(8O(HI(gTx0hbNzw z3nFmfA{74Rl-UMyItk}&TvKCRr+uqjuGV%+l~SctZPgl$dL!;;6BHz%9~wnBGvLe+lghHi@fg!~t!h9heuc~)pp?k^DD0%CyD)z)0ovvg@4=a#{VJKP ztFqFspz|)jINr)l*WK!oX0=;ycFAsyG;7tZMrEtnt<{N(&Q7J#Aib?>y`J$W0XEH@ zv_DZV9X&GgWhs!P(rq?&cdJgLR&92-8cx-%?z-EghP&9UIJniQG_bqf{EwI0)VS+X zmhMu%n%<>^NrPC>r#$>8)2ICXu&7U&>^&`OYVTRtq+CcPDC_4c&&O?2icsA(DId&* z1)G!wJB##&x_m#d{3iZhTkmQ%%$4qVE7)u9HiM7#${L+Y6mA7;zh}0BU)PGZg2+tz zm37L|#@(6N%7eNS2Ni|jBL3pyV!-A26auKse1tI(%0>Fki*%sZnvum82z(n-UEbs8 zV8gb}$?0DFRb+ny7*`+zbZC#Fn*e*d2r!^*NPO*OWIGS;rwq7L18xSUHxK39Pj;MLRs