From 65bfdc0dd795a8cf1d9016bfbd3740d00a9bfce1 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Sat, 5 Oct 2024 05:43:27 +0300 Subject: [PATCH] chore(acceptance): make the tests more robust against time related complexities --- .../proposals/n:upgrade-next/lib/vaults.js | 178 -------------- .../proposals/n:upgrade-next/performAction.js | 59 ++--- .../proposals/n:upgrade-next/use.sh | 2 +- .../proposals/z:acceptance/auction.test.js | 220 +++++++----------- .../proposals/z:acceptance/performActions.js | 12 - .../z:acceptance/scripts/test-vaults.mts | 4 +- .../proposals/z:acceptance/test.sh | 1 + 7 files changed, 123 insertions(+), 353 deletions(-) delete mode 100644 a3p-integration/proposals/n:upgrade-next/lib/vaults.js delete mode 100644 a3p-integration/proposals/z:acceptance/performActions.js diff --git a/a3p-integration/proposals/n:upgrade-next/lib/vaults.js b/a3p-integration/proposals/n:upgrade-next/lib/vaults.js deleted file mode 100644 index aa463da9a543..000000000000 --- a/a3p-integration/proposals/n:upgrade-next/lib/vaults.js +++ /dev/null @@ -1,178 +0,0 @@ -/* eslint-disable @jessie.js/safe-await-separator */ -/* eslint-env node */ - -import { strict as assert } from 'node:assert'; - -import { - addUser, - agops, - agoric, - ATOM_DENOM, - executeOffer, - GOV1ADDR, - GOV2ADDR, - GOV3ADDR, - provisionSmartWallet, - waitForBlock, -} from '@agoric/synthetic-chain'; - -const govAccounts = [GOV1ADDR, GOV2ADDR, GOV3ADDR]; - -export const ISTunit = 1_000_000n; // aka displayInfo: { decimalPlaces: 6 } - -// XXX likely longer than necessary -const VOTING_WAIT_MS = 65 * 1000; - -const proposeNewAuctionParams = async ( - address, - startFequency, - clockStep, - priceLockPeriod, -) => { - const charterAcceptOfferId = await agops.ec( - 'find-continuing-id', - '--for', - `${'charter\\ member\\ invitation'}`, - '--from', - address, - ); - - return executeOffer( - address, - agops.auctioneer( - 'proposeParamChange', - '--charterAcceptOfferId', - charterAcceptOfferId, - '--start-frequency', - startFequency, - '--clock-step', - clockStep, - '--price-lock-period', - priceLockPeriod, - ), - ); -}; - -const voteForNewParams = (accounts, position) => { - console.log('ACTIONS voting for position', position, 'using', accounts); - return Promise.all( - accounts.map((account) => - agops.ec('vote', '--forPosition', position, '--send-from', account), - ), - ); -}; - -const paramChangeOfferGeneration = async ( - previousOfferId, - voteDur, - debtLimit, -) => { - const voteDurSec = BigInt(voteDur); - const debtLimitValue = BigInt(debtLimit) * ISTunit; - const toSec = (ms) => BigInt(Math.round(ms / 1000)); - - const id = `propose-${Date.now()}`; - const deadline = toSec(Date.now()) + voteDurSec; - - const zip = (xs, ys) => - xs.map((x, i) => [x, ys[i]]); - const fromSmallCapsEntries = (txt) => { - const { body, slots } = JSON.parse(txt); - const theEntries = zip(JSON.parse(body.slice(1)), slots).map( - ([[name, ref], boardID]) => { - const iface = ref.replace(/^\$\d+\./, ''); - return [name, { iface, boardID }]; - }, - ); - return Object.fromEntries(theEntries); - }; - - const slots = []; // XXX global mutable state - const smallCaps = { - Nat: (n) => `+${n}`, - // XXX mutates obj - ref: (obj) => { - if (obj.ix) return obj.ix; - const ix = slots.length; - slots.push(obj.boardID); - obj.ix = `$${ix}.Alleged: ${obj.iface}`; - return obj.ix; - }, - }; - - const instance = fromSmallCapsEntries( - await agoric.follow('-lF', ':published.agoricNames.instance', '-o', 'text'), - ); - assert(instance.VaultFactory); - - const brand = fromSmallCapsEntries( - await agoric.follow('-lF', ':published.agoricNames.brand', '-o', 'text'), - ); - assert(brand.IST); - assert(brand.ATOM); - - const body = { - method: 'executeOffer', - offer: { - id, - invitationSpec: { - invitationMakerName: 'VoteOnParamChange', - previousOffer: previousOfferId, - source: 'continuing', - }, - offerArgs: { - deadline: smallCaps.Nat(deadline), - instance: smallCaps.ref(instance.VaultFactory), - params: { - DebtLimit: { - brand: smallCaps.ref(brand.IST), - value: smallCaps.Nat(debtLimitValue), - }, - }, - path: { - paramPath: { - key: { - collateralBrand: smallCaps.ref(brand.ATOM), - }, - }, - }, - }, - proposal: {}, - }, - }; - - const capData = { body: `#${JSON.stringify(body)}`, slots }; - return JSON.stringify(capData); -}; -export const provisionWallet = async (user) => { - const userAddress = await addUser(user); - - await provisionSmartWallet( - userAddress, - `20000000ubld,100000000${ATOM_DENOM}`, - ); - await waitForBlock(); -}; - -export const implementNewAuctionParams = async ( - address, - oracles, - startFequency, - clockStep, - priceLockPeriod, -) => { - await waitForBlock(3); - - await proposeNewAuctionParams( - address, - startFequency, - clockStep, - priceLockPeriod, - ); - - console.log('ACTIONS voting for new auction params'); - await voteForNewParams(govAccounts, 0); - - console.log('ACTIONS wait for the vote deadline to pass'); - await new Promise(r => setTimeout(r, VOTING_WAIT_MS)); -}; \ No newline at end of file diff --git a/a3p-integration/proposals/n:upgrade-next/performAction.js b/a3p-integration/proposals/n:upgrade-next/performAction.js index ae07de9e10be..36b17c62a29e 100644 --- a/a3p-integration/proposals/n:upgrade-next/performAction.js +++ b/a3p-integration/proposals/n:upgrade-next/performAction.js @@ -1,37 +1,40 @@ #!/usr/bin/env node -import { GOV1ADDR, GOV2ADDR, CHAINID, agd, agopsInter, addPreexistingOracles, pushPrices, agoric } from "@agoric/synthetic-chain"; +import { + GOV1ADDR, + CHAINID, + agd, + agopsInter, + addUser, + waitForBlock, + provisionSmartWallet, + ATOM_DENOM, +} from '@agoric/synthetic-chain'; export const bankSend = (from, addr, wanted) => { - const chain = ['--chain-id', CHAINID]; - const fromArg = ['--from', from]; - const testKeyring = ['--keyring-backend', 'test']; - const noise = [...fromArg, ...chain, ...testKeyring, '--yes']; + const chain = ['--chain-id', CHAINID]; + const fromArg = ['--from', from]; + const testKeyring = ['--keyring-backend', 'test']; + const noise = [...fromArg, ...chain, ...testKeyring, '--yes']; - return agd.tx('bank', 'send', from, addr, wanted, ...noise); + return agd.tx('bank', 'send', from, addr, wanted, ...noise); }; -const setupOracles = async () => { - const oraclesByBrand = new Map(); - const round = await agoric.follow( - '-lF', - ':published.priceFeed.ATOM-USD_price_feed.latestRound', - ); - await addPreexistingOracles('ATOM', oraclesByBrand); - - await pushPrices(9.99, 'ATOM', oraclesByBrand, parseInt(round.roundId) + 1); -}; - -// await setupOracles(); -await bankSend(GOV1ADDR, GOV2ADDR, `80000000uist`); - +const bidder = await addUser('long-living-bidder'); +console.log('BIDDDER', bidder); +await bankSend(GOV1ADDR, bidder, `80000000uist`); +console.log('IST sent'); +await provisionSmartWallet(bidder, `20000000ubld,100000000${ATOM_DENOM}`); +console.log('Provision sent'); +await waitForBlock(3); +console.log('Wait For Block done. Sending bid offer'); agopsInter( - 'bid', - 'by-discount', - `--discount 10`, - `--give 80IST`, - '--from', - GOV2ADDR, - '--keyring-backend test', - `--offer-id gov-2-bid-for-acceptance`, + 'bid', + 'by-price', + `--price 49.0`, + `--give 80IST`, + '--from', + bidder, + '--keyring-backend test', + `--offer-id long-living-bid-for-acceptance`, ); diff --git a/a3p-integration/proposals/n:upgrade-next/use.sh b/a3p-integration/proposals/n:upgrade-next/use.sh index e60a76704524..d1cd92ac17cc 100644 --- a/a3p-integration/proposals/n:upgrade-next/use.sh +++ b/a3p-integration/proposals/n:upgrade-next/use.sh @@ -5,4 +5,4 @@ set -e source /usr/src/upgrade-test-scripts/env_setup.sh -./performAction.js \ No newline at end of file +./performAction.js diff --git a/a3p-integration/proposals/z:acceptance/auction.test.js b/a3p-integration/proposals/z:acceptance/auction.test.js index 34a806ae4073..6da02f85159f 100644 --- a/a3p-integration/proposals/z:acceptance/auction.test.js +++ b/a3p-integration/proposals/z:acceptance/auction.test.js @@ -1,13 +1,14 @@ /** * @file In this file we aim to test auctioneer in an isolated manner. Here's the scenario to test; * - Send 100 ATOMs to gov1 from validator - * - Make sure auctioneer params like ClockStep, StartFrequency are reduced * - For book0, ATOM is collateral, set two types of bids; by price and by percentage, user1 is the bidder - * - Deposit some collateral into book0, gov1 is the depositor + * - Deposit 100 ATOMs into book0, gov1 is the depositor * - Wait until placed bids get their payouts - * - Make sure the depositer gets correct amounts + * - Make sure the depositor gets correct amounts */ +/** @typedef {import('./test-lib/sync-tools.js').RetyrOptions} RetryOptions */ + import { addPreexistingOracles, agd, @@ -17,8 +18,8 @@ import { CHAINID, executeOffer, getPriceQuote, + getUser, GOV1ADDR, - GOV2ADDR, GOV3ADDR, pushPrices, USER1ADDR, @@ -36,7 +37,7 @@ import { AmountMath } from '@agoric/ertp'; export const scale6 = x => BigInt(Math.round(x * 1_000_000)); -const ambientAuthroity = { +const ambientAuthority = { query: agd.query, follow: agoric.follow, setTimeout: globalThis.setTimeout, @@ -55,7 +56,11 @@ export const bankSend = (from, addr, wanted) => { }; const config = { - price: 9.99, + depositor: { + depositValue: '100000000', + offerId: `gov1-deposit-${Date.now()}`, + }, + price: 50.0, bidsSetup: [ { bidder: USER1ADDR, @@ -65,7 +70,7 @@ const config = { }, offerId: `user1-bid-${Date.now()}`, give: '90IST', - price: 9.0, + price: 46, }, { bidder: GOV3ADDR, @@ -75,32 +80,32 @@ const config = { }, offerId: `gov3-bid-${Date.now()}`, give: '150IST', - discount: '15', + discount: '13', }, ], bidsOutcome: [ { payouts: { Bid: 0, - Collateral: 8.897786, + Collateral: 1.68421, }, }, { payouts: { Bid: 0, - Collateral: 10.01001, + Collateral: 2.0, }, }, { payouts: { Bid: 0, - Collateral: 17.664723, + Collateral: 3.448275, }, }, ], }; -const setupOracles = async t => { +const pushPricesForAuction = async t => { const oraclesByBrand = new Map(); await addPreexistingOracles('ATOM', oraclesByBrand); @@ -118,36 +123,34 @@ const setupOracles = async t => { ); }; -const DEPOSIT_OFFER_ID = `gov1-deposit-${Date.now()}`; - const fundAccts = async (depositorAmt = '100000000', t) => { const retryOpts = t.context.retryOpts.bankSendRetryOpts; await bankSend(VALIDATORADDR, GOV1ADDR, `${depositorAmt}${ATOM_DENOM}`), await waitUntilAccountFunded( GOV1ADDR, - ambientAuthroity, + ambientAuthority, { denom: ATOM_DENOM, value: Number(depositorAmt) }, { errorMessage: 'gov1 not funded yet', ...retryOpts }, ); const user1Fund = config.bidsSetup[0].bidderFund; await bankSend(GOV1ADDR, USER1ADDR, `${user1Fund.value}${user1Fund.denom}`); - await waitUntilAccountFunded(USER1ADDR, ambientAuthroity, user1Fund, { + await waitUntilAccountFunded(USER1ADDR, ambientAuthority, user1Fund, { errorMessage: 'user1 not funded yet', ...retryOpts, }); const gov3Fund = config.bidsSetup[1].bidderFund; await bankSend(GOV1ADDR, GOV3ADDR, `${gov3Fund.value}${gov3Fund.denom}`); - await waitUntilAccountFunded(GOV3ADDR, ambientAuthroity, gov3Fund, { + await waitUntilAccountFunded(GOV3ADDR, ambientAuthority, gov3Fund, { errorMessage: 'gov3 not funded yet', ...retryOpts, }); }; const bidByPrice = (price, give, offerId, bidder, t) => { - agopsInter( + return agopsInter( 'bid', 'by-price', `--price ${price}`, @@ -157,15 +160,10 @@ const bidByPrice = (price, give, offerId, bidder, t) => { '--keyring-backend test', `--offer-id ${offerId}`, ); - - return waitUntilOfferResult(bidder, offerId, true, ambientAuthroity, { - errorMessage: 'bid not settled yet', - ...t.context.retryOpts.clientRetryOpts, - }); }; const bidByDiscount = (discount, give, offerId, bidder, t) => { - agopsInter( + return agopsInter( 'bid', 'by-discount', `--discount ${discount}`, @@ -175,17 +173,11 @@ const bidByDiscount = (discount, give, offerId, bidder, t) => { '--keyring-backend test', `--offer-id ${offerId}`, ); - - return waitUntilOfferResult(bidder, offerId, true, ambientAuthroity, { - errorMessage: 'bid not settled yet', - ...t.context.retryOpts.clientRetryOpts, - }); }; -const placeBids = (t) => { +const placeBids = t => { return [...config.bidsSetup].map( ({ bidder, offerId, price, give, discount }) => { - console.log('BIDS', { bidder, offerId, price, give, discount }); if (price) return bidByPrice(price, give, offerId, bidder, t); return bidByDiscount(discount, give, offerId, bidder, t); }, @@ -193,18 +185,16 @@ const placeBids = (t) => { }; const depositCollateral = async t => { - const brandsRaw = await agoric.follow( - '-lF', - ':published.agoricNames.brand', - '-o', - 'text', - ); + const [brandsRaw, retryOptions] = await Promise.all([ + agoric.follow('-lF', ':published.agoricNames.brand', '-o', 'text'), + calculateRetryUntilNextStartTime(), + ]); const brands = Object.fromEntries( marshaller.fromCapData(JSON.parse(brandsRaw)), ); const offerSpec = { - id: DEPOSIT_OFFER_ID, + id: config.depositor.offerId, invitationSpec: { source: 'agoricContract', instancePath: ['auctioneer'], @@ -228,12 +218,12 @@ const depositCollateral = async t => { executeOffer(GOV1ADDR, offer); return waitUntilOfferResult( GOV1ADDR, - DEPOSIT_OFFER_ID, + config.depositor.offerId, true, - ambientAuthroity, + ambientAuthority, { errorMessage: 'proceeds not distributed yet', - ...t.context.retryOpts.clientRetryOpts, + ...retryOptions, }, ); }; @@ -273,78 +263,33 @@ const checkPrice = (res, expected) => { return false; }; -const parseRelTime = (params, keyword) => - parseInt(params.current[keyword].value.relValue); -const parseNat = (params, keyword) => parseInt(params.current[keyword].value); - /** - * Number of steps in an auction is the biggest integer that satisfies below - * steps * discountStep <= startingRate - lowestRate, and - * steps * clockStep < freq + * Calculates a set of retry options based on current auction params */ -const calculateAuctionSteps = params => { - const discountStep = parseNat(params, 'DiscountStep'); - const clockStep = parseRelTime(params, 'ClockStep'); - const lowestRate = parseNat(params, 'LowestRate'); - const startingRate = parseNat(params, 'StartingRate'); - const freq = parseRelTime(params, 'StartFrequency'); - - const maxRateSteps = Math.floor((startingRate - lowestRate) / discountStep); - const maxTimeSteps = Math.floor((freq - 1) / clockStep); - - console.log({ - discountStep, - clockStep, - lowestRate, - startingRate, - freq, - maxRateSteps, - maxTimeSteps, - }); +const calculateRetryUntilNextStartTime = async () => { + const schedule = await agoric.follow('-lF', ':published.auction.schedule'); + const nextStartTime = parseInt(schedule.nextStartTime.absValue); + + /** @type {RetryOptions} */ + const capturePriceRetryOpts = { + maxRetries: Math.round((nextStartTime * 1000 - Date.now()) / 10000) + 2, // wait until next schedule + retryIntervalMs: 10000, // 10 seconds in ms + }; - return Math.min(maxRateSteps, maxTimeSteps); + return capturePriceRetryOpts; }; test.before(async t => { - /** - * Calculates a set of retry options based on current auction params - */ - const calculateRetryOptions = async () => { - const params = await agoric.follow('-lF', ':published.auction.governance'); - const clockStep = parseRelTime(params, 'ClockStep'); - const freq = parseRelTime(params, 'StartFrequency'); - const steps = calculateAuctionSteps(params); - - /** @type {import('./test-lib/sync-tools.js').RetyrOptions} */ - const clientRetryOpts = { - maxRetries: steps + 1, // auctions steps + 1 - retryIntervalMs: clockStep * 1000, // in ms - }; - - /** @type {import('./test-lib/sync-tools.js').RetyrOptions} */ - const capturePriceRetryOpts = { - maxRetries: Math.floor(freq / clockStep) + 1, // wait for a whole round - retryIntervalMs: clockStep * 1000, // in ms - }; - - /** @type {import('./test-lib/sync-tools.js').RetyrOptions} */ - const pushPriceRetryOpts = { - maxRetries: 5, // arbitrary - retryIntervalMs: 5000, // in ms - }; - - /** @type {import('./test-lib/sync-tools.js').RetyrOptions} */ - const bankSendRetryOpts = { - maxRetries: 3, // arbitrary - retryIntervalMs: 3000, // in ms - }; - - return { - clientRetryOpts, - capturePriceRetryOpts, - pushPriceRetryOpts, - bankSendRetryOpts, - }; + /** @type {RetryOptions} */ + const pushPriceRetryOpts = { + maxRetries: 5, // arbitrary + retryIntervalMs: 5000, // in ms + }; + + /** @type {RetryOptions} */ + const bankSendRetryOpts = { + maxRetries: 3, // arbitrary + retryIntervalMs: 3000, // in ms }; // Get current round id @@ -352,53 +297,58 @@ test.before(async t => { '-lF', ':published.priceFeed.ATOM-USD_price_feed.latestRound', ); - const retryOpts = await calculateRetryOptions(); + t.context = { roundId: parseInt(round.roundId), - retryOpts, + retryOpts: { + bankSendRetryOpts, + pushPriceRetryOpts, + }, }; }); test.only('run auction', async t => { - await setupOracles(t); + // Push the price to a point where only our bids can settle + await pushPricesForAuction(t); + // Wait until next round starts. Retry error message is useful for debugging + const retryOptions = await calculateRetryUntilNextStartTime(); await retryUntilCondition( () => getCapturedPrice('book0'), res => checkPrice(res, scale6(config.price).toString()), // scale price to uist - 'price not captured yet', + 'price not captured yet [AUCTION TEST]', { log: t.log, - ...ambientAuthroity, - // @ts-ignore - ...t.context.retryOpts.capturePriceRetryOpts, + ...ambientAuthority, + ...retryOptions, }, ); - await fundAccts('100000000', t); + // Make sure depositor and bidders have enough balance + await fundAccts(config.depositor.depositValue, t); const bidsP = placeBids(t); const proceedsP = depositCollateral(t); - const longLivingBidP = waitUntilOfferResult( - // This bid placed in n:upgrade-next - GOV2ADDR, - 'gov-2-bid-for-acceptance', - true, - ambientAuthroity, - { - errorMessage: 'long living bid not resolved yet', - // @ts-ignore - ...t.context.retryOpts.clientRetryOpts, - }, - ); - await Promise.all([longLivingBidP, ...bidsP, proceedsP]); + // Resolves when auction finalizes and depositor gets payouts + const [longLivingBidderAddr] = await Promise.all([ + getUser('long-living-bidder'), + ...bidsP, + proceedsP, + ]); - const [gov1Results, gov2Results, user1Results, gov3Results, brands] = + // Query wallets of the actors involved for assertions + const [gov1Results, longLivingBidResults, user1Results, gov3Results, brands] = await Promise.all([ agoric .follow('-lF', `:published.wallet.${GOV1ADDR}`, '-o', 'text') .then(res => marshaller.fromCapData(JSON.parse(res))), agoric - .follow('-lF', `:published.wallet.${GOV2ADDR}`, '-o', 'text') + .follow( + '-lF', + `:published.wallet.${longLivingBidderAddr}`, + '-o', + 'text', + ) .then(res => marshaller.fromCapData(JSON.parse(res))), agoric .follow('-lF', `:published.wallet.${USER1ADDR}`, '-o', 'text') @@ -413,6 +363,7 @@ test.only('run auction', async t => { ), ]); + // Assert depositor paid correctly const { Bid: depositorBid, Collateral: depositorCol } = gov1Results.status.payouts; @@ -423,11 +374,16 @@ test.only('run auction', async t => { t.is( AmountMath.isGTE( - AmountMath.make(brands.ATOM, 100_000_000n - 27_563_510n), + AmountMath.make(brands.ATOM, 100_000_000n - 7_132_485n), depositorCol, ), true, ); - checkBidsOutcome([gov2Results, user1Results, gov3Results], t, brands); + // Assert bidders paid correctly + checkBidsOutcome( + [longLivingBidResults, user1Results, gov3Results], + t, + brands, + ); }); diff --git a/a3p-integration/proposals/z:acceptance/performActions.js b/a3p-integration/proposals/z:acceptance/performActions.js deleted file mode 100644 index 3eddd3bb8e7f..000000000000 --- a/a3p-integration/proposals/z:acceptance/performActions.js +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env node - -import { addPreexistingOracles, agoric, pushPrices } from "@agoric/synthetic-chain"; - -const round = await agoric.follow( - '-lF', - ':published.priceFeed.ATOM-USD_price_feed.latestRound', -); -const oraclesByBrand = new Map(); -await addPreexistingOracles('ATOM', oraclesByBrand); - -await pushPrices(9.99, 'ATOM', oraclesByBrand, parseInt(round.roundId) + 1); \ No newline at end of file diff --git a/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts b/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts index e914a768ddba..7fdbcef510ab 100755 --- a/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts +++ b/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts @@ -15,9 +15,9 @@ import { setDebtLimit, } from '../lib/vaults.mjs'; -const START_FREQUENCY = 300; // StartFrequency: 600s (auction runs every 10m) +const START_FREQUENCY = 600; // StartFrequency: 600s (auction runs every 10m) const CLOCK_STEP = 20; // ClockStep: 20s (ensures auction completes in time) -const PRICE_LOCK_PERIOD = 60; +const PRICE_LOCK_PERIOD = 300; const oraclesAddresses = [GOV1ADDR, GOV2ADDR]; const oracles = [] as { address: string; id: string }[]; diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index 2b0d798401d2..3e9de8bd73de 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -23,4 +23,5 @@ echo ACCEPTANCE TESTING state sync ./state-sync-snapshots-test.sh ./genesis-test.sh +echo ACCEPTANCE TESTING auction yarn ava auction.test.js