From 6f4936902fed970858e4666e6794b299d8fd6ad9 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 17 Dec 2024 20:14:58 -0500 Subject: [PATCH 1/4] tools: makeTestAddress helper - returns valid bech32 address for tests --- packages/orchestration/package.json | 1 + .../test/tools/make-test-address.test.ts | 22 +++++++++++++++++++ .../orchestration/tools/make-test-address.js | 21 ++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 packages/orchestration/test/tools/make-test-address.test.ts create mode 100644 packages/orchestration/tools/make-test-address.js diff --git a/packages/orchestration/package.json b/packages/orchestration/package.json index f104ad60ad6..e23054785e7 100644 --- a/packages/orchestration/package.json +++ b/packages/orchestration/package.json @@ -62,6 +62,7 @@ "@endo/import-bundle": "^1.3.2", "@endo/ses-ava": "^1.2.8", "ava": "^5.3.1", + "bech32": "^2.0.0", "c8": "^10.1.2", "prettier": "^3.4.2", "ts-blank-space": "^0.4.4" diff --git a/packages/orchestration/test/tools/make-test-address.test.ts b/packages/orchestration/test/tools/make-test-address.test.ts new file mode 100644 index 00000000000..54b859ad736 --- /dev/null +++ b/packages/orchestration/test/tools/make-test-address.test.ts @@ -0,0 +1,22 @@ +import test from '@endo/ses-ava/prepare-endo.js'; +import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; +import { makeTestAddress } from '../../tools/make-test-address.js'; + +test('makeTestAddress returns valid bech32', t => { + t.is(makeTestAddress(), 'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht'); + t.is(makeTestAddress(1), 'agoric1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc09z0g'); + t.is( + makeTestAddress(0, 'agoric', 32), + 'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkppep3', + ); + t.is( + makeTestAddress(0, 'cosmos'), + 'cosmos1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnrql8a', + ); +}); + +test('makeTestAddress creates address accepted by encodeAddressHook', t => { + const params = { EUD: makeTestAddress(0, 'osmosis') }; + t.throws(() => encodeAddressHook('agoric1FakeLCAAddress', params)); + t.notThrows(() => encodeAddressHook(makeTestAddress(), params)); +}); diff --git a/packages/orchestration/tools/make-test-address.js b/packages/orchestration/tools/make-test-address.js new file mode 100644 index 00000000000..0a80a82562d --- /dev/null +++ b/packages/orchestration/tools/make-test-address.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { bech32 } from 'bech32'; + +/** + * @param {number} index + * @param {string} prefix + * @param {number} byteLength + * @returns {string} a mock bech32 address for tests + */ +export const makeTestAddress = ( + index = 0, + prefix = 'agoric', + byteLength = 20, +) => { + // create a simple 20-byte array (common address length) + const bytes = new Uint8Array(byteLength).fill(0); + // if index provided, put it in the first byte + if (index !== 0) bytes[0] = Number(index); + const words = bech32.toWords(bytes); + return bech32.encode(prefix, words); +}; From 45eeee0ec349b10623871b800479babdf100c21b Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 17 Dec 2024 17:43:04 -0500 Subject: [PATCH 2/4] chore: mock `VLOCALCHAIN_ALLOCATE_ADDRESS` handler returns valid bech32 - instead of `agoric1fakeLCAAddress`, `agoric1fakeLCAAddress1` - to support `encodeAddressHook` which relies on valid bech32 --- .../test/bootstrapTests/orchestration.test.ts | 14 +++++++++++--- .../fast-usdc/snapshots/fast-usdc.test.ts.md | 8 ++++---- .../snapshots/fast-usdc.test.ts.snap | Bin 2209 -> 2218 bytes .../orchestration/contract-upgrade.test.ts | 3 +++ .../orchestration/restart-contracts.test.ts | 3 +++ packages/boot/tools/supports.ts | 3 ++- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index 0de187aa734..e2e49122890 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -16,6 +16,7 @@ import { SIMULATED_ERRORS } from '@agoric/vats/tools/fake-bridge.js'; import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; import { BridgeId } from '@agoric/internal'; +import { makeTestAddress } from '@agoric/orchestration/tools/make-test-address.js'; import { makeWalletFactoryContext, type WalletFactoryTestContext, @@ -410,8 +411,9 @@ test.serial('basic-flows', async t => { const publicSubscriberPaths = Object.fromEntries( wd.getCurrentWalletRecord().offerToPublicSubscriberPaths, ); + const expectedAddress = makeTestAddress(); t.deepEqual(publicSubscriberPaths['request-loa'], { - account: 'published.basicFlows.agoric1fakeLCAAddress', + account: `published.basicFlows.${expectedAddress}`, }); t.like(wd.getLatestUpdateRecord(), { status: { id: 'request-loa', numWantsSatisfied: 1 }, @@ -487,6 +489,8 @@ test.serial('basic-flows', async t => { await runInbound( BridgeId.VTRANSFER, buildVTransferEvent({ + sender: expectedAddress, + target: expectedAddress, sourceChannel: 'channel-62', sequence: '1', }), @@ -598,7 +602,8 @@ test.serial('basic-flows - portfolio holder', async t => { [ 'request-portfolio-acct', { - agoric: 'published.basicFlows.agoric1fakeLCAAddress1', + agoric: + 'published.basicFlows.agoric1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc09z0g', cosmoshub: 'published.basicFlows.cosmos1test2', osmosis: 'published.basicFlows.cosmos1test3', }, @@ -615,7 +620,10 @@ test.serial('basic-flows - portfolio holder', async t => { remoteAddress: '/ibc-hop/connection-1/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test3","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4', }); - t.is(readPublished('basicFlows.agoric1fakeLCAAddress1'), ''); + t.is( + readPublished('basicFlows.agoric1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc09z0g'), + '', + ); const { BLD } = agoricNamesRemotes.brand; BLD || Fail`BLD missing from agoricNames`; 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 eae055ef42a..ace588bbdf2 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 @@ -132,19 +132,19 @@ Generated by [AVA](https://avajs.dev). [ 'published.fastUsdc', { - poolAccount: 'agoric1fakeLCAAddress', - settlementAccount: 'agoric1fakeLCAAddress1', + poolAccount: 'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht', + settlementAccount: 'agoric1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc09z0g', }, ], [ - 'published.fastUsdc.agoric1fakeLCAAddress', + 'published.fastUsdc.agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht', { body: '#""', slots: [], }, ], [ - 'published.fastUsdc.agoric1fakeLCAAddress1', + 'published.fastUsdc.agoric1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc09z0g', { body: '#""', slots: [], 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 c837074b0fac8a8353a3bc2e4a0b8b6253eecf4f..0098b4788bfb2c84ca53df4b055872bf5e179aab 100644 GIT binary patch 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( literal 2209 zcmV;S2wwL=RzV2`E6f!>t&1v`;>In zK(`2KKkePS7i0p2fJ_qdfwq&*0IUJ{O3YD6}n&5^l`6?2z{q58Ae@U*v1de}W0Y{0a+tV_VCMi)qJ7uPhhs{8HA+Sb0XvS?gpzpU2hDR>aQ`6mYBEspG&^O0yEY5%u=(a ztTHpz=W9!uK6jYu3e#a8tAw=*J12x!n9l<0nbdbomCu~0Guth@cC*FYu;F;1u&1by z=A{cOMb(1y6Bnz@WSg{B_n28?9^Wus&ua)7(6CKI4`EkXjheiyeuYhGcJd8}0yaM} zae*@|rF`iw0b8S(*WpME@57-Xg%e;=LQAgCxaJ_ICP??K$=?5KefnW9p>|z>r=@CWi<6|<1W8Z zV?oo@@qRo4NR$HVJF&J+tW&SS)GxA|B&>8gosRf_cr^dcDq(;HUavmQ518XGC zza;8KJ+sDS;M!ua1SBrsGYR}V)y&0u`$tCqp@F3tDLRk+*h;&9-i!Hf1#aI(^HUgPakaL`S z9rIR{ik101tJ5u^s(f1mzNG;_8m=s_Yrq>CpvS3gtOt5P2OideN8`|m9_Z6L@ChB* zh(mkn>^U9yT8m!?(OHt{ztVx<>cHRP(5W4mZ_kJn5~ZJIV}MpT#t^Oh!d=Y#rMvKb z{w{oPj_#Z6k;5GcH%atw8o-MN@Z%xcm?Zk24B#&YfW+Wk%rr=%-~Y~{4kkuck&f@V z(S4IW+p`Bd1qu82LdX)LTZa-Y?Rj;m_C)w@2lWXx=F)|tdB*)@!*yJ_6;5ihO@4o9 z&ciLup|;H%zBFm`NXUi=&sa4gJc&Gj4+8iQfC~UVK1_rRB@S*gz$78hwq#hZ^@24M1EJ^d3?+K(lu%7U^ z6eBpNM|ai^YKN|+JZ@93m2K)+YQZhpb9Elb1=ptPD9$~P1#{K{51eoxws37Vwax}XoST1=&^+aIGc zG2iBn>sOSm9&pJQ)wtZexK(FrR5)u_Y7I}iS8AfNn!2u**SbDjXkQ~*so7M(YFx5d z9jd&((3N;sb;1SwUsvV2`m)aS`LdE-6gk$-rCqjN18ln^wx$$sk(Y*P@j(4uDVpu; zqxlKbG|jVIvSVf+Ui)aXkqfph!XIJ%G!76?omH<|7-ftUehvJ)$N&th)45HWxK6j?9J5b1f;KTXW_7 zRnc7T{jk>vYIKbWvmqSYq|SPHXzSfrklZ~x(cT&PMEi7~_a`@fF`wSF^H#~uS?O#k zm!_G;qxO=WO;g)i%3C>BvYl);XE}?xoKw!1%8MnIT`rdw^Gn%mW+`Var86qWKXgvR zT9N>JhM#;%jHbtgq_WYuvENl#xm-9~Jbm`D){gZbLwBs { await runInbound( BridgeId.VTRANSFER, buildVTransferEvent({ + sender: makeTestAddress(), + target: makeTestAddress(), sourceChannel: 'channel-5', sequence: '1', }), diff --git a/packages/boot/test/orchestration/restart-contracts.test.ts b/packages/boot/test/orchestration/restart-contracts.test.ts index b727b8a74be..bf5d22efd07 100644 --- a/packages/boot/test/orchestration/restart-contracts.test.ts +++ b/packages/boot/test/orchestration/restart-contracts.test.ts @@ -10,6 +10,7 @@ import { import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; import type { UpdateRecord } from '@agoric/smart-wallet/src/smartWallet.js'; import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; +import { makeTestAddress } from '@agoric/orchestration/tools/make-test-address.js'; import { makeWalletFactoryContext, type WalletFactoryTestContext, @@ -101,6 +102,8 @@ test.serial('send-anywhere', async t => { await runInbound( BridgeId.VTRANSFER, buildVTransferEvent({ + sender: makeTestAddress(), + target: makeTestAddress(), sourceChannel: 'channel-5', sequence: '1', }), diff --git a/packages/boot/tools/supports.ts b/packages/boot/tools/supports.ts index 77e1cdc61ca..8f17cb2be16 100644 --- a/packages/boot/tools/supports.ts +++ b/packages/boot/tools/supports.ts @@ -7,6 +7,7 @@ import { resolve as importMetaResolve } from 'import-meta-resolve'; import { basename, join } from 'path'; import { inspect } from 'util'; +import { makeTestAddress } from '@agoric/orchestration/tools/make-test-address.js'; import { buildSwingset } from '@agoric/cosmic-swingset/src/launch-chain.js'; import type { TypedPublished } from '@agoric/client-utils'; import { @@ -538,7 +539,7 @@ export const makeSwingsetTestKit = async ( return undefined; } case `${BridgeId.VLOCALCHAIN}:VLOCALCHAIN_ALLOCATE_ADDRESS`: { - const address = `${LOCALCHAIN_DEFAULT_ADDRESS}${lcaAccountsCreated || ''}`; + const address = makeTestAddress(lcaAccountsCreated); lcaAccountsCreated += 1; return address; } From 5ad59bdd690a4180481c7597aec9cf95e1db661f Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 17 Dec 2024 18:26:57 -0500 Subject: [PATCH 3/4] chore: `makeFakeLocalchainBridge` accepts `makeAddressFn` --- packages/vats/tools/fake-bridge.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/vats/tools/fake-bridge.js b/packages/vats/tools/fake-bridge.js index d8e1585d6a7..067d5dc724f 100644 --- a/packages/vats/tools/fake-bridge.js +++ b/packages/vats/tools/fake-bridge.js @@ -291,9 +291,14 @@ export const fakeLocalChainBridgeQueryHandler = message => { /** * @param {import('@agoric/zone').Zone} zone * @param {(obj) => void} [onToBridge] + * @param {(number) => string} makeAddressFn * @returns {ScopedBridgeManager<'vlocalchain'>} */ -export const makeFakeLocalchainBridge = (zone, onToBridge = () => {}) => { +export const makeFakeLocalchainBridge = ( + zone, + onToBridge = () => {}, + makeAddressFn = index => `${LOCALCHAIN_DEFAULT_ADDRESS}${index || ''}`, +) => { /** @type {Remote} */ let hndlr; let lcaExecuteTxSequence = 0; @@ -306,7 +311,7 @@ export const makeFakeLocalchainBridge = (zone, onToBridge = () => {}) => { trace('toBridge', type, method, params); switch (type) { case 'VLOCALCHAIN_ALLOCATE_ADDRESS': { - const address = `${LOCALCHAIN_DEFAULT_ADDRESS}${accountsCreated || ''}`; + const address = makeAddressFn(accountsCreated); accountsCreated += 1; return address; } From e06f12a37d7070c25f866a4ca59089f180e08c04 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 17 Dec 2024 18:29:49 -0500 Subject: [PATCH 4/4] chore: `advancer` validates `settlementAddress` --- .../boot/test/fast-usdc/fast-usdc.test.ts | 11 ++++- packages/fast-usdc/src/exos/advancer.js | 11 ++++- packages/fast-usdc/src/fast-usdc.contract.js | 15 +++--- packages/fast-usdc/test/cli/transfer.test.ts | 7 ++- packages/fast-usdc/test/exos/advancer.test.ts | 47 +++++++++++++++++-- .../fast-usdc/test/fast-usdc.contract.test.ts | 40 ++++++++++------ packages/fast-usdc/test/fixtures.ts | 32 +++++++------ packages/fast-usdc/test/supports.ts | 7 ++- 8 files changed, 117 insertions(+), 53 deletions(-) diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index f581f665a7f..38b2268e2c4 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -1,6 +1,7 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import type { TestFn } from 'ava'; +import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { configurations } from '@agoric/fast-usdc/src/utils/deploy-config.js'; import { MockCctpTxEvidences } from '@agoric/fast-usdc/test/fixtures.js'; import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; @@ -12,7 +13,7 @@ import { defaultSerializer, } from '@agoric/internal/src/storage-test-utils.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; -import { BridgeId } from '@agoric/internal'; +import { BridgeId, NonNullish } from '@agoric/internal'; import { makeWalletFactoryContext, type WalletFactoryTestContext, @@ -261,7 +262,13 @@ test.serial('makes usdc advance', async t => { 'FastLP balance not in wallet record', ); - const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); + const EUD = 'dydx1anything'; + const lastNodeValue = storage.getValues('published.fastUsdc').at(-1); + const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue)); + const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO( + // mock with the read settlementAccount address + encodeAddressHook(settlementAccount, { EUD }), + ); harness?.useRunPolicy(true); await Promise.all( diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index f63c2b7474c..a96c781f140 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -6,6 +6,7 @@ import { pickFacet } from '@agoric/vat-data'; import { VowShape } from '@agoric/vow'; import { E } from '@endo/far'; import { M, mustMatch } from '@endo/patterns'; +import { Fail, q } from '@endo/errors'; import { CctpTxEvidenceShape, AddressHookShape, @@ -116,6 +117,7 @@ export const prepareAdvancerKit = ( * notifyFacet: import('./settler.js').SettlerKit['notify']; * borrowerFacet: LiquidityPoolKit['borrower']; * poolAccount: HostInterface>; + * settlementAddress: ChainAddress; * intermediateRecipient?: ChainAddress; * }} config */ @@ -145,10 +147,14 @@ export const prepareAdvancerKit = ( return; } - const { borrowerFacet, poolAccount } = this.state; + const { borrowerFacet, poolAccount, settlementAddress } = + this.state; const { recipientAddress } = evidence.aux; const decoded = decodeAddressHook(recipientAddress); mustMatch(decoded, AddressHookShape); + if (decoded.baseAddress !== settlementAddress.value) { + throw Fail`⚠️ baseAddress of address hook ${q(decoded.baseAddress)} does not match the expected address ${q(settlementAddress.value)}`; + } const { EUD } = /** @type {AddressHook['query']} */ (decoded.query); log(`decoded EUD: ${EUD}`); // throws if the bech32 prefix is not found @@ -172,10 +178,10 @@ export const prepareAdvancerKit = ( harden({ USDC: advanceAmount }), ); void watch(depositV, this.facets.depositHandler, { - fullAmount, advanceAmount, destination, forwardingAddress: evidence.tx.forwardingAddress, + fullAmount, tmpSeat, txHash: evidence.txHash, }); @@ -271,6 +277,7 @@ export const prepareAdvancerKit = ( borrowerFacet: M.remotable(), poolAccount: M.remotable(), intermediateRecipient: M.opt(ChainAddressShape), + settlementAddress: M.opt(ChainAddressShape), }), }, ); diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index b9629bb05c3..cb3ed9bd8e7 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -179,16 +179,12 @@ export const contract = async (zcf, privateArgs, zone, tools) => { }, async publishAddresses() { !baggage.has(ADDRESSES_BAGGAGE_KEY) || Fail`Addresses already published`; - const [poolAccountAddress, settlementAccountAddress] = - await vowTools.when( - vowTools.all([ - E(poolAccount).getAddress(), - E(settlementAccount).getAddress(), - ]), - ); + const [poolAccountAddress] = await vowTools.when( + vowTools.all([E(poolAccount).getAddress()]), + ); const addresses = harden({ poolAccount: poolAccountAddress.value, - settlementAccount: settlementAccountAddress.value, + settlementAccount: settlementAddress.value, }); baggage.init(ADDRESSES_BAGGAGE_KEY, addresses); await publishAddresses(storageNode, addresses); @@ -284,6 +280,8 @@ export const contract = async (zcf, privateArgs, zone, tools) => { ); trace('settlementAccount', settlementAccount); trace('poolAccount', poolAccount); + const settlementAddress = await E(settlementAccount).getAddress(); + trace('settlementAddress', settlementAddress); const [_agoric, _noble, agToNoble] = await vowTools.when( chainHub.getChainsAndConnection('agoric', 'noble'), @@ -300,6 +298,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { borrowerFacet: poolKit.borrower, notifyFacet: settlerKit.notify, poolAccount, + settlementAddress, }), ); // Connect evidence stream to advancer diff --git a/packages/fast-usdc/test/cli/transfer.test.ts b/packages/fast-usdc/test/cli/transfer.test.ts index 1f75734d5ae..bdac4d4117a 100644 --- a/packages/fast-usdc/test/cli/transfer.test.ts +++ b/packages/fast-usdc/test/cli/transfer.test.ts @@ -8,6 +8,7 @@ import { makeFetchMock, makeMockSigner, } from '../../testing/mocks.js'; +import { settlementAddress } from '../fixtures.js'; test('Errors if config missing', async t => { const path = 'config/dir/.fast-usdc/config.json'; @@ -73,8 +74,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 = - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek'; + const agoricSettlementAccount = settlementAddress.value; const settlementAccountVstoragePath = 'published.fastUsdc.settlementAccount'; const vstorageMock = makeVstorageMock({ [settlementAccountVstoragePath]: agoricSettlementAccount, @@ -150,8 +150,7 @@ test('Transfer signs and broadcasts the depositForBurn message on Ethereum', asy }; const out = mockOut(); const file = mockFile(path, JSON.stringify(config)); - const agoricSettlementAccount = - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek'; + const agoricSettlementAccount = settlementAddress.value; const settlementAccountVstoragePath = 'published.fastUsdc.settlementAccount'; const vstorageMock = makeVstorageMock({ [settlementAccountVstoragePath]: agoricSettlementAccount, diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index 4bb122220df..1fba00c8fe3 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -19,7 +19,11 @@ import type { SettlerKit } from '../../src/exos/settler.js'; import { prepareStatusManager } from '../../src/exos/status-manager.js'; import type { LiquidityPoolKit } from '../../src/types.js'; import { makeFeeTools } from '../../src/utils/fees.js'; -import { MockCctpTxEvidences, intermediateRecipient } from '../fixtures.js'; +import { + MockCctpTxEvidences, + settlementAddress, + intermediateRecipient, +} from '../fixtures.js'; import { makeTestFeeConfig, makeTestLogger, @@ -127,6 +131,7 @@ const createTestExtensions = (t, common: CommonSetup) => { notifyFacet: mockNotifyF, poolAccount: mockAccounts.mockPoolAccount.account, intermediateRecipient, + settlementAddress, }); return { @@ -141,6 +146,7 @@ const createTestExtensions = (t, common: CommonSetup) => { }, mocks: { ...mockAccounts, + mockBorrowerF, mockNotifyF, resolveLocalTransferV, rejectLocalTransfeferV, @@ -260,6 +266,7 @@ test('updates status to OBSERVED on insufficient pool funds', async t => { notifyFacet: mockNotifyF, poolAccount: mockPoolAccount.account, intermediateRecipient, + settlementAddress, }); const evidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX(); @@ -393,10 +400,10 @@ test('updates status to OBSERVED if pre-condition checks fail', async t => { await advancer.handleTransactionEvent({ ...MockCctpTxEvidences.AGORIC_NO_PARAMS( - encodeAddressHook( - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', - { EUD: 'osmo1234', extra: 'value' }, - ), + encodeAddressHook(settlementAddress.value, { + EUD: 'osmo1234', + extra: 'value', + }), ), txHash: '0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799', @@ -550,6 +557,7 @@ test('alerts if `returnToPool` fallback fails', async t => { notifyFacet: mockNotifyF, poolAccount: mockPoolAccount.account, intermediateRecipient, + settlementAddress, }); const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -588,3 +596,32 @@ test('alerts if `returnToPool` fallback fails', async t => { 'Advancing tx is recorded as AdvanceFailed', ); }); + +test('rejects advances to unknown settlementAccount', async t => { + const { + extensions: { + services: { advancer }, + helpers: { inspectLogs }, + }, + } = t.context; + + const invalidSettlementAcct = + 'agoric1ax7hmw49tmqrdld7emc5xw3wf43a49rtkacr9d5nfpqa0y7k6n0sl8v94h'; + t.not(settlementAddress.value, invalidSettlementAcct); + const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO( + encodeAddressHook(invalidSettlementAcct, { + EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + }), + ); + + void advancer.handleTransactionEvent(mockEvidence); + await eventLoopIteration(); + t.deepEqual(inspectLogs(), [ + [ + 'Advancer error:', + Error( + '⚠️ baseAddress of address hook "agoric1ax7hmw49tmqrdld7emc5xw3wf43a49rtkacr9d5nfpqa0y7k6n0sl8v94h" does not match the expected address "agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek"', + ), + ], + ]); +}); diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index bc73389a5b9..6187f5ddc69 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -6,6 +6,7 @@ import { encodeAddressHook, } from '@agoric/cosmic-proto/address-hooks.js'; import { AmountMath } from '@agoric/ertp/src/amountMath.js'; +import type { makeFakeStorageKit } from '@agoric/internal/src/storage-test-utils.js'; import { eventLoopIteration, inspectMapStore, @@ -18,6 +19,7 @@ import { } from '@agoric/notifier'; import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; +import { makeTestAddress } from '@agoric/orchestration/tools/make-test-address.js'; import { heapVowE as VE } from '@agoric/vow/vat.js'; import { divideBy, @@ -136,12 +138,15 @@ const makeTestContext = async (t: ExecutionContext) => { }; const mint = async (e: CctpTxEvidence) => { - const settlerAddr = 'agoric1fakeLCAAddress1'; // TODO: get from contract - const rxd = await receiveUSDCAt(settlerAddr, e.tx.amount); + const accountsData = common.bootstrap.storage.data.get('fun'); + const { settlementAccount } = JSON.parse( + JSON.parse(accountsData!).values[0], + ); + const rxd = await receiveUSDCAt(settlementAccount, e.tx.amount); await VE(transferBridge).fromBridge( buildVTransferEvent({ receiver: e.aux.recipientAddress, - target: settlerAddr, + target: settlementAccount, sourceChannel: agToNoble.transferChannel.counterPartyChannelId, denom: 'uusdc', amount: e.tx.amount, @@ -155,7 +160,8 @@ const makeTestContext = async (t: ExecutionContext) => { return { bridges: { snapshot, since }, common, evm, mint, startKit, sync }; }; -const test = anyTest as TestFn>>; +type FucContext = Awaited>; +const test = anyTest as TestFn; test.before(async t => (t.context = await makeTestContext(t))); test('baggage', async t => { @@ -190,8 +196,8 @@ test('getStaticInfo', async t => { t.deepEqual(await E(publicFacet).getStaticInfo(), { addresses: { - poolAccount: 'agoric1fakeLCAAddress', - settlementAccount: 'agoric1fakeLCAAddress1', + poolAccount: makeTestAddress(), + settlementAccount: makeTestAddress(1), }, }); }); @@ -346,7 +352,6 @@ const makeLP = async ( }; const makeEVM = (template = MockCctpTxEvidences.AGORIC_PLUS_OSMO()) => { - const [settleAddr] = template.aux.recipientAddress.split('?'); let nonce = 0; const makeTx = (amount: bigint, recipientAddress: string): CctpTxEvidence => { @@ -378,10 +383,6 @@ const makeCustomer = ( const feeTools = makeFeeTools(feeConfig); const sent = [] as CctpTxEvidence[]; - // TODO: get settlerAddr from vstorage - const [settleAddr] = - MockCctpTxEvidences.AGORIC_PLUS_OSMO().aux.recipientAddress.split('?'); - const me = harden({ checkPoolAvailable: async ( t: ExecutionContext, @@ -394,8 +395,17 @@ const makeCustomer = ( t.log(who, 'sees', poolBalance.value, enough ? '>' : 'NOT >', want); return enough; }, - sendFast: async (t: ExecutionContext, amount: bigint, EUD: string) => { - const recipientAddress = encodeAddressHook(settleAddr, { EUD }); + sendFast: async ( + t: ExecutionContext, + amount: bigint, + EUD: string, + ) => { + const { storage } = t.context.common.bootstrap; + const accountsData = storage.data.get('fun'); + const { settlementAccount } = JSON.parse( + JSON.parse(accountsData!).values[0], + ); + const recipientAddress = encodeAddressHook(settlementAccount, { EUD }); // KLUDGE: UI would ask noble for a forwardingAddress // "cctp" here has some noble stuff mixed in. const tx = cctp.makeTx(amount, recipientAddress); @@ -559,7 +569,7 @@ test.serial('STORY01: advancing happy path for 100 USDC', async t => { t.deepEqual(inspectBankBridge().at(-1), { amount: String(expectedAdvance.value), denom: uusdcOnAgoric, - recipient: 'agoric1fakeLCAAddress', + recipient: makeTestAddress(), type: 'VBANK_GIVE', }); @@ -791,7 +801,7 @@ test.serial('Settlement for unknown transaction (operator down)', async t => { }, { amount: '20000000', - recipient: 'agoric1fakeLCAAddress1', + recipient: 'agoric1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc09z0g', type: 'VBANK_GIVE', }, ], diff --git a/packages/fast-usdc/test/fixtures.ts b/packages/fast-usdc/test/fixtures.ts index 35ec0c231e2..8496051125d 100644 --- a/packages/fast-usdc/test/fixtures.ts +++ b/packages/fast-usdc/test/fixtures.ts @@ -37,10 +37,9 @@ export const MockCctpTxEvidences: Record< forwardingChannel: 'channel-21', recipientAddress: receiverAddress || - encodeAddressHook( - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', - { EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men' }, - ), + encodeAddressHook(settlementAddress.value, { + EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + }), }, chainId: 1, }), @@ -59,10 +58,9 @@ export const MockCctpTxEvidences: Record< forwardingChannel: 'channel-21', recipientAddress: receiverAddress || - encodeAddressHook( - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', - { EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men' }, - ), + encodeAddressHook(settlementAddress.value, { + EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + }), }, chainId: 1, }), @@ -79,9 +77,7 @@ export const MockCctpTxEvidences: Record< }, aux: { forwardingChannel: 'channel-21', - recipientAddress: - receiverAddress || - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + recipientAddress: receiverAddress || settlementAddress.value, }, chainId: 1, }), @@ -100,10 +96,9 @@ export const MockCctpTxEvidences: Record< forwardingChannel: 'channel-21', recipientAddress: receiverAddress || - encodeAddressHook( - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', - { EUD: 'random1addr' }, - ), + encodeAddressHook(settlementAddress.value, { + EUD: 'random1addr', + }), }, chainId: 1, }), @@ -166,3 +161,10 @@ export const intermediateRecipient: ChainAddress = harden({ value: 'noble1test', encoding: 'bech32', }); + +export const settlementAddress: ChainAddress = harden({ + chainId: 'agoric-3', + encoding: 'bech32' as const, + // Random value, copied from tests of address hooks + value: 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', +}); diff --git a/packages/fast-usdc/test/supports.ts b/packages/fast-usdc/test/supports.ts index 984352b3c07..6a4608a0cf5 100644 --- a/packages/fast-usdc/test/supports.ts +++ b/packages/fast-usdc/test/supports.ts @@ -20,6 +20,7 @@ import { prepareCosmosInterchainService } from '@agoric/orchestration/src/exos/c import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; import { setupFakeNetwork } from '@agoric/orchestration/test/network-fakes.js'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; +import { makeTestAddress } from '@agoric/orchestration/tools/make-test-address.js'; import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; import { makeNameHubKit } from '@agoric/vats'; import { prepareBridgeTargetModule } from '@agoric/vats/src/bridge-target.js'; @@ -139,8 +140,10 @@ export const commonSetup = async (t: ExecutionContext) => { await E(transferBridge).initHandler(bridgeTargetKit.bridgeHandler); const localBridgeMessages = [] as any[]; - const localchainBridge = makeFakeLocalchainBridge(rootZone, obj => - localBridgeMessages.push(obj), + const localchainBridge = makeFakeLocalchainBridge( + rootZone, + obj => localBridgeMessages.push(obj), + makeTestAddress, ); const localchain = prepareLocalChainTools( rootZone.subZone('localchain'),