From 9f57fd8986e47e649a45926c57d49a35fb47afe6 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 10:49:09 -0700 Subject: [PATCH 1/9] chore(types): BridgeHandler is Remote --- packages/vats/src/types.d.ts | 8 ++++---- packages/vats/test/vat-bank.test.js | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/vats/src/types.d.ts b/packages/vats/src/types.d.ts index f364d8f0bd7..6369ae6a5b7 100644 --- a/packages/vats/src/types.d.ts +++ b/packages/vats/src/types.d.ts @@ -1,5 +1,5 @@ import type { Bytes } from '@agoric/network'; -import type { PromiseVow } from '@agoric/vow'; +import type { PromiseVow, Remote } from '@agoric/vow'; import type { Guarded } from '@endo/exo'; import type { ERef } from '@endo/far'; import type { BridgeIdValue } from '@agoric/internal'; @@ -106,15 +106,15 @@ export type ScopedBridgeManager = Guarded<{ getBridgeId?: () => BridgeId; toBridge: (obj: any) => Promise; fromBridge: (obj: any) => PromiseVow; - initHandler: (handler: ERef) => void; - setHandler: (handler: ERef) => void; + initHandler: (handler: Remote) => void; + setHandler: (handler: Remote) => void; }>; /** The object to manage this bridge */ export type BridgeManager = { register: ( bridgeId: BridgeId, - handler?: ERef, + handler?: Remote, ) => ScopedBridgeManager; }; diff --git a/packages/vats/test/vat-bank.test.js b/packages/vats/test/vat-bank.test.js index c57ff95211e..93a86130ade 100644 --- a/packages/vats/test/vat-bank.test.js +++ b/packages/vats/test/vat-bank.test.js @@ -8,6 +8,11 @@ import { makeHeapZone } from '@agoric/zone'; import { subscribeEach } from '@agoric/notifier'; import { buildRootObject } from '../src/vat-bank.js'; +/** + * @import {Remote} from '@agoric/vow'; + * @import {BridgeHandler, ScopedBridgeManager} from '../src/types.js'; + */ + const { fakeVomKit } = reincarnate({ relaxDurabilityRules: false }); const provideBaggage = key => { const root = fakeVomKit.cm.provideBaggage(); @@ -58,10 +63,10 @@ test('communication', async t => { const zone = makeDurableZone(baggage); - /** @type {undefined | ERef} */ + /** @type {undefined | Remote} */ let bankHandler; - /** @type {import('../src/types.js').ScopedBridgeManager<'bank'>} */ + /** @type {ScopedBridgeManager<'bank'>} */ const bankBridgeMgr = zone.exo('fakeBankBridgeManager', undefined, { async fromBridge(obj) { t.is(typeof obj, 'string'); From dfd77dc6ed9855db7390438e1f8bf8d8a89b0b9a Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 10:48:34 -0700 Subject: [PATCH 2/9] refactor(bank): re-use manager in scope --- packages/vats/src/vat-bank.js | 19 ++++++++++--------- packages/vats/test/vat-bank.test.js | 5 ++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vats/src/vat-bank.js b/packages/vats/src/vat-bank.js index 023233746c9..acca72249a6 100644 --- a/packages/vats/src/vat-bank.js +++ b/packages/vats/src/vat-bank.js @@ -52,8 +52,8 @@ const BalanceUpdaterI = M.interface('BalanceUpdater', { /** * @typedef {Pick< - * import('./types.js').ScopedBridgeManager, - * 'fromBridge' | 'toBridge' + * import('./types.js').ScopedBridgeManager<'bank'>, + * 'getBridgeId' | 'fromBridge' | 'toBridge' * >} BridgeChannel */ @@ -199,7 +199,6 @@ const prepareBankChannelHandler = zone => if (addressToUpdater.has(address)) { updater = addressToUpdater.get(address); } - // console.info('bank balance update', update); } catch (e) { console.error('Unregistered denom in', update, e); } @@ -882,25 +881,27 @@ export function buildRootObject(_vatPowers, _args, baggage) { 'denomToAddressUpdater', ); - /** @param {ERef>} [bankBridgeMgr] */ - async function getBankChannel(bankBridgeMgr) { + async function getBankChannel() { // We do the logic here if the bridge manager is available. Otherwise, // the bank is not "remote" (such as on sim-chain), so we just use // immediate purses instead of virtual ones. - if (!bankBridgeMgr) { + if (!bankBridgeManager) { + console.warn( + 'no bank bridge manager, using immediate purses instead of virtual ones', + ); return undefined; } // We need to synchronise with the remote bank. const handler = makeBankChannelHandler(denomToAddressUpdater); - await E(bankBridgeMgr).initHandler(handler); + await E(bankBridgeManager).initHandler(handler); // We can only downcall to the bank if there exists a bridge manager. - return makeBridgeChannelAttenuator({ target: bankBridgeMgr }); + return makeBridgeChannelAttenuator({ target: bankBridgeManager }); } const [bankChannel, nameAdmin] = await Promise.all([ - getBankChannel(bankBridgeManager), + getBankChannel(), nameAdminP, ]); return makeBankManager({ diff --git a/packages/vats/test/vat-bank.test.js b/packages/vats/test/vat-bank.test.js index 93a86130ade..aff6065e622 100644 --- a/packages/vats/test/vat-bank.test.js +++ b/packages/vats/test/vat-bank.test.js @@ -57,7 +57,7 @@ test('provideAssetSubscription - MapStore insertion order preserved', async t => }); test('communication', async t => { - t.plan(32); + t.plan(31); const baggage = provideBaggage('communication'); const bankVat = E(buildRootObject)(null, null, baggage); @@ -201,8 +201,7 @@ test('communication', async t => { const updateRecord = await E(notifier).getUpdateSince(); const balance = { address: 'agoricfoo', denom: 'ubld', amount: '92929' }; const obj = { type: 'VBANK_BALANCE_UPDATE', updated: [balance] }; - t.assert(bankHandler); - // @ts-expect-error banHandler does not resolve to undefined + assert(bankHandler); await E(bankHandler).fromBridge(obj); // Wait for new balance. From 9abd0931182993545504f9f264440052128756e8 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 10:58:08 -0700 Subject: [PATCH 3/9] feat: fake bank bridge --- packages/vats/test/fake-bridge.test.js | 49 +++++++++++ packages/vats/tools/fake-bridge.js | 114 ++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 packages/vats/test/fake-bridge.test.js diff --git a/packages/vats/test/fake-bridge.test.js b/packages/vats/test/fake-bridge.test.js new file mode 100644 index 00000000000..63329d1459d --- /dev/null +++ b/packages/vats/test/fake-bridge.test.js @@ -0,0 +1,49 @@ +import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; + +import { makeIssuerKit } from '@agoric/ertp'; +import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import { makeHeapZone } from '@agoric/zone'; +import { E } from '@endo/far'; +import { buildRootObject as buildBankVatRoot } from '../src/vat-bank.js'; +import { makeFakeBankBridge } from '../tools/fake-bridge.js'; + +const issuerKit = makeIssuerKit('IST'); +const stable = withAmountUtils(issuerKit); +const ten = stable.units(10); + +test('balances', async t => { + const zone = makeHeapZone(); + + const bankBridge = makeFakeBankBridge(zone, { + balances: { + faucet: { uist: 999999999n }, + agoric1fakeBridgeAddress: { uist: ten.value }, + }, + }); + + const bankManager = await buildBankVatRoot( + undefined, + undefined, + zone.mapStore('bankManager'), + ).makeBankManager(bankBridge); + + await E(bankManager).addAsset('uist', 'IST', 'Inter Stable Token', issuerKit); + + const faucet = await E(bankManager).getBankForAddress('faucet'); + const tenIst = await E(E(faucet).getPurse(issuerKit.brand)).withdraw(ten); + + const bank = await E(bankManager).getBankForAddress( + 'agoric1fakeBridgeAddress', + ); + + const istPurse = await E(bank).getPurse(issuerKit.brand); + t.like(await E(istPurse).getCurrentAmount(), ten); + + await E(istPurse).deposit(tenIst); + await E(istPurse).withdraw(ten); + + t.like(await E(istPurse).getCurrentAmount(), ten); + await E(istPurse).withdraw(ten); + + t.like(await E(istPurse).getCurrentAmount(), stable.makeEmpty()); +}); diff --git a/packages/vats/tools/fake-bridge.js b/packages/vats/tools/fake-bridge.js index e191e72c880..7c1c2da5a1b 100644 --- a/packages/vats/tools/fake-bridge.js +++ b/packages/vats/tools/fake-bridge.js @@ -1,10 +1,116 @@ import { Fail } from '@agoric/assert'; +import { makeTracer, VBankAccount } from '@agoric/internal'; import assert from 'node:assert/strict'; +import { E } from '@endo/far'; /** * @import {MsgDelegateResponse} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; - * @import {ScopedBridgeManager} from '../src/types.js'; + * @import {BridgeHandler, ScopedBridgeManager} from '../src/types.js'; + * @import {Remote} from '@agoric/vow'; */ +const trace = makeTracer('FakeBridge', false); + +/** @typedef {{ [address: string]: { [denom: string]: bigint } }} Balances */ + +export const FAUCET_ADDRESS = 'faucet'; +const INFINITE_AMOUNT = 99999999999n; + +/** + * You can withdraw from the `faucet` address infinitely because its balance is + * always huge. When you withdraw, it's as if it is topped up again by a Cosmos + * transaction outside the Agoric VM. (Similarly for deposits.) + * + * @param {import('@agoric/zone').Zone} zone + * @param {object} opts + * @param {Balances} opts.balances initial balances + * @returns {ScopedBridgeManager<'bank'>} + */ +export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { + const { balances } = opts; + + const currentBalance = ({ address, denom }) => + address === FAUCET_ADDRESS + ? INFINITE_AMOUNT + : Nat((balances[address] && balances[address][denom]) ?? 0n); + + let lastNonce = 0n; + /** @type {Remote} */ + let hndlr; + return zone.exo('Fake Bank Bridge Manager', undefined, { + getBridgeId: () => 'bank', + toBridge: async obj => { + const { method, type, ...params } = obj; + trace('toBridge', type, method, params); + switch (obj.type) { + case 'VBANK_GET_MODULE_ACCOUNT_ADDRESS': { + const { moduleName } = obj; + const moduleDescriptor = Object.values(VBankAccount).find( + ({ module }) => module === moduleName, + ); + if (!moduleDescriptor) { + return 'undefined'; + } + return moduleDescriptor.address; + } + + // Observed message: + // address: 'agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346', + // denom: 'ibc/toyatom', + // type: 'VBANK_GET_BALANCE' + case 'VBANK_GET_BALANCE': { + return String(currentBalance(obj)); + } + + case 'VBANK_GRAB': + case 'VBANK_GIVE': { + const { amount, denom } = obj; + const address = type === 'VBANK_GRAB' ? obj.sender : obj.recipient; + balances[address] ||= {}; + balances[address][denom] ||= 0n; + + if (type === 'VBANK_GRAB') { + balances[address][denom] = Nat( + currentBalance({ address, denom }) - BigInt(amount), + ); + } else { + balances[address][denom] = Nat( + currentBalance({ address, denom }) + BigInt(amount), + ); + } + + lastNonce += 1n; + // Also empty balances. + return harden({ + type: 'VBANK_BALANCE_UPDATE', + nonce: `${lastNonce}`, + updated: [ + { + address, + denom, + amount: String(currentBalance({ address, denom })), + }, + ], + }); + } + default: + Fail`unknown type ${type}`; + } + }, + fromBridge: async obj => { + if (!hndlr) throw Error('no handler!'); + await E(hndlr).fromBridge(obj); + return E(hndlr).fromBridge(obj); + }, + initHandler: h => { + if (hndlr) throw Error('already init'); + hndlr = h; + }, + setHandler: h => { + if (!hndlr) throw Error('must init first'); + hndlr = h; + }, + }); +}; /** * @param {import('@agoric/zone').Zone} zone @@ -13,6 +119,7 @@ import assert from 'node:assert/strict'; * @returns {ScopedBridgeManager<'dibc'>} */ export const makeFakeIbcBridge = (zone, onToBridge, onFromBridge) => { + /** @type {Remote} */ let hndlr; return zone.exo('Fake IBC Bridge Manager', undefined, { getBridgeId: () => 'dibc', @@ -29,6 +136,7 @@ export const makeFakeIbcBridge = (zone, onToBridge, onFromBridge) => { fromBridge: async obj => { if (!hndlr) throw Error('no handler!'); await onFromBridge(hndlr, obj); + return E(hndlr).fromBridge(obj); }, initHandler: h => { if (hndlr) throw Error('already init'); @@ -52,6 +160,7 @@ export const makeFakeLocalchainBridge = ( onToBridge = () => {}, onFromBridge = () => {}, ) => { + /** @type {Remote} */ let hndlr; let lcaExecuteTxSequence = 0; return zone.exo('Fake Localchain Bridge Manager', undefined, { @@ -59,7 +168,7 @@ export const makeFakeLocalchainBridge = ( toBridge: async obj => { onToBridge(obj); const { method, type, ...params } = obj; - console.info('toBridge', type, method, params); + trace('toBridge', type, method, params); switch (type) { case 'VLOCALCHAIN_ALLOCATE_ADDRESS': return 'agoric1fakeLCAAddress'; @@ -97,6 +206,7 @@ export const makeFakeLocalchainBridge = ( fromBridge: async obj => { if (!hndlr) throw Error('no handler!'); await onFromBridge(hndlr, obj); + return E(hndlr).fromBridge(obj); }, initHandler: h => { if (hndlr) throw Error('already init'); From b71744d42bcb837ccd75cf47ed302a1f865e28c8 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 11:12:39 -0700 Subject: [PATCH 4/9] test: bank balances --- .../test/examples/swapExample.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/test/examples/swapExample.test.ts b/packages/orchestration/test/examples/swapExample.test.ts index 86b364bf441..e4b826f2728 100644 --- a/packages/orchestration/test/examples/swapExample.test.ts +++ b/packages/orchestration/test/examples/swapExample.test.ts @@ -9,6 +9,7 @@ import { makeHeapZone } from '@agoric/zone'; import { prepareLocalChainTools } from '@agoric/vats/src/localchain.js'; import { buildRootObject as buildBankVatRoot } from '@agoric/vats/src/vat-bank.js'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import { makeFakeBankBridge } from '@agoric/vats/tools/fake-bridge.js'; import { makeFakeLocalchainBridge } from '../supports.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -26,11 +27,12 @@ test('start', async t => { const zone = makeHeapZone(); const { makeLocalChain } = prepareLocalChainTools(zone.subZone('localchain')); + const bankManager = await buildBankVatRoot( undefined, undefined, zone.mapStore('bankManager'), - ).makeBankManager(); + ).makeBankManager(makeFakeBankBridge(zone)); await E(bankManager).addAsset('uist', 'IST', 'Inter Stable Token', issuerKit); @@ -67,6 +69,14 @@ test('start', async t => { 'Swap for TIA and stake', ); + const bank = await E(bankManager).getBankForAddress( + 'agoric1fakeBridgeAddress', + ); + + const istPurse = await E(bank).getPurse(issuerKit.brand); + // bank purse is empty + t.like(await E(istPurse).getCurrentAmount(), stable.makeEmpty()); + const ten = stable.units(10); const userSeat = await E(zoe).offer( inv, @@ -83,4 +93,7 @@ test('start', async t => { ); const result = await E(userSeat).getOfferResult(); t.is(result, undefined); + + // bank purse now has the 10 IST + t.like(await E(istPurse).getCurrentAmount(), ten); }); From 89078060abeece36ad23edcdc9067be49e463139 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 12:14:24 -0700 Subject: [PATCH 5/9] fix(fake-bridge): use when to handle vows --- packages/vats/test/network.test.js | 12 ++++-------- packages/vats/tools/fake-bridge.js | 24 ++++++++++-------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/vats/test/network.test.js b/packages/vats/test/network.test.js index 326b9f05bcd..84532ee4c28 100644 --- a/packages/vats/test/network.test.js +++ b/packages/vats/test/network.test.js @@ -93,14 +93,10 @@ test('network - ibc', async t => { const pinnedHistoryTopic = makePinnedHistoryTopic(subscriber); const events = subscribeEach(pinnedHistoryTopic)[Symbol.asyncIterator](); - const ibcBridge = makeFakeIbcBridge( - zone, - obj => { - const { method, type: _, ...params } = obj; - publisher.publish([method, params]); - }, - (target, obj) => when(E(target).fromBridge(obj)), - ); + const ibcBridge = makeFakeIbcBridge(zone, obj => { + const { method, type: _, ...params } = obj; + publisher.publish([method, params]); + }); await registerNetworkProtocols( { network: networkVat, ibc: ibcVat, provisioning: undefined }, diff --git a/packages/vats/tools/fake-bridge.js b/packages/vats/tools/fake-bridge.js index 7c1c2da5a1b..3801d7d4fc5 100644 --- a/packages/vats/tools/fake-bridge.js +++ b/packages/vats/tools/fake-bridge.js @@ -2,6 +2,8 @@ import { Fail } from '@agoric/assert'; import { makeTracer, VBankAccount } from '@agoric/internal'; import assert from 'node:assert/strict'; import { E } from '@endo/far'; +import { makeWhen } from '@agoric/vow/src/when.js'; +import { Nat } from '@endo/nat'; /** * @import {MsgDelegateResponse} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; @@ -10,6 +12,8 @@ import { E } from '@endo/far'; */ const trace = makeTracer('FakeBridge', false); +const when = makeWhen(); + /** @typedef {{ [address: string]: { [denom: string]: bigint } }} Balances */ export const FAUCET_ADDRESS = 'faucet'; @@ -98,8 +102,7 @@ export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { }, fromBridge: async obj => { if (!hndlr) throw Error('no handler!'); - await E(hndlr).fromBridge(obj); - return E(hndlr).fromBridge(obj); + return when(E(hndlr).fromBridge(obj)); }, initHandler: h => { if (hndlr) throw Error('already init'); @@ -107,6 +110,7 @@ export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { }, setHandler: h => { if (!hndlr) throw Error('must init first'); + trace('fromBridge', obj); hndlr = h; }, }); @@ -115,10 +119,9 @@ export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { /** * @param {import('@agoric/zone').Zone} zone * @param {(obj) => void} onToBridge - * @param {(handler, obj) => Promise} onFromBridge * @returns {ScopedBridgeManager<'dibc'>} */ -export const makeFakeIbcBridge = (zone, onToBridge, onFromBridge) => { +export const makeFakeIbcBridge = (zone, onToBridge) => { /** @type {Remote} */ let hndlr; return zone.exo('Fake IBC Bridge Manager', undefined, { @@ -135,8 +138,7 @@ export const makeFakeIbcBridge = (zone, onToBridge, onFromBridge) => { }, fromBridge: async obj => { if (!hndlr) throw Error('no handler!'); - await onFromBridge(hndlr, obj); - return E(hndlr).fromBridge(obj); + return when(E(hndlr).fromBridge(obj)); }, initHandler: h => { if (hndlr) throw Error('already init'); @@ -152,14 +154,9 @@ export const makeFakeIbcBridge = (zone, onToBridge, onFromBridge) => { /** * @param {import('@agoric/zone').Zone} zone * @param {(obj) => void} [onToBridge] - * @param {(handler, obj) => ERef} [onFromBridge] * @returns {ScopedBridgeManager<'vlocalchain'>} */ -export const makeFakeLocalchainBridge = ( - zone, - onToBridge = () => {}, - onFromBridge = () => {}, -) => { +export const makeFakeLocalchainBridge = (zone, onToBridge = () => {}) => { /** @type {Remote} */ let hndlr; let lcaExecuteTxSequence = 0; @@ -205,8 +202,7 @@ export const makeFakeLocalchainBridge = ( }, fromBridge: async obj => { if (!hndlr) throw Error('no handler!'); - await onFromBridge(hndlr, obj); - return E(hndlr).fromBridge(obj); + return when(E(hndlr).fromBridge(obj)); }, initHandler: h => { if (hndlr) throw Error('already init'); From 030caf4c8bc04a921c9ee5bf79a225d9616f7878 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 13:32:57 -0700 Subject: [PATCH 6/9] chore(types): bank assets are 'nat' --- .../src/proposals/addAssetToVault.js | 2 +- .../inter-protocol/src/provisionPoolKit.js | 2 - .../test/examples/stake-bld.contract.test.ts | 1 - packages/smart-wallet/src/smartWallet.js | 1 + packages/vats/src/core/basic-behaviors.js | 4 +- packages/vats/src/core/types-ambient.d.ts | 2 +- packages/vats/src/vat-bank.js | 13 +++--- packages/vats/src/virtual-purse.js | 40 +++++++++++++------ packages/vats/tools/bank-utils.js | 4 +- 9 files changed, 42 insertions(+), 27 deletions(-) diff --git a/packages/inter-protocol/src/proposals/addAssetToVault.js b/packages/inter-protocol/src/proposals/addAssetToVault.js index e7e0109498d..a96bfb81ccb 100644 --- a/packages/inter-protocol/src/proposals/addAssetToVault.js +++ b/packages/inter-protocol/src/proposals/addAssetToVault.js @@ -113,7 +113,7 @@ export const publishInterchainAssetFromBank = async ( }); const brand = await E(issuer).getBrand(); - const kit = { mint, issuer, brand }; + const kit = /** @type {IssuerKit<'nat'>} */ ({ mint, issuer, brand }); await E(E.get(reserveKit).creatorFacet).addIssuer(issuer, keyword); diff --git a/packages/inter-protocol/src/provisionPoolKit.js b/packages/inter-protocol/src/provisionPoolKit.js index cd87e8571df..59b2b0ca424 100644 --- a/packages/inter-protocol/src/provisionPoolKit.js +++ b/packages/inter-protocol/src/provisionPoolKit.js @@ -449,8 +449,6 @@ export const prepareProvisionPoolKit = ( * @param {ERef} opts.storageNode */ const makeProvisionPoolKit = async ({ poolBrand, storageNode }) => { - /** @type {Purse<'nat'>} */ - // @ts-expect-error vbank purse is close enough for our use. const fundPurse = await E(poolBank).getPurse(poolBrand); const metricsNode = await E(storageNode).makeChildNode('metrics'); diff --git a/packages/orchestration/test/examples/stake-bld.contract.test.ts b/packages/orchestration/test/examples/stake-bld.contract.test.ts index f314e486d8c..ca2b057da67 100644 --- a/packages/orchestration/test/examples/stake-bld.contract.test.ts +++ b/packages/orchestration/test/examples/stake-bld.contract.test.ts @@ -96,7 +96,6 @@ test('stakeBld contract - makeAccount, deposit, withdraw', async t => { t.log('withdraw 1 bld from account'); const withdrawResp = await E(account).withdraw(oneHundredStakeAmt); - // @ts-expect-error Argument of type 'Payment' is not assignable to parameter of type 'ERef>'. const withdrawAmt = await stake.issuer.getAmountOf(withdrawResp); t.true(AmountMath.isEqual(withdrawAmt, oneHundredStakeAmt), 'withdraw'); diff --git a/packages/smart-wallet/src/smartWallet.js b/packages/smart-wallet/src/smartWallet.js index f86c66f1ee9..8de3d56e2e8 100644 --- a/packages/smart-wallet/src/smartWallet.js +++ b/packages/smart-wallet/src/smartWallet.js @@ -837,6 +837,7 @@ export const prepareSmartWallet = (baggage, shared) => { // When there is a purse deposit into it if (registry.has(brand)) { const purse = E(bank).getPurse(brand); + // @ts-expect-error narrow assetKind to 'nat' return E(purse).deposit(payment); } else if (invitationBrand === brand) { // @ts-expect-error narrow assetKind to 'set' diff --git a/packages/vats/src/core/basic-behaviors.js b/packages/vats/src/core/basic-behaviors.js index a8592295d6c..17106dc9b81 100644 --- a/packages/vats/src/core/basic-behaviors.js +++ b/packages/vats/src/core/basic-behaviors.js @@ -653,7 +653,9 @@ export const addBankAssets = async ({ }); const bldBrand = await E(bldIssuer).getBrand(); - const bldKit = harden({ mint: bldMint, issuer: bldIssuer, brand: bldBrand }); + const bldKit = /** @type {IssuerKit<'nat'>} */ ( + harden({ mint: bldMint, issuer: bldIssuer, brand: bldBrand }) + ); bldIssuerKit.resolve(bldKit); const assetAdmin = E(agoricNamesAdmin).lookupAdmin('vbankAsset'); diff --git a/packages/vats/src/core/types-ambient.d.ts b/packages/vats/src/core/types-ambient.d.ts index 0cbf6bf7f4e..9a6b4ae680d 100644 --- a/packages/vats/src/core/types-ambient.d.ts +++ b/packages/vats/src/core/types-ambient.d.ts @@ -390,7 +390,7 @@ type ChainBootstrapSpaceT = { GovernanceFacetKit & { label: string } >; /** Used only for testing. Should not appear in any production proposals. */ - testFirstAnchorKit: import('../vat-bank.js').AssetIssuerKit<'nat'>; + testFirstAnchorKit: import('../vat-bank.js').AssetIssuerKit; walletBridgeManager: import('../types.js').ScopedBridgeManager | undefined; walletFactoryStartResult: import('./startWalletFactory.js').WalletFactoryStartResult; provisionPoolStartResult: GovernanceFacetKit< diff --git a/packages/vats/src/vat-bank.js b/packages/vats/src/vat-bank.js index acca72249a6..489df8bb832 100644 --- a/packages/vats/src/vat-bank.js +++ b/packages/vats/src/vat-bank.js @@ -349,11 +349,10 @@ const prepareAssetSubscription = zone => { }; /** - * @template {AssetKind} [K=AssetKind] * @typedef {object} AssetIssuerKit - * @property {Mint} [mint] - * @property {Issuer} issuer - * @property {Brand} brand + * @property {Mint<'nat'>} [mint] + * @property {Issuer<'nat'>} issuer + * @property {Brand<'nat'>} brand */ const BaseIssuerKitShape = harden({ @@ -368,7 +367,7 @@ const AssetIssuerKitShape = M.splitRecord(BaseIssuerKitShape, { /** * @typedef {AssetIssuerKit & { * denom: string; - * escrowPurse?: RemotableObject & ERef; + * escrowPurse?: RemotableObject & ERef>; * }} AssetRecord */ @@ -686,8 +685,8 @@ const prepareBankManager = ( * @param {string} denom lower-level denomination string * @param {string} issuerName * @param {string} proposedName - * @param {AssetIssuerKit & { payment?: ERef }} kit ERTP issuer - * kit (mint, brand, issuer) + * @param {AssetIssuerKit & { payment?: ERef> }} kit ERTP + * issuer kit (mint, brand, issuer) */ async addAsset(denom, issuerName, proposedName, kit) { const { diff --git a/packages/vats/src/virtual-purse.js b/packages/vats/src/virtual-purse.js index 5f4d9db397b..c08b39751ae 100644 --- a/packages/vats/src/virtual-purse.js +++ b/packages/vats/src/virtual-purse.js @@ -81,8 +81,13 @@ export const makeVirtualPurseKitIKit = ( /** @import {EOnly} from '@endo/far'; */ -/** @typedef {(pmt: Payment, optAmountShape?: Pattern) => Promise} Retain */ -/** @typedef {(amt: Amount) => Promise} Redeem */ +/** + * @typedef {( + * pmt: Payment<'nat'>, + * optAmountShape?: Pattern, + * ) => Promise} Retain + */ +/** @typedef {(amt: Amount<'nat'>) => Promise>} Redeem */ /** * @typedef {object} VirtualPurseController The object that determines the @@ -106,8 +111,15 @@ const prepareVirtualPurseKit = zone => makeVirtualPurseKitIKit().VirtualPurseIKit, /** * @param {ERef} vpc - * @param {{ issuer: ERef; brand: Brand; mint?: ERef }} issuerKit - * @param {{ recoveryPurse: ERef; escrowPurse?: ERef }} purses + * @param {{ + * issuer: ERef; + * brand: Brand; + * mint?: ERef>; + * }} issuerKit + * @param {{ + * recoveryPurse: ERef>; + * escrowPurse?: ERef>; + * }} purses */ (vpc, issuerKit, purses) => ({ vpc, @@ -124,8 +136,9 @@ const prepareVirtualPurseKit = zone => * this on the `retain` operations (since we are just burning the * payment or depositing it directly in the `escrowPurse`). * - * @param {ERef} payment - * @param {Amount} [optAmountShape] + * @param {ERef>} payment + * @param {Amount<'nat'>} [optAmountShape] + * @returns {Promise>} */ async recoverableClaim(payment, optAmountShape) { const { @@ -235,6 +248,7 @@ const prepareVirtualPurseKit = zone => getDepositFacet() { return this.facets.depositFacet; }, + /** @type {(amount: Amount<'nat'>) => Promise>} */ async withdraw(amount) { // Both ensure that the amount exists, and have the other side "send" it // to us. If this fails, the balance is not affected and the withdraw @@ -268,10 +282,10 @@ export const prepareVirtualPurse = zone => { * @param {ERef} vpc the controller that represents * the "other side" of this purse. * @param {{ - * issuer: ERef; - * brand: Brand; - * mint?: ERef; - * escrowPurse?: ERef; + * issuer: ERef>; + * brand: Brand<'nat'>; + * mint?: ERef>; + * escrowPurse?: ERef>; * }} params * the contents of the issuer kit for "us". * @@ -280,9 +294,9 @@ export const prepareVirtualPurse = zone => { * general, but escrow doesn't support the case where the "other side" is * also minting assets... our escrow purse may not have enough assets in it * to redeem the ones that are sent from the "other side". - * @returns {Promise>>} This is not just a Purse because - * it plays fast-and-loose with the synchronous Purse interface. So, the - * consumer of this result must only interact with the virtual purse via + * @returns {Promise>>>} This is not just a Purse + * because it plays fast-and-loose with the synchronous Purse interface. So, + * the consumer of this result must only interact with the virtual purse via * eventual-send (to conceal the methods that are returning promises instead * of synchronously). */ diff --git a/packages/vats/tools/bank-utils.js b/packages/vats/tools/bank-utils.js index b0d95423262..57b1d133417 100644 --- a/packages/vats/tools/bank-utils.js +++ b/packages/vats/tools/bank-utils.js @@ -3,7 +3,9 @@ import { makeScalarMapStore } from '@agoric/vat-data'; import { E } from '@endo/far'; import { Far } from '@endo/marshal'; -/** @param {Pick[]} issuerKits */ +/** + * @param {Pick, 'brand' | 'issuer'>[]} issuerKits + */ export const makeFakeBankKit = issuerKits => { /** @type {MapStore} */ const issuers = makeScalarMapStore(); From bd0708b3fbe1d9917223c6b43124f13150046e92 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 16:24:26 -0700 Subject: [PATCH 7/9] feat(lca): getBalance --- packages/vats/src/localchain.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/vats/src/localchain.js b/packages/vats/src/localchain.js index 09294b9f0d0..6630f4c44e8 100644 --- a/packages/vats/src/localchain.js +++ b/packages/vats/src/localchain.js @@ -1,7 +1,7 @@ // @ts-check import { E } from '@endo/far'; import { M } from '@endo/patterns'; -import { AmountShape, PaymentShape } from '@agoric/ertp'; +import { AmountShape, BrandShape, PaymentShape } from '@agoric/ertp'; const { Fail } = assert; @@ -27,6 +27,7 @@ const { Fail } = assert; export const LocalChainAccountI = M.interface('LocalChainAccount', { getAddress: M.callWhen().returns(M.string()), + getBalance: M.callWhen(BrandShape).returns(AmountShape), deposit: M.callWhen(PaymentShape).optional(M.pattern()).returns(AmountShape), withdraw: M.callWhen(AmountShape).returns(PaymentShape), executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), @@ -47,6 +48,12 @@ const prepareLocalChainAccount = zone => async getAddress() { return this.state.address; }, + /** @param {Brand<'nat'>} brand */ + async getBalance(brand) { + const { bank } = this.state; + const purse = E(bank).getPurse(brand); + return E(purse).getCurrentAmount(); + }, /** * Deposit a payment into the bank purse that matches the alleged brand. * This is safe, since even if the payment lies about its brand, ERTP will @@ -68,7 +75,7 @@ const prepareLocalChainAccount = zone => * Withdraw a payment from the account's bank purse of the amount's brand. * * @param {Amount<'nat'>} amount - * @returns {Promise} payment + * @returns {Promise>} payment */ async withdraw(amount) { const { bank } = this.state; From 65dad73bc6c8a133ecead55779112f0f6223e750 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 16:24:40 -0700 Subject: [PATCH 8/9] feat(tools): issuerKit on AmountUtils --- packages/zoe/tools/test-utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/zoe/tools/test-utils.js b/packages/zoe/tools/test-utils.js index 6d0b13661e2..976e5a0fc18 100644 --- a/packages/zoe/tools/test-utils.js +++ b/packages/zoe/tools/test-utils.js @@ -17,6 +17,8 @@ export const withAmountUtils = kit => { /** @param {number} n */ units: n => AmountMath.make(kit.brand, BigInt(Math.round(n * 10 ** decimalPlaces))), + /** The intact Exo remotable */ + issuerKit: kit, }; }; /** @typedef {ReturnType} AmountUtils */ From ce7886e7360654963817ea2949e393e5adc8924b Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 23 May 2024 13:33:42 -0700 Subject: [PATCH 9/9] test: makeFakeBankManagerKit --- .../inter-protocol/test/provisionPool.test.js | 16 +-- packages/orchestration/src/facade.js | 1 - .../test/examples/swapExample.test.ts | 14 +- .../test/examples/unbondExample.test.ts | 7 +- packages/smart-wallet/test/addAsset.test.js | 10 +- packages/smart-wallet/test/supports.js | 17 +-- ...bootstrap-walletFactory-service-upgrade.js | 8 +- packages/vats/src/vat-bank.js | 4 + packages/vats/test/localchain.test.js | 122 ++++++++++-------- packages/vats/tools/bank-utils.js | 35 ++++- packages/vats/tools/fake-bridge.js | 12 +- 11 files changed, 136 insertions(+), 110 deletions(-) diff --git a/packages/inter-protocol/test/provisionPool.test.js b/packages/inter-protocol/test/provisionPool.test.js index c907e3527ed..7f2d3798609 100644 --- a/packages/inter-protocol/test/provisionPool.test.js +++ b/packages/inter-protocol/test/provisionPool.test.js @@ -6,18 +6,19 @@ import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance'; import committeeBundle from '@agoric/governance/bundles/bundle-committee.js'; import { WalletName } from '@agoric/internal'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { publishDepositFacet } from '@agoric/smart-wallet/src/walletFactory.js'; import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; -import { makeScalarBigMapStore } from '@agoric/vat-data'; import centralSupplyBundle from '@agoric/vats/bundles/bundle-centralSupply.js'; import { makeNameHubKit } from '@agoric/vats/src/nameHub.js'; -import { buildRootObject as buildBankRoot } from '@agoric/vats/src/vat-bank.js'; import { PowerFlags } from '@agoric/vats/src/walletFlags.js'; -import { makeFakeBankKit } from '@agoric/vats/tools/bank-utils.js'; +import { + makeFakeBankKit, + makeFakeBankManagerKit, +} from '@agoric/vats/tools/bank-utils.js'; import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; import { makeRatio } from '@agoric/zoe/src/contractSupport/ratio.js'; import { E, Far } from '@endo/far'; import path from 'path'; -import { publishDepositFacet } from '@agoric/smart-wallet/src/walletFactory.js'; import { makeBridgeProvisionTool } from '../src/provisionPoolKit.js'; import { makeMockChainStorageRoot, @@ -275,12 +276,7 @@ test('provisionPool trades provided assets for IST', async t => { * @param {string[]} addresses */ const makeWalletFactoryKitForAddresses = async addresses => { - const baggage = makeScalarBigMapStore('bank baggage'); - const bankManager = await buildBankRoot( - undefined, - undefined, - baggage, - ).makeBankManager(); + const { bankManager } = await makeFakeBankManagerKit(); const feeKit = makeIssuerKit('FEE'); const fees = withAmountUtils(feeKit); diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 7dcfd9aafe4..ed507e4ab77 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -40,7 +40,6 @@ const makeLocalChainFacade = localchain => { return { deposit(payment) { console.log('deposit got', payment); - // XXX yet again tripped up on remote methods looking local statically return E(account).deposit(payment); }, transferSteps(amount, msg) { diff --git a/packages/orchestration/test/examples/swapExample.test.ts b/packages/orchestration/test/examples/swapExample.test.ts index e4b826f2728..05c8cb4bdd3 100644 --- a/packages/orchestration/test/examples/swapExample.test.ts +++ b/packages/orchestration/test/examples/swapExample.test.ts @@ -7,9 +7,9 @@ import path from 'path'; import { makeFakeStorageKit } from '@agoric/internal/src/storage-test-utils.js'; import { makeHeapZone } from '@agoric/zone'; import { prepareLocalChainTools } from '@agoric/vats/src/localchain.js'; -import { buildRootObject as buildBankVatRoot } from '@agoric/vats/src/vat-bank.js'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; -import { makeFakeBankBridge } from '@agoric/vats/tools/fake-bridge.js'; +import { makeFakeBankManagerKit } from '@agoric/vats/tools/bank-utils.js'; +import { LOCALCHAIN_DEFAULT_ADDRESS } from '@agoric/vats/tools/fake-bridge.js'; import { makeFakeLocalchainBridge } from '../supports.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -28,11 +28,7 @@ test('start', async t => { const zone = makeHeapZone(); const { makeLocalChain } = prepareLocalChainTools(zone.subZone('localchain')); - const bankManager = await buildBankVatRoot( - undefined, - undefined, - zone.mapStore('bankManager'), - ).makeBankManager(makeFakeBankBridge(zone)); + const { bankManager, pourPayment } = await makeFakeBankManagerKit(); await E(bankManager).addAsset('uist', 'IST', 'Inter Stable Token', issuerKit); @@ -70,7 +66,7 @@ test('start', async t => { ); const bank = await E(bankManager).getBankForAddress( - 'agoric1fakeBridgeAddress', + LOCALCHAIN_DEFAULT_ADDRESS, ); const istPurse = await E(bank).getPurse(issuerKit.brand); @@ -81,7 +77,7 @@ test('start', async t => { const userSeat = await E(zoe).offer( inv, { give: { Stable: ten } }, - { Stable: stable.mint.mintPayment(ten) }, + { Stable: await pourPayment(ten) }, { staked: ten, validator: { diff --git a/packages/orchestration/test/examples/unbondExample.test.ts b/packages/orchestration/test/examples/unbondExample.test.ts index eebe0bcb930..6af00453834 100644 --- a/packages/orchestration/test/examples/unbondExample.test.ts +++ b/packages/orchestration/test/examples/unbondExample.test.ts @@ -9,6 +9,7 @@ import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; import { makeHeapZone } from '@agoric/zone'; import { E } from '@endo/far'; import path from 'path'; +import { makeFakeBankManagerKit } from '@agoric/vats/tools/bank-utils.js'; import { makeFakeLocalchainBridge } from '../supports.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -26,11 +27,7 @@ test('start', async t => { const zone = makeHeapZone(); const { makeLocalChain } = prepareLocalChainTools(zone.subZone('localchain')); - const bankManager = await buildBankVatRoot( - undefined, - undefined, - zone.mapStore('bankManager'), - ).makeBankManager(); + const { bankManager } = await makeFakeBankManagerKit(); await E(bankManager).addAsset('uist', 'IST', 'Inter Stable Token', issuerKit); diff --git a/packages/smart-wallet/test/addAsset.test.js b/packages/smart-wallet/test/addAsset.test.js index 0ba36801ba4..48018096a94 100644 --- a/packages/smart-wallet/test/addAsset.test.js +++ b/packages/smart-wallet/test/addAsset.test.js @@ -2,14 +2,14 @@ /* eslint @typescript-eslint/no-floating-promises: "warn" */ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E, Far } from '@endo/far'; -import { buildRootObject as buildBankVatRoot } from '@agoric/vats/src/vat-bank.js'; import { AmountMath, makeIssuerKit } from '@agoric/ertp'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; -import { makeCopyBag, makeScalarMapStore } from '@agoric/store'; +import { makeCopyBag } from '@agoric/store'; import { makePromiseKit } from '@endo/promise-kit'; import bundleSource from '@endo/bundle-source'; import { makeMarshal } from '@endo/marshal'; import { resolve as importMetaResolve } from 'import-meta-resolve'; +import { makeFakeBankManagerKit } from '@agoric/vats/tools/bank-utils.js'; import { makeDefaultTestContext } from './contexts.js'; import { ActionType, headValue, makeMockTestSpace } from './supports.js'; import { makeImportContext } from '../src/marshal-contexts.js'; @@ -24,11 +24,7 @@ const test = anyTest; test.before(async t => { const withBankManager = async () => { - const noBridge = undefined; - const baggage = makeScalarMapStore('baggage'); - const bankManager = E( - buildBankVatRoot(undefined, undefined, baggage), - ).makeBankManager(noBridge); + const { bankManager } = await makeFakeBankManagerKit(); const noop = () => {}; const space0 = await makeMockTestSpace(noop); space0.produce.bankManager.reset(); diff --git a/packages/smart-wallet/test/supports.js b/packages/smart-wallet/test/supports.js index 007c2ac39cf..109d93e5d37 100644 --- a/packages/smart-wallet/test/supports.js +++ b/packages/smart-wallet/test/supports.js @@ -13,7 +13,7 @@ import { import { setupClientManager } from '@agoric/vats/src/core/chain-behaviors.js'; import { buildRootObject as boardRoot } from '@agoric/vats/src/vat-board.js'; import { buildRootObject as mintsRoot } from '@agoric/vats/src/vat-mints.js'; -import { makeFakeBankKit } from '@agoric/vats/tools/bank-utils.js'; +import { makeFakeBankManagerKit } from '@agoric/vats/tools/bank-utils.js'; import { makeRatio } from '@agoric/zoe/src/contractSupport/ratio.js'; import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E, Far } from '@endo/far'; @@ -136,18 +136,9 @@ export const makeMockTestSpace = async log => { produce.testFirstAnchorKit.resolve(makeIssuerKit('AUSD', 'nat')); - const fakeBankKit = makeFakeBankKit([]); - - produce.bankManager.resolve( - Promise.resolve( - Far( - 'mockBankManager', - /** @type {any} */ ({ - getBankForAddress: _a => fakeBankKit.bank, - }), - ), - ), - ); + const { bankManager } = await makeFakeBankManagerKit(); + + produce.bankManager.resolve(bankManager); await Promise.all([ // @ts-expect-error diff --git a/packages/smart-wallet/test/swingsetTests/upgradeWalletFactory/bootstrap-walletFactory-service-upgrade.js b/packages/smart-wallet/test/swingsetTests/upgradeWalletFactory/bootstrap-walletFactory-service-upgrade.js index 127a465cdbe..9a556dd926e 100644 --- a/packages/smart-wallet/test/swingsetTests/upgradeWalletFactory/bootstrap-walletFactory-service-upgrade.js +++ b/packages/smart-wallet/test/swingsetTests/upgradeWalletFactory/bootstrap-walletFactory-service-upgrade.js @@ -5,8 +5,8 @@ import { makeTracer } from '@agoric/internal'; import { makeFakeStorageKit } from '@agoric/internal/src/storage-test-utils.js'; import { makeNameHubKit } from '@agoric/vats'; import { makeAgoricNamesAccess } from '@agoric/vats/src/core/utils.js'; +import { makeFakeBankManagerKit } from '@agoric/vats/tools/bank-utils.js'; import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; -import { makeFakeBankKit } from '@agoric/vats/tools/bank-utils.js'; import { E } from '@endo/eventual-send'; import { Far } from '@endo/marshal'; import { makePromiseKit } from '@endo/promise-kit'; @@ -20,7 +20,7 @@ const walletAddr = 'agoric1whatever'; const moolaKit = makeIssuerKit('moola'); export const buildRootObject = async () => { - const { bank, addAsset } = makeFakeBankKit([]); + const { bankManager } = await makeFakeBankManagerKit(); const storageKit = makeFakeStorageKit('walletFactoryUpgradeTest'); const statusPath = `walletFactoryUpgradeTest.${walletAddr}`; const currentPath = `${statusPath}.current`; @@ -40,6 +40,8 @@ export const buildRootObject = async () => { /** @type {import('../../../src/smartWallet.js').SmartWallet} */ let wallet; + const bank = await E(bankManager).getBankForAddress(walletAddr); + // for startInstance /** @type {Installation} */ let installation; @@ -104,7 +106,7 @@ export const buildRootObject = async () => { ).storagePath; currentStoragePath === currentPath || Fail`bad storage path`; - addAsset('umoola', 'moola', 'moola', moolaKit); + await E(bankManager).addAsset('umoola', 'moola', 'moola', moolaKit); const depositFacet = E(wallet).getDepositFacet(); const payment = moolaKit.mint.mintPayment( AmountMath.make(moolaKit.brand, 100n), diff --git a/packages/vats/src/vat-bank.js b/packages/vats/src/vat-bank.js index 489df8bb832..55d2771ea1c 100644 --- a/packages/vats/src/vat-bank.js +++ b/packages/vats/src/vat-bank.js @@ -206,6 +206,10 @@ const prepareBankChannelHandler = zone => try { updater.update(value, nonce); } catch (e) { + // ??? Is this an invariant that should complain louder? + + // NB: this failure does not propagate. The update() method is + // responsible for propagating the errow without side-effects. console.error('Error updating balance', update, e); } } diff --git a/packages/vats/test/localchain.test.js b/packages/vats/test/localchain.test.js index c8542ad8da7..4253b72e9d5 100644 --- a/packages/vats/test/localchain.test.js +++ b/packages/vats/test/localchain.test.js @@ -1,23 +1,26 @@ // @ts-check import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; +import { NonNullish } from '@agoric/assert'; import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp'; import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; import { E } from '@endo/far'; import { getInterfaceOf } from '@endo/marshal'; -import { M } from '@endo/patterns'; import { prepareLocalChainTools } from '../src/localchain.js'; -import { buildRootObject as buildBankVatRoot } from '../src/vat-bank.js'; -import { makeFakeLocalchainBridge } from '../tools/fake-bridge.js'; +import { makeFakeBankManagerKit } from '../tools/bank-utils.js'; +import { + LOCALCHAIN_DEFAULT_ADDRESS, + makeFakeLocalchainBridge, +} from '../tools/fake-bridge.js'; /** * @import {LocalChainAccount, LocalChainPowers} from '../src/localchain.js'; * @import {BridgeHandler, ScopedBridgeManager} from '../src/types.js'; */ -/** @type {import('ava').TestFn>} */ +/** @type {import('ava').TestFn>>} */ const test = anyTest; const { fakeVomKit } = reincarnate({ relaxDurabilityRules: false }); @@ -28,20 +31,20 @@ const provideBaggage = key => { }; const makeTestContext = async _t => { + const issuerKits = ['BLD', 'BEAN'].map(x => + makeIssuerKit(x, AssetKind.NAT, harden({ decimalPlaces: 6 })), + ); + const [bld, bean] = issuerKits.map(withAmountUtils); + const localchainBridge = makeFakeLocalchainBridge( makeDurableZone(provideBaggage('localchain')), ); - const makeBankManager = () => { - const zone = makeDurableZone(provideBaggage('bank')); - return buildBankVatRoot( - undefined, - undefined, - zone.mapStore('bankManager'), - ).makeBankManager(); - }; - - const bankManager = await makeBankManager(); + const { bankManager, pourPayment } = await makeFakeBankManagerKit({ + balances: { + // agoric1fakeBridgeAddress: { ubld: bld.units(100).value }, + }, + }); /** @param {LocalChainPowers} powers */ const makeLocalChain = async powers => { @@ -55,36 +58,34 @@ const makeTestContext = async _t => { }); return { + bld, + bean, bankManager, + issuerKits, localchain, + pourPayment, }; }; -test.beforeEach(t => { - t.context = makeTestContext(t); +test.beforeEach(async t => { + t.context = await makeTestContext(t); }); test('localchain - deposit and withdraw', async t => { - const issuerKits = ['BLD', 'BEAN'].map(x => - makeIssuerKit(x, AssetKind.NAT, harden({ decimalPlaces: 6 })), - ); - const [bld, bean] = issuerKits.map(withAmountUtils); + const { bld, bean, pourPayment } = t.context; const boot = async () => { const { bankManager } = await t.context; await t.notThrowsAsync( - E(bankManager).addAsset('ubld', 'BLD', 'Staking Token', issuerKits[0]), + E(bankManager).addAsset('ubld', 'BLD', 'Staking Token', bld.issuerKit), ); }; await boot(); const makeContract = async () => { - const { localchain } = await t.context; + const { localchain } = t.context; /** @type {LocalChainAccount | undefined} */ let contractsLca; - const contractsBldPurse = bld.issuer.makeEmptyPurse(); - // contract starts with 100 BLD in its Purse - contractsBldPurse.deposit(bld.mint.mintPayment(bld.make(100_000_000n))); return { makeAccount: async () => { @@ -92,54 +93,63 @@ test('localchain - deposit and withdraw', async t => { t.is(getInterfaceOf(lca), 'Alleged: LocalChainAccount'); const address = await E(lca).getAddress(); - t.is(address, 'agoric1fakeLCAAddress'); + t.is(address, LOCALCHAIN_DEFAULT_ADDRESS); contractsLca = lca; }, deposit: async () => { - if (!contractsLca) throw Error('LCA not found.'); - const fiftyBldAmt = bld.make(50_000_000n); + assert(contractsLca, 'first makeAccount'); + t.deepEqual( + await E(contractsLca).getBalance(bld.brand), + bld.makeEmpty(), + ); + + const fiftyBldAmt = bld.units(50); const res = await E(contractsLca).deposit( - contractsBldPurse.withdraw(fiftyBldAmt), + await pourPayment(fiftyBldAmt), ); t.true(AmountMath.isEqual(res, fiftyBldAmt)); - const payment2 = contractsBldPurse.withdraw(fiftyBldAmt); - await t.throwsAsync( - () => - // @ts-expect-error LCA is possibly undefined - E(contractsLca).deposit(payment2, { - brand: M.remotable('Brand'), - value: M.record(), - }), - { - message: /amount(.+) Must be a copyRecord/, - }, + const payment2 = await pourPayment(fiftyBldAmt); + // TODO check optAmountShape after https://github.com/Agoric/agoric-sdk/issues/9407 + // await t.throwsAsync( + // () => + // E(NonNullish(contractsLca)).deposit(payment2, { + // brand: bld.brand, + // value: M.record(), + // }), + // { + // message: /amount(.+) Must be a copyRecord/, + // }, + // ); + await E(contractsLca).deposit(payment2); + t.deepEqual( + await E(contractsLca).getBalance(bld.brand), + bld.units(100), ); - await E(contractsLca).deposit(payment2, { - brand: M.remotable('Brand'), - value: M.nat(), - }); }, withdraw: async () => { - if (!contractsLca) throw Error('LCA not found.'); - const oneHundredBldAmt = bld.make(100_000_000n); - const oneHundredBeanAmt = bean.make(100_000_000n); + assert(contractsLca, 'first makeAccount'); + const oneHundredBldAmt = bld.units(100); + const oneHundredBeanAmt = bean.units(100); + t.deepEqual( + await E(contractsLca).getBalance(bld.brand), + bld.units(100), + ); const payment = await E(contractsLca).withdraw(oneHundredBldAmt); - // @ts-expect-error Argument of type 'Payment' is not assignable to parameter of type 'ERef>'. + t.deepEqual(await E(contractsLca).getBalance(bld.brand), bld.units(0)); const paymentAmount = await E(bld.issuer).getAmountOf(payment); t.true(AmountMath.isEqual(paymentAmount, oneHundredBldAmt)); await t.throwsAsync( - // @ts-expect-error LCA is possibly undefined - () => E(contractsLca).withdraw(oneHundredBldAmt), + () => E(NonNullish(contractsLca)).withdraw(oneHundredBldAmt), + // fake bank is has different error messages than production + ); + + await t.throwsAsync( + () => E(NonNullish(contractsLca)).withdraw(oneHundredBeanAmt), { - message: /Withdrawal (.+) failed (.+) purse only contained/, + message: /not found in collection "brandToAssetRecord"/, }, ); - - // @ts-expect-error LCA is possibly undefined - await t.throwsAsync(() => E(contractsLca).withdraw(oneHundredBeanAmt), { - message: /not found in collection "brandToAssetRecord"/, - }); }, }; }; diff --git a/packages/vats/tools/bank-utils.js b/packages/vats/tools/bank-utils.js index 57b1d133417..eff6af2fbde 100644 --- a/packages/vats/tools/bank-utils.js +++ b/packages/vats/tools/bank-utils.js @@ -1,9 +1,13 @@ import { makeSubscriptionKit } from '@agoric/notifier'; -import { makeScalarMapStore } from '@agoric/vat-data'; +import { makeScalarBigMapStore, makeScalarMapStore } from '@agoric/vat-data'; +import { makeDurableZone } from '@agoric/zone/durable.js'; import { E } from '@endo/far'; import { Far } from '@endo/marshal'; +import { buildRootObject as buildBankVatRoot } from '../src/vat-bank.js'; +import { FAUCET_ADDRESS, makeFakeBankBridge } from './fake-bridge.js'; /** + * @deprecated use makeFakeBankManagerKit * @param {Pick, 'brand' | 'issuer'>[]} issuerKits */ export const makeFakeBankKit = issuerKits => { @@ -57,3 +61,32 @@ export const makeFakeBankKit = issuerKits => { return { addAsset, assetPublication: publication, bank }; }; + +/** + * @param {object} [opts] + * @param {import('./fake-bridge.js').Balances} opts.balances initial balances + */ +export const makeFakeBankManagerKit = async opts => { + const baggage = makeScalarBigMapStore('baggage'); + const zone = makeDurableZone(baggage); + + const bankManager = await buildBankVatRoot( + undefined, + undefined, + zone.mapStore('bankManager'), + ).makeBankManager(makeFakeBankBridge(zone, opts)); + + /** + * Get a payment from the faucet + * + * @param {Amount<'nat'>} amount + * @returns {Promise>} + */ + const pourPayment = async amount => { + const faucet = await E(bankManager).getBankForAddress(FAUCET_ADDRESS); + const purse = await E(faucet).getPurse(amount.brand); + return E(purse).withdraw(amount); + }; + + return { bankManager, pourPayment }; +}; diff --git a/packages/vats/tools/fake-bridge.js b/packages/vats/tools/fake-bridge.js index 3801d7d4fc5..5117d6e7579 100644 --- a/packages/vats/tools/fake-bridge.js +++ b/packages/vats/tools/fake-bridge.js @@ -1,6 +1,5 @@ -import { Fail } from '@agoric/assert'; +import { assert, Fail } from '@agoric/assert'; import { makeTracer, VBankAccount } from '@agoric/internal'; -import assert from 'node:assert/strict'; import { E } from '@endo/far'; import { makeWhen } from '@agoric/vow/src/when.js'; import { Nat } from '@endo/nat'; @@ -10,7 +9,7 @@ import { Nat } from '@endo/nat'; * @import {BridgeHandler, ScopedBridgeManager} from '../src/types.js'; * @import {Remote} from '@agoric/vow'; */ -const trace = makeTracer('FakeBridge', false); +const trace = makeTracer('FakeBridge'); const when = makeWhen(); @@ -28,6 +27,7 @@ const INFINITE_AMOUNT = 99999999999n; * @param {object} opts * @param {Balances} opts.balances initial balances * @returns {ScopedBridgeManager<'bank'>} + * @see {makeFakeBankManagerKit} and its `pourPayment` for a helper */ export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { const { balances } = opts; @@ -102,6 +102,7 @@ export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { }, fromBridge: async obj => { if (!hndlr) throw Error('no handler!'); + trace('fromBridge', obj); return when(E(hndlr).fromBridge(obj)); }, initHandler: h => { @@ -110,7 +111,6 @@ export const makeFakeBankBridge = (zone, opts = { balances: {} }) => { }, setHandler: h => { if (!hndlr) throw Error('must init first'); - trace('fromBridge', obj); hndlr = h; }, }); @@ -151,6 +151,8 @@ export const makeFakeIbcBridge = (zone, onToBridge) => { }); }; +export const LOCALCHAIN_DEFAULT_ADDRESS = 'agoric1fakeLCAAddress'; + /** * @param {import('@agoric/zone').Zone} zone * @param {(obj) => void} [onToBridge] @@ -168,7 +170,7 @@ export const makeFakeLocalchainBridge = (zone, onToBridge = () => {}) => { trace('toBridge', type, method, params); switch (type) { case 'VLOCALCHAIN_ALLOCATE_ADDRESS': - return 'agoric1fakeLCAAddress'; + return LOCALCHAIN_DEFAULT_ADDRESS; case 'VLOCALCHAIN_EXECUTE_TX': { lcaExecuteTxSequence += 1; return obj.messages.map(message => {