From fe85bd9a66d95866d01ced19136c8e6b7939f08e Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 16 Mar 2023 12:43:46 -0700 Subject: [PATCH] feat(auction): durable offer book --- .../inter-protocol/src/auction/auctionBook.js | 10 +- .../inter-protocol/src/auction/offerBook.js | 248 ++++++++++-------- 2 files changed, 147 insertions(+), 111 deletions(-) diff --git a/packages/inter-protocol/src/auction/auctionBook.js b/packages/inter-protocol/src/auction/auctionBook.js index 6af7805c2752..9a94835af6a6 100644 --- a/packages/inter-protocol/src/auction/auctionBook.js +++ b/packages/inter-protocol/src/auction/auctionBook.js @@ -19,7 +19,7 @@ import { import { E } from '@endo/captp'; import { makeTracer } from '@agoric/internal'; -import { makeScaledBidBook, makePriceBook } from './offerBook.js'; +import { preparePriceBook, prepareScaledBidBook } from './offerBook.js'; import { isScaledBidPriceHigher, makeBrandedRatioPattern, @@ -168,13 +168,15 @@ export const makeAuctionBook = async ( let curAuctionPrice = zeroRatio; - // FIXME the maker callbacks return a non-durable object + const makeScaledBidBook = prepareScaledBidBook(baggage); + const makePriceBook = preparePriceBook(baggage); + const scaledBidBook = provide(baggage, 'scaledBidBook', () => { const ratioPattern = makeBrandedRatioPattern( currencyAmountShape, currencyAmountShape, ); - return makeScaledBidBook(baggage, ratioPattern, collateralBrand); + return makeScaledBidBook(ratioPattern, collateralBrand); }); const priceBook = provide(baggage, 'sortedOffers', () => { @@ -183,7 +185,7 @@ export const makeAuctionBook = async ( collateralAmountShape, ); - return makePriceBook(baggage, ratioPattern, collateralBrand); + return makePriceBook(ratioPattern, collateralBrand); }); /** diff --git a/packages/inter-protocol/src/auction/offerBook.js b/packages/inter-protocol/src/auction/offerBook.js index 8d9dc26a561a..0601d9d686fd 100644 --- a/packages/inter-protocol/src/auction/offerBook.js +++ b/packages/inter-protocol/src/auction/offerBook.js @@ -1,16 +1,15 @@ // book of offers to buy liquidating vaults with prices in terms of // discount/markup from the current oracle price. -import { Far } from '@endo/marshal'; -import { M, mustMatch } from '@agoric/store'; import { AmountMath } from '@agoric/ertp'; -import { provideDurableMapStore } from '@agoric/vat-data'; +import { M, mustMatch } from '@agoric/store'; +import { makeScalarBigMapStore, prepareExoClass } from '@agoric/vat-data'; import { toBidScalingComparator, - toScaledRateOfferKey, toPartialOfferKey, toPriceOfferKey, + toScaledRateOfferKey, } from './sortedOffers.js'; /** @typedef {import('@agoric/vat-data').Baggage} Baggage */ @@ -39,122 +38,157 @@ const nextSequenceNumber = () => { * snapshot taken when the auction started. .4 is 60% off. 1.1 is 10% above par. * * @param {Baggage} baggage - * @param {Pattern} bidScalingPattern - * @param {Brand} collateralBrand */ -export const makeScaledBidBook = ( - baggage, - bidScalingPattern, - collateralBrand, -) => { - /** @type {MapStore} */ - const store = provideDurableMapStore(baggage, 'scaledBidStore'); - - return Far('scaledBidBook ', { +export const prepareScaledBidBook = baggage => + prepareExoClass( + baggage, + 'scaledBidBook', + undefined, /** - * @param {ZCFSeat} seat - * @param {Ratio} bidScaling - * @param {Amount<'nat'>} wanted + * + * @param {Pattern} bidScalingPattern + * @param {Brand} collateralBrand */ - add(seat, bidScaling, wanted) { - mustMatch(bidScaling, bidScalingPattern); + (bidScalingPattern, collateralBrand) => ({ + bidScalingPattern, + collateralBrand, + /** @type {MapStore} */ + records: makeScalarBigMapStore('scaledBidRecords'), + }), + { + /** + * @param {ZCFSeat} seat + * @param {Ratio} bidScaling + * @param {Amount<'nat'>} wanted + */ + add(seat, bidScaling, wanted) { + const { bidScalingPattern, collateralBrand, records } = this.state; + mustMatch(bidScaling, bidScalingPattern); - const seqNum = nextSequenceNumber(); - const key = toScaledRateOfferKey(bidScaling, seqNum); - const empty = AmountMath.makeEmpty(collateralBrand); - /** @type {BidderRecord} */ - const bidderRecord = { - bidScaling, - price: undefined, - received: empty, - seat, - seqNum, - wanted, - }; - store.init(key, harden(bidderRecord)); - return key; - }, - /** @param {Ratio} bidScaling */ - offersAbove(bidScaling) { - return [...store.entries(M.gte(toBidScalingComparator(bidScaling)))]; - }, - hasOrders() { - return store.getSize() > 0; - }, - delete(key) { - store.delete(key); - }, - updateReceived(key, sold) { - const oldRec = store.get(key); - store.set( - key, - harden({ ...oldRec, received: AmountMath.add(oldRec.received, sold) }), - ); - }, - exitAllSeats() { - for (const [key, { seat }] of store.entries()) { - if (!seat.hasExited()) { - seat.exit(); - store.delete(key); + const seqNum = nextSequenceNumber(); + const key = toScaledRateOfferKey(bidScaling, seqNum); + const empty = AmountMath.makeEmpty(collateralBrand); + /** @type {BidderRecord} */ + const bidderRecord = { + bidScaling, + price: undefined, + received: empty, + seat, + seqNum, + wanted, + }; + records.init(key, harden(bidderRecord)); + return key; + }, + /** @param {Ratio} bidScaling */ + offersAbove(bidScaling) { + const { records } = this.state; + return [...records.entries(M.gte(toBidScalingComparator(bidScaling)))]; + }, + hasOrders() { + const { records } = this.state; + return records.getSize() > 0; + }, + delete(key) { + const { records } = this.state; + records.delete(key); + }, + updateReceived(key, sold) { + const { records } = this.state; + const oldRec = records.get(key); + records.set( + key, + harden({ + ...oldRec, + received: AmountMath.add(oldRec.received, sold), + }), + ); + }, + exitAllSeats() { + const { records } = this.state; + for (const [key, { seat }] of records.entries()) { + if (!seat.hasExited()) { + seat.exit(); + records.delete(key); + } } - } + }, }, - }); -}; + ); /** * Prices in this book are actual prices expressed in terms of currency amount * and collateral amount. * * @param {Baggage} baggage - * @param {Pattern} ratioPattern - * @param {Brand} collateralBrand */ -export const makePriceBook = (baggage, ratioPattern, collateralBrand) => { - /** @type {MapStore} */ - const store = provideDurableMapStore(baggage, 'pricedBidStore'); - return Far('priceBook ', { - add(seat, price, wanted) { - mustMatch(price, ratioPattern); +export const preparePriceBook = baggage => + prepareExoClass( + baggage, + 'priceBook', + undefined, + /** + * + * @param {Pattern} priceRatioPattern + * @param {Brand} collateralBrand + */ + (priceRatioPattern, collateralBrand) => ({ + priceRatioPattern, + collateralBrand, + /** @type {MapStore} */ + records: makeScalarBigMapStore('scaledBidRecords'), + }), + { + add(seat, price, wanted) { + const { priceRatioPattern, collateralBrand, records } = this.state; + mustMatch(price, priceRatioPattern); - const seqNum = nextSequenceNumber(); - const key = toPriceOfferKey(price, seqNum); - const empty = AmountMath.makeEmpty(collateralBrand); - /** @type {BidderRecord} */ - const bidderRecord = { - bidScaling: undefined, - price, - received: empty, - seat, - seqNum, - wanted, - }; - store.init(key, harden(bidderRecord)); - return key; - }, - offersAbove(price) { - return [...store.entries(M.gte(toPartialOfferKey(price)))]; - }, - hasOrders() { - return store.getSize() > 0; - }, - delete(key) { - store.delete(key); - }, - updateReceived(key, sold) { - const oldRec = store.get(key); - store.set( - key, - harden({ ...oldRec, received: AmountMath.add(oldRec.received, sold) }), - ); - }, - exitAllSeats() { - for (const [key, { seat }] of store.entries()) { - if (!seat.hasExited()) { - seat.exit(); - store.delete(key); + const seqNum = nextSequenceNumber(); + const key = toPriceOfferKey(price, seqNum); + const empty = AmountMath.makeEmpty(collateralBrand); + /** @type {BidderRecord} */ + const bidderRecord = { + bidScaling: undefined, + price, + received: empty, + seat, + seqNum, + wanted, + }; + records.init(key, harden(bidderRecord)); + return key; + }, + offersAbove(price) { + const { records } = this.state; + return [...records.entries(M.gte(toPartialOfferKey(price)))]; + }, + hasOrders() { + const { records } = this.state; + return records.getSize() > 0; + }, + delete(key) { + const { records } = this.state; + records.delete(key); + }, + updateReceived(key, sold) { + const { records } = this.state; + const oldRec = records.get(key); + records.set( + key, + harden({ + ...oldRec, + received: AmountMath.add(oldRec.received, sold), + }), + ); + }, + exitAllSeats() { + const { records } = this.state; + for (const [key, { seat }] of records.entries()) { + if (!seat.hasExited()) { + seat.exit(); + records.delete(key); + } } - } + }, }, - }); -}; + );