From d36382de96b936e1bee4d49231e70afeeaf2a22d Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Tue, 18 Jun 2024 00:32:43 +0000 Subject: [PATCH 1/3] chore(a3p): restore localchain test --- .../a:upgrade-next/localchain.test.js | 33 +++++++++++++++++++ .../proposals/a:upgrade-next/package.json | 1 + 2 files changed, 34 insertions(+) create mode 100644 a3p-integration/proposals/a:upgrade-next/localchain.test.js diff --git a/a3p-integration/proposals/a:upgrade-next/localchain.test.js b/a3p-integration/proposals/a:upgrade-next/localchain.test.js new file mode 100644 index 00000000000..b7145c64d6e --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/localchain.test.js @@ -0,0 +1,33 @@ +import test from 'ava'; + +import { agd, evalBundles, waitForBlock } from '@agoric/synthetic-chain'; + +const SUBMISSION_DIR = 'localchaintest-submission'; + +const readPublished = async path => { + const { value } = await agd.query( + 'vstorage', + 'data', + '--output', + 'json', + `published.${path}`, + ); + if (value === '') { + return undefined; + } + const obj = JSON.parse(value); + return obj.values[0]; +}; + +// The testing assertions are in the submission that runs in the core-eval. +// The test here runs that and confirms the eval made it through all the assertions. +test(`localchain passes tests`, async t => { + await evalBundles(SUBMISSION_DIR); + + const nodePath = 'test.localchain'; + const nodeValue = JSON.stringify({ success: true }); + + await waitForBlock(2); // enough time for core eval to execute ? + + t.is(await readPublished(nodePath), nodeValue); +}); diff --git a/a3p-integration/proposals/a:upgrade-next/package.json b/a3p-integration/proposals/a:upgrade-next/package.json index c56bf1e6cec..153f07dfaca 100644 --- a/a3p-integration/proposals/a:upgrade-next/package.json +++ b/a3p-integration/proposals/a:upgrade-next/package.json @@ -7,6 +7,7 @@ "coreProposals": [] }, "sdk-generate": [ + "vats/test-localchain.js localchaintest-submission", "vats/upgrade-bank.js upgrade-bank", "vats/upgrade-provisionPool.js upgrade-provisionPool", "testing/add-LEMONS.js add-LEMONS", From 5ff628ec43a75b816c8c0172112071a6362e40e1 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Tue, 18 Jun 2024 00:54:24 +0000 Subject: [PATCH 2/3] Revert "test: drop or clean-up old Tests" This reverts commit 6189733447bd9bd60bff1e245b15ce768df76ddb. --- .../proposals/a:upgrade-next/package.json | 1 + .../priceFeed-follower-auction.test.js | 8 ++ .../a:upgrade-next/probeZcfBundleCap.test.js | 27 ++++ .../a:upgrade-next/provisioning.test.js | 51 +++++++ .../a:upgrade-next/upgradeVaults.test.js | 134 ++++++++++++++++++ 5 files changed, 221 insertions(+) create mode 100644 a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js create mode 100644 a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js create mode 100644 a3p-integration/proposals/a:upgrade-next/provisioning.test.js create mode 100644 a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js diff --git a/a3p-integration/proposals/a:upgrade-next/package.json b/a3p-integration/proposals/a:upgrade-next/package.json index 153f07dfaca..8a30802df8a 100644 --- a/a3p-integration/proposals/a:upgrade-next/package.json +++ b/a3p-integration/proposals/a:upgrade-next/package.json @@ -7,6 +7,7 @@ "coreProposals": [] }, "sdk-generate": [ + "vats/probe-zcf-bundle.js probe-submission", "vats/test-localchain.js localchaintest-submission", "vats/upgrade-bank.js upgrade-bank", "vats/upgrade-provisionPool.js upgrade-provisionPool", diff --git a/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js b/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js new file mode 100644 index 00000000000..30ae903b075 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js @@ -0,0 +1,8 @@ +import test from 'ava'; +import { getDetailsMatchingVats } from './vatDetails.js'; + +test('new auction vat', async t => { + const details = await getDetailsMatchingVats('auctioneer'); + // This query matches both the auction and its governor, so 2*2 + t.is(Object.keys(details).length, 4); +}); diff --git a/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js b/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js new file mode 100644 index 00000000000..ef27a73f649 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js @@ -0,0 +1,27 @@ +import test from 'ava'; + +import { + evalBundles, + getIncarnation, + getVatDetails, +} from '@agoric/synthetic-chain'; + +const SUBMISSION_DIR = 'probe-submission'; + +test('upgrade Zoe to verify ZcfBundleCap endures', async t => { + await null; + t.assert((await getIncarnation('zoe')) === 1, 'zoe incarnation must be one'); + + // Before the test, the Wallet Factory should be using the legacy ZCF + const detailsBefore = await getVatDetails('walletFactory'); + t.true(detailsBefore.incarnation >= 2, 'wf incarnation must be >= 2'); + + await evalBundles(SUBMISSION_DIR); + + const detailsAfter = await getVatDetails('walletFactory'); + t.is( + detailsAfter.incarnation, + detailsBefore.incarnation + 2, + 'wf incarnation must increase by 2', + ); +}); diff --git a/a3p-integration/proposals/a:upgrade-next/provisioning.test.js b/a3p-integration/proposals/a:upgrade-next/provisioning.test.js new file mode 100644 index 00000000000..e31ffbb89a8 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/provisioning.test.js @@ -0,0 +1,51 @@ +// @ts-check + +import test from 'ava'; +import { readFile, writeFile } from 'node:fs/promises'; + +import { + getIncarnation, + getUser, + evalBundles, + waitForBlock, + agoric, +} from '@agoric/synthetic-chain'; + +const SUBMISSION_DIR = 'provisioning-test-submission'; + +/** + * @param {string} fileName base file name without .tjs extension + * @param {Record} replacements + */ +const replaceTemplateValuesInFile = async (fileName, replacements) => { + let script = await readFile(`${fileName}.tjs`, 'utf-8'); + for (const [template, value] of Object.entries(replacements)) { + script = script.replaceAll(`{{${template}}}`, value); + } + await writeFile(`${fileName}.js`, script); +}; + +test.serial(`provisioning vat was upgraded`, async t => { + const incarnation = await getIncarnation('provisioning'); + + t.is(incarnation, 1); +}); + +test.serial(`send invitation via namesByAddress`, async t => { + const addr = await getUser('gov1'); + + await replaceTemplateValuesInFile(`${SUBMISSION_DIR}/send-script`, { + ADDRESS: addr, + }); + + await evalBundles(SUBMISSION_DIR); + + await waitForBlock(2); // enough time for invitation to arrive? + const update = await agoric.follow('-lF', `:published.wallet.${addr}`); + t.is(update.updated, 'balance'); + t.notDeepEqual(update.currentAmount.value, []); + t.log('balance value', update.currentAmount.value); + t.log('balance brand', update.currentAmount.brand); + // XXX agoric follow returns brands as strings + t.regex(update.currentAmount.brand, /Invitation/); +}); diff --git a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js new file mode 100644 index 00000000000..1dc1c662232 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js @@ -0,0 +1,134 @@ +import test from 'ava'; + +import { + agops, + ATOM_DENOM, + getISTBalance, + getVatDetails, + openVault, + USER1ADDR, +} from '@agoric/synthetic-chain'; + +import { + addOraclesForBrand, + bankSend, + BID_OFFER_ID, + checkForOracle, + createBid, + getLiveOffers, + getPriceQuote, + pushPrices, +} from './agd-tools.js'; +import { getDetailsMatchingVats } from './vatDetails.js'; + +const checkPriceFeedVatsUpdated = async t => { + const atomDetails = await getVatDetails('ATOM-USD_price_feed'); + // both the original and the new ATOM vault are incarnation 0 + t.is(atomDetails.incarnation, 0); + const stAtomDetails = await getVatDetails('stATOM'); + t.is(stAtomDetails.incarnation, 0); + const stOsmoDetails = await getVatDetails('stOSMO'); + t.is(stOsmoDetails.incarnation, 0); + const stTiaDetails = await getVatDetails('stTIA'); + t.is(stTiaDetails.incarnation, 0); + await Promise.all([ + checkForOracle(t, 'ATOM'), + checkForOracle(t, 'stATOM'), + checkForOracle(t, 'stTIA'), + checkForOracle(t, 'stOSMO'), + checkForOracle(t, 'stkATOM'), + ]); +}; + +const oraclesByBrand = new Map(); + +const tryPushPrices = async t => { + // There are no old prices for the other currencies. + const atomOutPre = await getPriceQuote('ATOM'); + t.is(atomOutPre, '+12010000'); + + t.log('adding oracle for each brand'); + await addOraclesForBrand('ATOM', oraclesByBrand); + await addOraclesForBrand('stATOM', oraclesByBrand); + await addOraclesForBrand('stTIA', oraclesByBrand); + await addOraclesForBrand('stOSMO', oraclesByBrand); + await addOraclesForBrand('stkATOM', oraclesByBrand); + + t.log('pushing new prices'); + await pushPrices(11.2, 'ATOM', oraclesByBrand); + await pushPrices(11.3, 'stTIA', oraclesByBrand); + await pushPrices(11.4, 'stATOM', oraclesByBrand); + await pushPrices(11.5, 'stOSMO', oraclesByBrand); + await pushPrices(11.6, 'stkATOM', oraclesByBrand); + + t.log('awaiting new quotes'); + const atomOut = await getPriceQuote('ATOM'); + t.is(atomOut, '+11200000'); + const tiaOut = await getPriceQuote('stTIA'); + t.is(tiaOut, '+11300000'); + const stAtomOut = await getPriceQuote('stATOM'); + t.is(stAtomOut, '+11400000'); + const osmoOut = await getPriceQuote('stOSMO'); + t.is(osmoOut, '+11500000'); + const stkAtomOut = await getPriceQuote('stkATOM'); + t.is(stkAtomOut, '+11600000'); +}; + +const createNewBid = async t => { + await createBid('20', USER1ADDR, BID_OFFER_ID); + const liveOffer = await getLiveOffers(USER1ADDR); + t.true(liveOffer[0].includes(BID_OFFER_ID)); +}; + +const openMarginalVault = async t => { + let user1IST = await getISTBalance(USER1ADDR); + await bankSend(USER1ADDR, `20000000${ATOM_DENOM}`); + const currentVaults = await agops.vaults('list', '--from', USER1ADDR); + + t.log('opening a vault'); + await openVault(USER1ADDR, 5, 10); + user1IST += 5; + const istBalanceAfterVaultOpen = await getISTBalance(USER1ADDR); + t.is(istBalanceAfterVaultOpen, user1IST); + + const activeVaultsAfter = await agops.vaults('list', '--from', USER1ADDR); + t.log(currentVaults, activeVaultsAfter); + t.true( + activeVaultsAfter.length > currentVaults.length, + `vaults count should increase, ${activeVaultsAfter.length}, ${currentVaults.length}`, + ); +}; + +const triggerAuction = async t => { + await pushPrices(5.2, 'ATOM', oraclesByBrand); + + const atomOut = await getPriceQuote('ATOM'); + t.is(atomOut, '+5200000'); +}; + +const makeNewAuctionVat = async t => { + const details = await getDetailsMatchingVats('auctioneer'); + // This query matches both the auction and its governor, so double the count + t.true(Object.keys(details).length > 2); +}; + +// test.serial() isn't guaranteed to run tests in order, so we run the intended tests here +test('liquidation post upgrade', async t => { + t.log('starting upgrade vaults test'); + await checkPriceFeedVatsUpdated(t); + + t.log('starting pushPrices'); + await tryPushPrices(t); + + t.log('create a new Bid for the auction'); + await createNewBid(t); + + t.log('open a marginal vault'); + await openMarginalVault(t); + + t.log('trigger Auction'); + await triggerAuction(t); + + t.log('make new auction'); + await makeNewAuctionVat(t); +}); From b5cf8bd51585df36a72b65d7a1d66babd358b316 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Tue, 18 Jun 2024 04:13:32 +0000 Subject: [PATCH 3/3] fix(localchain): `callWhen`s return `PromiseVow` --- packages/vats/src/localchain.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/vats/src/localchain.js b/packages/vats/src/localchain.js index 11495090730..91f544bc39a 100644 --- a/packages/vats/src/localchain.js +++ b/packages/vats/src/localchain.js @@ -5,6 +5,7 @@ import { AmountShape, BrandShape, PaymentShape } from '@agoric/ertp'; import { Shape as NetworkShape } from '@agoric/network'; const { Fail } = assert; +const { Vow$ } = NetworkShape; /** * @import {TypedJson, ResponseTo, JsonSafe} from '@agoric/cosmic-proto'; @@ -45,15 +46,17 @@ const { Fail } = assert; */ export const LocalChainAccountI = M.interface('LocalChainAccount', { - getAddress: M.callWhen().returns(M.string()), - getBalance: M.callWhen(BrandShape).returns(AmountShape), + getAddress: M.callWhen().returns(Vow$(M.string())), + getBalance: M.callWhen(BrandShape).returns(Vow$(AmountShape)), deposit: M.callWhen(PaymentShape) .optional(M.pattern()) - .returns(NetworkShape.Vow$(AmountShape)), - withdraw: M.callWhen(AmountShape).returns(PaymentShape), - executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), + .returns(Vow$(AmountShape)), + withdraw: M.callWhen(AmountShape).returns(Vow$(PaymentShape)), + executeTx: M.callWhen(M.arrayOf(M.record())).returns( + Vow$(M.arrayOf(M.record())), + ), monitorTransfers: M.callWhen(M.remotable('TransferTap')).returns( - M.remotable('TargetRegistration'), + Vow$(M.remotable('TargetRegistration')), ), }); @@ -192,9 +195,11 @@ export const prepareLocalChainAccountKit = (zone, { watch }) => /** @typedef {LocalChainAccountKit['account']} LocalChainAccount */ export const LocalChainI = M.interface('LocalChain', { - makeAccount: M.callWhen().returns(M.remotable('LocalChainAccount')), - query: M.callWhen(M.record()).returns(M.record()), - queryMany: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), + makeAccount: M.callWhen().returns(Vow$(M.remotable('LocalChainAccount'))), + query: M.callWhen(M.record()).returns(Vow$(M.record())), + queryMany: M.callWhen(M.arrayOf(M.record())).returns( + Vow$(M.arrayOf(M.record())), + ), }); /**