diff --git a/packages/inter-protocol/src/auction/auctionBook.js b/packages/inter-protocol/src/auction/auctionBook.js index 548be5f0943..672c3ce7fe9 100644 --- a/packages/inter-protocol/src/auction/auctionBook.js +++ b/packages/inter-protocol/src/auction/auctionBook.js @@ -555,9 +555,12 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { trace(this.state.collateralBrand, 'settleAtNewRate', reduction); const { capturedPriceForRound, priceBook, scaledBidBook } = state; - capturedPriceForRound !== null || - Fail`price must be captured before auction starts`; - assert(capturedPriceForRound); + if (!capturedPriceForRound) { + console.error( + `⚠️No price for ${this.state.collateralBrand}, skipping auction.`, + ); + return; + } state.curAuctionPrice = multiplyRatios( reduction, diff --git a/packages/inter-protocol/src/auction/scheduler.js b/packages/inter-protocol/src/auction/scheduler.js index c1d05b18b5f..d1b5c1466bf 100644 --- a/packages/inter-protocol/src/auction/scheduler.js +++ b/packages/inter-protocol/src/auction/scheduler.js @@ -323,7 +323,17 @@ export const makeScheduler = async ( async updateState(_newState) { trace('received param update', _newState); await null; + + let fixableSchedule; if (!nextSchedule) { + fixableSchedule = true; + } else { + now = await E(timer).getCurrentTimestamp(); + fixableSchedule = + TimeMath.compareAbs(nextSchedule.startTime, now) < 0; + } + + if (fixableSchedule) { trace('repairing nextSchedule and restarting'); ({ nextSchedule } = await initializeNextSchedule()); startSchedulingFromScratch(); diff --git a/packages/inter-protocol/test/auction/test-auctionContract.js b/packages/inter-protocol/test/auction/test-auctionContract.js index 16960075028..f953b605098 100644 --- a/packages/inter-protocol/test/auction/test-auctionContract.js +++ b/packages/inter-protocol/test/auction/test-auctionContract.js @@ -18,6 +18,7 @@ import { assertPayoutAmount } from '@agoric/zoe/test/zoeTestHelpers.js'; import { makeManualPriceAuthority } from '@agoric/zoe/tools/manualPriceAuthority.js'; import { providePriceAuthorityRegistry } from '@agoric/zoe/tools/priceAuthorityRegistry.js'; import { E } from '@endo/eventual-send'; +import { NonNullish } from '@agoric/assert'; import { setupReserve, @@ -110,6 +111,11 @@ export const setupServices = async (t, params = defaultParams) => { const space = await setupBootstrap(t, timer); installPuppetGovernance(zoe, space.installation.produce); + t.context.puppetGovernors = { + auctioneer: E.get(space.consume.auctioneerKit).governorCreatorFacet, + vaultFactory: E.get(space.consume.vaultFactoryKit).governorCreatorFacet, + }; + // @ts-expect-error not all installs are needed for auctioneer. produceInstallations(space, t.context.installs); @@ -309,6 +315,15 @@ const makeAuctionDriver = async (t, params = defaultParams) => { await eventLoopIteration(); } }, + + setGovernedParam: (name, newValue) => { + trace(t, 'setGovernedParam', name); + const auctionGov = NonNullish(t.context.puppetGovernors.auctioneer); + return E(auctionGov).changeParams( + harden({ changes: { [name]: newValue } }), + ); + }, + async updatePriceAuthority(newPrice) { priceAuthorities.get(newPrice.denominator.brand).setPrice(newPrice); await eventLoopIteration(); @@ -1408,3 +1423,75 @@ test.serial('time jumps forward', async t => { t.is(schedules.liveAuctionSchedule?.startTime.absValue, 1530n); t.is(schedules.liveAuctionSchedule?.endTime.absValue, 1550n); }); + +// serial because dynamicConfig is shared across tests +test.serial('add collateral type during auction', async t => { + const { collateral, bid } = t.context; + const driver = await makeAuctionDriver(t); + const asset = withAmountUtils(makeIssuerKit('Asset')); + const timerBrand = await E(driver.getTimerService()).getTimerBrand(); + + const liqSeat = await driver.setupCollateralAuction( + collateral, + collateral.make(1000n), + ); + await driver.updatePriceAuthority( + makeRatioFromAmounts(bid.make(11n), collateral.make(10n)), + ); + + const result = await E(liqSeat).getOfferResult(); + t.is(result, 'deposited'); + + await driver.advanceTo(167n); + const seat = await driver.bidForCollateralSeat( + // 1.1 * 1.05 * 200 + bid.make(231n), + collateral.make(200n), + ); + t.is(await E(seat).getOfferResult(), 'Your bid has been accepted'); + t.false(await E(seat).hasExited()); + + const scheduleTracker = await driver.getScheduleTracker(); + await scheduleTracker.assertInitial({ + activeStartTime: TimeMath.coerceTimestampRecord(170n, timerBrand), + nextDescendingStepTime: TimeMath.coerceTimestampRecord(170n, timerBrand), + nextStartTime: TimeMath.coerceTimestampRecord(210n, timerBrand), + }); + + await driver.advanceTo(170n, 'wait'); + await scheduleTracker.assertChange({ + nextDescendingStepTime: { absValue: 175n }, + }); + + await driver.advanceTo(175n, 'wait'); + await scheduleTracker.assertChange({ + nextDescendingStepTime: { absValue: 180n }, + }); + + // before the fix for #8296 in AuctionBook, this broke the ongoing auction. + await driver.setupCollateralAuction(asset, asset.make(500n)); + + await driver.advanceTo(180n, 'wait'); + await scheduleTracker.assertChange({ + nextDescendingStepTime: { absValue: 185n }, + }); + + await driver.advanceTo(185n, 'wait'); + + await scheduleTracker.assertChange({ + nextDescendingStepTime: { absValue: 190n }, + }); + + t.true(await E(seat).hasExited()); + await assertPayouts(t, seat, bid, collateral, 0n, 200n); + + await driver.advanceTo(210n, 'wait'); + + t.true(await E(liqSeat).hasExited()); + await assertPayouts(t, liqSeat, bid, collateral, 231n, 800n); + + await scheduleTracker.assertChange({ + activeStartTime: null, + nextDescendingStepTime: { absValue: 210n }, + }); +});