From 8b1c58297665f224018110317a3768587594121a Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 29 Oct 2020 13:46:39 -0600 Subject: [PATCH] fix: add more types and update APIs --- packages/zoe/tools/manualTimer.js | 79 ++++++++++---- packages/zoe/tools/priceAuthorityRegistry.js | 86 +++++++-------- packages/zoe/tools/types.js | 104 ++++++++++++++----- 3 files changed, 178 insertions(+), 91 deletions(-) diff --git a/packages/zoe/tools/manualTimer.js b/packages/zoe/tools/manualTimer.js index 42c51e34e43..4fde0f6a7a5 100644 --- a/packages/zoe/tools/manualTimer.js +++ b/packages/zoe/tools/manualTimer.js @@ -1,37 +1,77 @@ +// @ts-check + import { E } from '@agoric/eventual-send'; +import { makeStore } from '@agoric/store'; + +import './types'; + +/** + * @typedef {Object} ManualTimerAdmin + * @property {(msg: string | undefined) => Promise} tick + */ -// A fake clock that also logs progress in tests. +/** + * @typedef {ManualTimerAdmin & TimerService} ManualTimer + */ + +/** + * A fake clock that also logs progress. + * + * @param {(...args: any[]) => void} log + * @param {Timestamp} [startValue=0] + * @returns {ManualTimer} + */ export default function buildManualTimer(log, startValue = 0) { let ticks = startValue; - const schedule = new Map(); + + /** @type {Store>} */ + const schedule = makeStore('Timestamp'); + + /** @type {ManualTimer} */ const timer = { - setWakeup(deadline, handler) { - if (deadline <= ticks) { - log(`&& task was past its deadline when scheduled: ${deadline} &&`); - handler.wake(ticks); - return undefined; - } - log(`@@ schedule task for:${deadline}, currently: ${ticks} @@`); - if (!schedule.has(deadline)) { - schedule.set(deadline, []); - } - schedule.get(deadline).push(handler); - return deadline; - }, // This function will only be called in testing code to advance the clock. async tick(msg) { ticks += 1; log(`@@ tick:${ticks}${msg ? `: ${msg}` : ''} @@`); if (schedule.has(ticks)) { + const handlers = schedule.get(ticks); + schedule.delete(ticks); await Promise.allSettled( - schedule.get(ticks).map(h => { + handlers.map(h => { log(`&& running a task scheduled for ${ticks}. &&`); return E(h).wake(ticks); }), ); } }, - createRepeater(delaySecs, interval) { + getCurrentTimestamp() { + return ticks; + }, + setWakeup(baseTime, handler) { + if (baseTime <= ticks) { + log(`&& task was past its deadline when scheduled: ${baseTime} &&`); + handler.wake(ticks); + return undefined; + } + log(`@@ schedule task for:${baseTime}, currently: ${ticks} @@`); + if (!schedule.has(baseTime)) { + schedule.init(baseTime, []); + } + schedule.get(baseTime).push(handler); + return baseTime; + }, + removeWakeup(handler) { + const baseTimes = []; + for (const [baseTime, handlers] of schedule.entries()) { + const index = handlers.indexOf(handler); + if (index >= 0) { + handlers.splice(index, 1); + baseTimes.push(baseTime); + } + } + return baseTimes; + }, + createRepeater(baseTime, interval) { let handlers = []; const repeater = { schedule(h) { @@ -51,12 +91,9 @@ export default function buildManualTimer(log, startValue = 0) { await Promise.allSettled(handlers.map(h => E(h).wake(timestamp))); }, }; - timer.setWakeup(ticks + delaySecs, repeaterHandler); + timer.setWakeup(ticks + baseTime, repeaterHandler); return repeater; }, - getCurrentTimestamp() { - return ticks; - }, }; harden(timer); return timer; diff --git a/packages/zoe/tools/priceAuthorityRegistry.js b/packages/zoe/tools/priceAuthorityRegistry.js index 2efb4cdb99c..28570cd4ec7 100644 --- a/packages/zoe/tools/priceAuthorityRegistry.js +++ b/packages/zoe/tools/priceAuthorityRegistry.js @@ -13,9 +13,9 @@ import '../exported'; /** * @typedef {Object} PriceAuthorityRegistryAdmin - * @property {(pa: ERef, assetBrand: Brand, priceBrand: Brand) + * @property {(pa: ERef, brandIn: Brand, brandOut: Brand) * => Deleter} registerPriceAuthority Add a unique price authority for a given - * asset/price pair + * pair */ /** @@ -37,15 +37,15 @@ export const makePriceAuthorityRegistry = () => { */ /** @type {Store>} */ - const assetToPriceStore = makeStore('assetBrand'); + const assetToPriceStore = makeStore('brandIn'); /** - * @param {Brand} assetBrand - * @param {Brand} priceBrand + * @param {Brand} brandIn + * @param {Brand} brandOut */ - const lookup = (assetBrand, priceBrand) => { - const priceStore = assetToPriceStore.get(assetBrand); - return priceStore.get(priceBrand); + const lookup = (brandIn, brandOut) => { + const priceStore = assetToPriceStore.get(brandIn); + return priceStore.get(brandOut); }; /** @@ -55,59 +55,59 @@ export const makePriceAuthorityRegistry = () => { * @type {PriceAuthority} */ const priceAuthority = { - async getQuoteIssuer(assetBrand, priceBrand) { - const record = lookup(assetBrand, priceBrand); - return E(record.priceAuthority).getQuoteIssuer(assetBrand, priceBrand); + async getQuoteIssuer(brandIn, brandOut) { + const record = lookup(brandIn, brandOut); + return E(record.priceAuthority).getQuoteIssuer(brandIn, brandOut); }, - async getInputPrice(amountIn, brandOut) { + async getAmountInQuote(amountIn, brandOut) { const record = lookup(amountIn.brand, brandOut); - return E(record.priceAuthority).getInputPrice(amountIn, brandOut); + return E(record.priceAuthority).getAmountInQuote(amountIn, brandOut); }, - async getOutputPrice(amountOut, brandIn) { + async getAmountOutQuote(brandIn, amountOut) { const record = lookup(brandIn, amountOut.brand); - return E(record.priceAuthority).getOutputPrice(amountOut, brandIn); + return E(record.priceAuthority).getAmountOutQuote(brandIn, amountOut); }, - async getPriceNotifier(assetBrand, priceBrand) { - const record = lookup(assetBrand, priceBrand); - return E(record.priceAuthority).getPriceNotifier(assetBrand, priceBrand); + async getPriceNotifier(brandIn, brandOut) { + const record = lookup(brandIn, brandOut); + return E(record.priceAuthority).getPriceNotifier(brandIn, brandOut); }, - async priceAtTime(timer, deadline, assetAmount, priceBrand) { - const record = lookup(assetAmount.brand, priceBrand); - return E(record.priceAuthority).priceAtTime( + async quoteAtTime(timer, deadline, amountIn, brandOut) { + const record = lookup(amountIn.brand, brandOut); + return E(record.priceAuthority).quoteAtTime( timer, deadline, - assetAmount, - priceBrand, + amountIn, + brandOut, ); }, - async priceWhenLT(assetAmount, priceLimit) { - const record = lookup(assetAmount.brand, priceLimit.brand); - return E(record.priceAuthority).priceWhenLT(assetAmount, priceLimit); + async quoteWhenLT(amountIn, amountOutLimit) { + const record = lookup(amountIn.brand, amountOutLimit.brand); + return E(record.priceAuthority).quoteWhenLT(amountIn, amountOutLimit); }, - async priceWhenLTE(assetAmount, priceLimit) { - const record = lookup(assetAmount.brand, priceLimit.brand); - return E(record.priceAuthority).priceWhenLTE(assetAmount, priceLimit); + async quoteWhenLTE(amountIn, amountOutLimit) { + const record = lookup(amountIn.brand, amountOutLimit.brand); + return E(record.priceAuthority).quoteWhenLTE(amountIn, amountOutLimit); }, - async priceWhenGTE(assetAmount, priceLimit) { - const record = lookup(assetAmount.brand, priceLimit.brand); - return E(record.priceAuthority).priceWhenGT(assetAmount, priceLimit); + async quoteWhenGTE(amountIn, amountOutLimit) { + const record = lookup(amountIn.brand, amountOutLimit.brand); + return E(record.priceAuthority).quoteWhenGT(amountIn, amountOutLimit); }, - async priceWhenGT(assetAmount, priceLimit) { - const record = lookup(assetAmount.brand, priceLimit.brand); - return E(record.priceAuthority).priceWhenGT(assetAmount, priceLimit); + async quoteWhenGT(amountIn, amountOutLimit) { + const record = lookup(amountIn.brand, amountOutLimit.brand); + return E(record.priceAuthority).quoteWhenGT(amountIn, amountOutLimit); }, }; /** @type {PriceAuthorityRegistryAdmin} */ const adminFacet = { - registerPriceAuthority(pa, assetBrand, priceBrand) { + registerPriceAuthority(pa, brandIn, brandOut) { /** @type {Store} */ let priceStore; - if (assetToPriceStore.has(assetBrand)) { - priceStore = assetToPriceStore.get(assetBrand); + if (assetToPriceStore.has(brandIn)) { + priceStore = assetToPriceStore.get(brandIn); } else { - priceStore = makeStore('priceBrand'); - assetToPriceStore.init(assetBrand, priceStore); + priceStore = makeStore('brandOut'); + assetToPriceStore.init(brandIn, priceStore); } // Put a box around the authority so that we can be ensured the deleter @@ -117,16 +117,16 @@ export const makePriceAuthorityRegistry = () => { }; // Set up the record. - priceStore.init(priceBrand, harden(record)); + priceStore.init(brandOut, harden(record)); return harden({ delete() { assert.equal( - priceStore.has(priceBrand) && priceStore.get(priceBrand), + priceStore.has(brandOut) && priceStore.get(brandOut), record, details`Price authority already dropped`, ); - priceStore.delete(priceBrand); + priceStore.delete(brandOut); }, }); }, diff --git a/packages/zoe/tools/types.js b/packages/zoe/tools/types.js index bb053696180..5b5e4684dad 100644 --- a/packages/zoe/tools/types.js +++ b/packages/zoe/tools/types.js @@ -1,42 +1,92 @@ +/** + * @typedef {Object} TimerService Gives the ability to get the current time, + * schedule a single wake() call, create a repeater that will allow scheduling + * of events at regular intervals, or remove scheduled calls. + * @property {() => Timestamp} getCurrentTimestamp Retrieve the latest timestamp + * @property {(baseTime: Timestamp, handler: TimerServiceHandler) => Timestamp} + * setWakeup Return value is the time at which the call is scheduled to take + * place + * @property {(handler: TimerServiceHandler) => Array} removeWakeup Remove + * the handler from all its scheduled wakeup, whether produced by + * `timer.setWakeup(h)` or `repeater.schedule(h)`. + * @property {(baseTime: Timestamp, interval: RelativeTime) => TimerRepeater} + * createRepeater Create and return a repeater that will schedule `wake()` calls + * repeatedly at times that are a multiple of interval following baseTime. + * Interval is the delay between successive times at which wake will be called. + * When `schedule(h)` is called, `h.wake()` will be scheduled to be called after + * the next multiple of interval from the base. Since times can be + * coarse-grained, the actual call may occur later, but this won't change when + * the next event will be called. + */ + +/** + * @typedef {number} Timestamp An absolute individual stamp returned by a + * TimerService. Note that different timer services may have different + * interpretations of actual Timestamp values. + * @typedef {number} RelativeTime Difference between two Timestamps. Note that + * different timer services may have different interpretations of actual + * RelativeTime values. + */ + +/** + * @typedef {Object} TimerServiceHandler + * @property {(timestamp: Timestamp) => void} wake The timestamp passed to + * `wake()` is the time that the call was scheduled to occur. + */ + +/** + * @typedef {Object} TimerRepeater + * @property {(handler: TimerHandler) => void} schedule Returns the time + * scheduled for the first call on handler. The handler will continue to be + * scheduled for a `wake()` call every interval until the repeater is disabled. + * @property {() => void} disable Disable this repeater, so `schedule()` can't + * be called, and handlers already scheduled with this repeater won't be + * rescheduled again after `wake()` is next called on them. + */ + /** * @typedef {Object} PriceQuote - * @property {Payment} quotePayment The quote wrapped as a payment - * @property {Amount} quoteAmount Amount of `quotePayment` (`quoteIssuer.getAmountOf(quotePayment)`) + * @property {Amount} quoteAmount Amount whose value is a PriceQuoteValue + * @property {Payment} quotePayment The `quoteAmount` wrapped as a payment + */ + +/** + * @typedef {Array} PriceQuoteValue A set of PriceDescriptions */ /** - * @typedef {Object} PriceQuoteValue An individual quote's value - * @property {Amount} assetAmount The amount of the asset being quoted - * @property {Amount} price The quoted price for the `assetAmount` + * @typedef {Object} PriceDescription A description of a single quote + * @property {Amount} amountIn The amount supplied to a trade + * @property {Amount} amountOut The quoted result of trading `amountIn` * @property {TimerService} timer The service that gave the `timestamp` - * @property {number} timestamp A timestamp for the quote according to `timer` + * @property {Timestamp} timestamp A timestamp according to `timer` for the + * quote * @property {any=} conditions Additional conditions for the quote */ /** * @typedef {Object} PriceAuthority An object that mints PriceQuotes and handles * triggers and notifiers for changes in the price - * @property {(assetBrand: Brand, priceBrand: Brand) => ERef} - * getQuoteIssuer Get the ERTP issuer of PriceQuotes + * @property {(brandIn: Brand, brandOut: Brand) => ERef} getQuoteIssuer + * Get the ERTP issuer of PriceQuotes * @property {(amountIn: Amount, brandOut: Brand) => Promise} - * getInputPrice calculate the amount of brandOut that will be returned if the - * amountIn is sold at the current price - * @property {(amountOut: Amount, brandIn: Brand) => Promise} - * getOutputPrice calculate the amount of brandIn that is required in order to - * get amountOut using the current price - * @property {(assetBrand: Brand, priceBrand: Brand) => ERef>} + * getAmountInQuote get a quote corresponding to the specified amountIn + * @property {(brandIn: Brand, amountOut: Amount) => Promise} + * getAmountOutQuote get a quote corresponding to the specified amountOut + * @property {(brandIn: Brand, brandOut: Brand) => ERef>} * getPriceNotifier - * @property {(timer: TimerService, deadline: number, assetAmount: Amount, - * priceBrand: Brand) => Promise} priceAtTime Resolves after - * `deadline` passes on `timer` with the price of `assetAmount` at that time - * @property {(assetAmount: Amount, priceLimit: Amount) => Promise} - * priceWhenGT Resolve when the price of `assetAmount` exceeds `priceLimit` - * @property {(assetAmount: Amount, priceLimit: Amount) => Promise} - * priceWhenGTE Resolve when the price of `assetAmount` reaches or exceeds - * `priceLimit` - * @property {(assetAmount: Amount, priceLimit: Amount) => Promise} - * priceWhenLTE Resolve when the price of `assetAmount` reaches or drops below - * `priceLimit` - * @property {(assetAmount: Amount, priceLimit: Amount) => Promise} - * priceWhenLT Resolve when the price of `assetAmount` drops below `priceLimit` + * @property {(timer: TimerService, deadline: number, amountIn: Amount, + * brandOut: Brand) => Promise} quoteAtTime Resolves after + * `deadline` passes on `timer` with the price quote of `amountIn` at that time + * @property {(amountIn: Amount, amountOutLimit: Amount) => Promise} + * quoteWhenGT Resolve when a price quote of `amountIn` exceeds `amountOutLimit` + * @property {(amountIn: Amount, amountOutLimit: Amount) => Promise} + * quoteWhenGTE Resolve when a price quote of `amountIn` reaches or exceeds + * `amountOutLimit` + * @property {(amountIn: Amount, amountOutLimit: Amount) => Promise} + * quoteWhenLTE Resolve when a price quote of `amountIn` reaches or drops below + * `amountOutLimit` + * @property {(amountIn: Amount, amountOutLimit: Amount) => Promise} + * quoteWhenLT Resolve when the price quote of `amountIn` drops below + * `amountOutLimit` */