From 06649f8017904c4ea9aacbbd609f5ad9466a5ed4 Mon Sep 17 00:00:00 2001 From: Samuel Siegart Date: Fri, 20 Dec 2024 10:15:31 -0800 Subject: [PATCH] fixup! feat(fast-usdc): support risk assessment arg --- .../boot/test/fast-usdc/fast-usdc.test.ts | 50 +++++++++++++++ .../fast-usdc/snapshots/fast-usdc.test.ts.md | 25 ++++++++ .../snapshots/fast-usdc.test.ts.snap | Bin 2218 -> 2399 bytes .../fast-usdc/src/exos/transaction-feed.js | 60 ++++++++---------- packages/fast-usdc/test/exos/settler.test.ts | 2 +- .../test/exos/transaction-feed.test.ts | 54 ++++++++++++++++ 6 files changed, 156 insertions(+), 35 deletions(-) diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index 38b2268e2c4..60850171ada 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -311,6 +311,56 @@ test.serial('makes usdc advance', async t => { await documentStorageSchema(t, storage, doc); }); +test.serial('skips usdc advance when risks identified', async t => { + const { walletFactoryDriver: wd, storage } = t.context; + const oracles = await Promise.all([ + wd.provideSmartWallet('agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8'), + wd.provideSmartWallet('agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr'), + wd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'), + ]); + + const EUD = 'dydx1riskyeud'; + const lastNodeValue = storage.getValues('published.fastUsdc').at(-1); + const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue)); + const evidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX( + // mock with the read settlementAccount address + encodeAddressHook(settlementAccount, { EUD }), + ); + + await Promise.all( + oracles.map(wallet => + wallet.sendOffer({ + id: 'submit-mock-evidence-dydx-risky', + invitationSpec: { + source: 'continuing', + previousOffer: 'claim-oracle-invitation', + invitationMakerName: 'SubmitEvidence', + invitationArgs: [evidence, { risksIdentified: ['TOO_LARGE_AMOUNT'] }], + }, + proposal: {}, + }), + ), + ); + await eventLoopIteration(); + + t.deepEqual( + storage + .getValues(`published.fastUsdc.txns.${evidence.txHash}`) + .map(defaultSerializer.parse), + [ + { evidence, status: 'OBSERVED' }, // observation includes evidence observed + { status: 'ADVANCE_SKIPPED', risksIdentified: ['TOO_LARGE_AMOUNT'] }, + ], + ); + + const doc = { + node: `fastUsdc.txns`, + owner: `the Ethereum transactions upon which Fast USDC is acting`, + showValue: defaultSerializer.parse, + }; + await documentStorageSchema(t, storage, doc); +}); + test.serial('restart contract', async t => { const { EV } = t.context.runUtils; await null; diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md index ace588bbdf2..726ac84a4ac 100644 --- a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md +++ b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md @@ -174,3 +174,28 @@ Generated by [AVA](https://avajs.dev). }, ], ] + +## skips usdc advance when risks identified + +> Under "published", the "fastUsdc.txns" node is delegated to the Ethereum transactions upon which Fast USDC is acting. +> The example below illustrates the schema of the data published there. +> +> See also board marshalling conventions (_to appear_). + + [ + [ + 'published.fastUsdc.txns.0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', + { + status: 'ADVANCING', + }, + ], + [ + 'published.fastUsdc.txns.0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799', + { + risksIdentified: [ + 'TOO_LARGE_AMOUNT', + ], + status: 'ADVANCE_SKIPPED', + }, + ], + ] diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap index 0098b4788bfb2c84ca53df4b055872bf5e179aab..8528f3eda7ceb0a0e98939df069cb4dddc414925 100644 GIT binary patch literal 2399 zcmV-l383~tRzVo1=+8>Ju00000000B!SzC-7R~i1!%-HMfCfQw5BtmI2atck+UTkmd^`;eyg9=J(BRiJ>> z0xALtQ6T|UB@oQ3xrY^X?<_Yf760nNd^6&@{)=n_#YA@$^?KEfITESP9izi zrJIB_pGHSVL54vH$Os|#H;q&VU=_fpLWWA%PyyMAJw(ABL>ReU#*9}(as0;Ck+F$MUN5^@>oaH)loypsyS zoEjC(%nMOH>RM)rmdGdO)9Kuy8Omrr zmC3T1L$e|8dksTWm-s4kPBG78PKmjlzLRq^GYK=9n4ilV+1Ye5mCQ0)OeYtz*=%|~ zkjzf^*J-uNsJn9SR1*~L+5!b#PFJZn zCTPFDWti~vz?kr^?wF95dCMrzwICt#GJszJcniQ70Veyyf+oxDWT4!38r%^sHN-h8 zn3S#sq*~FYLcYBkcso*|8yBc+(Slt!`9aX(T0>HI7$fT63GfdByfa+8AvlN;HLU>m zDZsg~YOF)GrT~`};EAy6jt|A2nrvIFpA8r}2@-3ckmxwsle2ABV&>sG{7N?+ z^Y4E*$l*6PLJoD<>A?+Km|Als2T3MqvB2#?0Q(yRQ03g#D@?eS;q|6~$c0-*0nZFf z0sFd8fS>xQdge0D=UXH&^eTYY0qh{ay9tmdK#>3+?@tC%x&5vMuzQ?2My*m{E;El( zn>t1v-hUWS8TQK5We;*!l%?#=AoL3^sC|^XE?@Vg^gsYe$E7sipqpci)m2UhRCY;~ zvz*&4@n#T;rT84H(oIj!@^uAxUID({pIKg2fY%j34MSVBLv^1D+^qulg;j?;R3B1- zkEpGBNH}JI=oW+Rx6BYQm3j8^&x@#Nc+dUwJ1mR~+>%kSK)`#mJzlq3C z-h}P5H(`5qaNAf19c~M_F`|A!176U87yEEyjHtiWfInyeBE7qqF_1{N@7AF9M+Rn* zmhIWWZDSqn+1}QK`1QN!qlDMaLy-peywaC@0(iF#`}iDlelf2m! z-Mpks#wX$~t8l?W^HAp1>9)k%X2&1E|MaXpDtFfNU3ONi4S=KVNZlse(L=T~AgdGo zCi!|lrtit_^Lf2_IW;w`>$-lL3wBuV0)r3N>pN!{!e8_cO>|Xiph`nUdh{n2W6Fh)Zku) zd-qQI#*>XkVRF?HQy0DZaBqiB#;wp*=IJ%hG<0gN`A5RewuNMF*?IZpfzQhiby+{L z;doPt4I`T@7@1@uUC1P8YUYqJYors@NX}-H8CEdNbUKqXXEGVHm@O1%3M@TWEY4(S z)9KV~COMl($sAv6owb!10e1I4SCbG-M?FDBt@VGvj@fB?&U4NCl!`cx;^^jgWjrDZQow{dYL)8Yk8}l zZkfJ$(PCzo|L(pT*mPSqA>*Xz6cb;ElJu%7^*?AZ}fVO0)~Ewa@z4(wQ?W59H1rUs%bVI&=Q? R(*JMK{|kKMIx?^y002Lgq^tk{ literal 2218 zcmV;b2vzq%RzV*U(0|lC_oI+c4zVrHGpD#tIjxPyO zX&Q1#D%yiE-W}VUTkkl#dPMTn$9h)NYgJRpI1Kq@@&PXej}1*8^G zC3vAi0;)K&lb$r ztBhHClRLJ#tqZPi_=0=1#%$lEO~1~CfAzX$Q9(bhysTmZ!H1-XG6x_B-~dU^l0?b# z=r$ppr~Ui)gG_=DkSRhw)NxW7fOP<0h&d{8M+Iar4iE)*5EUdbOYUm6Dvs^fnKfUf zzBun&=6q238CkeYb0bJXf;`$U0Q?%jTL2bFJ6H1I$|YtBqIMr=W@bR95v15h$T@;; zx@ogcZC3`jnGdo*D5hh)!{rr+m1^9x&2pW(E^|&<9pj`Kwq$Zf%xzk_t?&l5-8Nn$ znjFk41h^WnQ|qgf$U2{k)R`)I6ZLzpD!I*q>Pqx zg(6!#vJ{Jcr)i4jIlj)^hnVj(x5m8w*v9hWV%9RUE6ZiGxRf_?Mv>8K-Z)+?7V|6F ze6esOcRX7x#bQs$DeDZZ`i&cikH5*?s_iwXu(|8E$6sL%tuSZnU*}zmd3w70nx*G- zQD=I3z}uGAU2ZYm_H~OntQL$_uyO);jk(ODj!s=mm-S@)CNu4-ZH7zC4i+5uB=!XL zg?@f@r7V}={N%Yh)7chnG##c_n8P=9+i_aH@My410}tP-Sz1T&K+@qu#~)<~yWt*A6M@a=2>4 zIYIl~E#rh|#^!|g4d#ThtXpPzzDo&-R{;DLz}o<32#^^~3z}SR4@Q>TUX#1Rqoz1Z z1(Vi|h*hgPRLItAk=CgO-MT>D6O_UCU(q_Y-tdBb+8;GQ(<>W*=b z+iEZ`G0@v7diuy?0lz)`T-4?(|~f^ZK^MWr2&^T;A$MY zcb8fFvNqPZDMob2el(sl$whpBHlD9)^i0+-$INViqgnObcZEr=XUvNR4W8CxjrZ26BBSbnYt>w1doM0@3oQ^=ICFBg} zPS?C0rDAD5%bIlCm!o_`0bWpm?~PWL*A(Cl1yJMEmh6KbRDruy;QlytvJd*G3Vcch zHsjEKI{Ul|e6h{1!{{tU)Ss)suTQG{A73un( z8Q(Y6w>`VFJ0QXOT@6^m@71A1TYFv|sXYjXA$s)<0@rZrPSCwu7WP+v2x} z=G@)Z9BP`p%uCr62pCdI)H156R}bXz8-2>DL% zsL01Z29K(I`=+Hx00lBkrBXyK5#S^No{0gqIPiG_e2oBa$AA-YU_t?=6ku8D1x}6> z$anP%r0my<683hG&jSNQmCD-=%SB(olaY3%<1Y0IO{VZ9M)j2*e&ckoTCBNY87s6cYdgc(`m`6 zUNXX|zQct-hVxV7J1d8j!&lM{H>uOUe@Y)sFW40`)8w95uuUot`rL7tmoXN&X9c^w zg)3|64d(each}NK*V4A>=aw?YT6%6Ry@M}v*JWnFcc&}~GrJ9$rJMxUxi5ynj$|dQ z&4bdq^X^I$b4_m9ZcW;n9v6I3wu`NE+f620eQW(4&QON?Gq*PR&p1x~`w*f*sWd(BNa?`YxHK2qyg#a|1zbR%t_I#NbkM5Y%=B^BT;u zso06CQ;$V93YBgBfsj2GxM$Xx++o<Gs^7C|i~7S+(>*fby>gX)+T?zN`}bu6 z=S+K}kXg6I{3So^?(MLX@fviU`FhK@Or2UA!I7|k+d>Mr?7aNS*yrU(2h5+?a{c-2 zmRU3^X2HnjD}^l0EgmtK%zT!b#!}HJu!?Er^993NEEKG2u~J>Eu>5khx>#Jw=W|O1 zV=0@HHNM_GYbz-N>>GWqCNY{$`htp9_y2%BrIib%)8$jAPqz2Ne;v6W4*E0X8s@m1 s=~hD*9(8?chV$8Ka#!E1+h#p-FK-8XQenHb|Ls2c--S_@2D=*o00@auLjV8( diff --git a/packages/fast-usdc/src/exos/transaction-feed.js b/packages/fast-usdc/src/exos/transaction-feed.js index 5560ad01f63..55bcd6cb859 100644 --- a/packages/fast-usdc/src/exos/transaction-feed.js +++ b/packages/fast-usdc/src/exos/transaction-feed.js @@ -36,6 +36,22 @@ const TransactionFeedKitI = harden({ }), }); +/** + * @param {globalThis.MapStore[]} riskStores + * @param {string} txHash + */ +const allRisksIdentified = (riskStores, txHash) => { + /** @type {Set} */ + const setOfRisks = new Set(); + for (const store of riskStores) { + const next = store.get(txHash); + for (const risk of next.risksIdentified ?? []) { + setOfRisks.add(risk); + } + } + return [...setOfRisks.values()].sort(); +}; + /** * @param {Zone} zone * @param {ZCF} zcf @@ -60,17 +76,11 @@ export const prepareTransactionFeedKit = (zone, zcf) => { TransactionFeedKitI, () => { /** @type {MapStore} */ - const operators = zone.mapStore('operators', { - durable: true, - }); + const operators = zone.mapStore('operators'); /** @type {MapStore>} */ - const pending = zone.mapStore('pending', { - durable: true, - }); + const pending = zone.mapStore('pending'); /** @type {MapStore>} */ - const risks = zone.mapStore('risks', { - durable: true, - }); + const risks = zone.mapStore('risks'); return { operators, pending, risks }; }, { @@ -136,7 +146,7 @@ export const prepareTransactionFeedKit = (zone, zcf) => { */ attest(evidence, riskAssessment, operatorId) { const { operators, pending, risks } = this.state; - trace('submitEvidence', operatorId, evidence); + trace('attest', operatorId, evidence); // TODO https://github.com/Agoric/agoric-sdk/pull/10720 // TODO validate that it's a valid for Fast USDC before accepting @@ -151,18 +161,8 @@ export const prepareTransactionFeedKit = (zone, zcf) => { trace(`operator ${operatorId} already reported ${txHash}`); } else { pendingStore.init(txHash, evidence); - } - } - - // accept the risk assessment - { - const riskStore = risks.get(operatorId); - if (riskStore.has(txHash)) { - trace( - `operator ${operatorId} already reported risk for ${txHash}, updating...`, - ); - riskStore.set(txHash, riskAssessment); - } else { + // accept the risk assessment as well + const riskStore = risks.get(operatorId); riskStore.init(txHash, riskAssessment); } } @@ -206,19 +206,11 @@ export const prepareTransactionFeedKit = (zone, zcf) => { lastEvidence = next; } - // take the union of risks identified from all operators - /** @type {Set} */ - const setUnionRisks = new Set(); const riskStores = [...risks.values()].filter(store => store.has(txHash), ); - for (const store of riskStores) { - const next = store.get(txHash); - for (const risk of next.risksIdentified ?? []) { - setUnionRisks.add(risk); - } - } - const unionRisks = [...setUnionRisks.values()].sort(); + // take the union of risks identified from all operators + const risksIdentified = allRisksIdentified(riskStores, txHash); // sufficient agreement, so remove from pending risks, then publish for (const store of found) { @@ -227,10 +219,10 @@ export const prepareTransactionFeedKit = (zone, zcf) => { for (const store of riskStores) { store.delete(txHash); } - trace('publishing evidence', evidence, unionRisks); + trace('publishing evidence', evidence, risksIdentified); publisher.publish({ evidence, - risk: { risksIdentified: unionRisks }, + risk: { risksIdentified }, }); }, }, diff --git a/packages/fast-usdc/test/exos/settler.test.ts b/packages/fast-usdc/test/exos/settler.test.ts index 41453467923..5a8d9928385 100644 --- a/packages/fast-usdc/test/exos/settler.test.ts +++ b/packages/fast-usdc/test/exos/settler.test.ts @@ -399,7 +399,7 @@ test('skip advance: forward to EUD; remove pending tx', async t => { cctpTxEvidence.tx.amount, ), [], - 'SETTLED entry removed from StatusManger', + 'FORWARDED entry removed from StatusManger', ); const { storage } = t.context; t.deepEqual(storage.getDeserialized(`fun.txns.${cctpTxEvidence.txHash}`), [ diff --git a/packages/fast-usdc/test/exos/transaction-feed.test.ts b/packages/fast-usdc/test/exos/transaction-feed.test.ts index 7c7c6a04d5d..e3cb8df299b 100644 --- a/packages/fast-usdc/test/exos/transaction-feed.test.ts +++ b/packages/fast-usdc/test/exos/transaction-feed.test.ts @@ -93,6 +93,60 @@ test('takes union of risk assessments', async t => { }); }); +test('takes union of risk assessments pt. 2', async t => { + const feedKit = makeFeedKit(); + const evidenceSubscriber = feedKit.public.getEvidenceSubscriber(); + + const { op1, op2 } = await makeOperators(feedKit); + + const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); + op1.operator.submitEvidence(e1, { risksIdentified: ['RISK1'] }); + op2.operator.submitEvidence(e1); + + // Publishes with 2 of 3 + const accepted = await evidenceSubscriber.getUpdateSince(0); + t.deepEqual(accepted, { + value: { evidence: e1, risk: { risksIdentified: ['RISK1'] } }, + updateCount: 1n, + }); +}); + +test('takes union of risk assessments pt. 3', async t => { + const feedKit = makeFeedKit(); + const evidenceSubscriber = feedKit.public.getEvidenceSubscriber(); + + const { op1, op2 } = await makeOperators(feedKit); + + const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); + op1.operator.submitEvidence(e1, { risksIdentified: ['RISK1'] }); + op2.operator.submitEvidence(e1, { risksIdentified: ['RISK1'] }); + + // Publishes with 2 of 3 + const accepted = await evidenceSubscriber.getUpdateSince(0); + t.deepEqual(accepted, { + value: { evidence: e1, risk: { risksIdentified: ['RISK1'] } }, + updateCount: 1n, + }); +}); + +test('takes union of risk assessments pt. 4', async t => { + const feedKit = makeFeedKit(); + const evidenceSubscriber = feedKit.public.getEvidenceSubscriber(); + + const { op1, op2 } = await makeOperators(feedKit); + + const e1 = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); + op1.operator.submitEvidence(e1); + op2.operator.submitEvidence(e1); + + // Publishes with 2 of 3 + const accepted = await evidenceSubscriber.getUpdateSince(0); + t.deepEqual(accepted, { + value: { evidence: e1, risk: { risksIdentified: [] } }, + updateCount: 1n, + }); +}); + test('disagreement', async t => { const feedKit = makeFeedKit(); const { op1, op2 } = await makeOperators(feedKit);