diff --git a/packages/inter-protocol/package.json b/packages/inter-protocol/package.json index 6c2115f9d10..23e85f1f1e7 100644 --- a/packages/inter-protocol/package.json +++ b/packages/inter-protocol/package.json @@ -45,6 +45,7 @@ "@endo/far": "^0.2.18", "@endo/marshal": "^0.8.5", "@endo/nat": "^4.1.27", + "@endo/promise-kit": "^0.2.56", "jessie.js": "^0.3.2" }, "devDependencies": { diff --git a/packages/inter-protocol/src/auction/auctionBook.js b/packages/inter-protocol/src/auction/auctionBook.js index 5c5e701ba3a..60c3683eb6d 100644 --- a/packages/inter-protocol/src/auction/auctionBook.js +++ b/packages/inter-protocol/src/auction/auctionBook.js @@ -3,8 +3,12 @@ import '@agoric/zoe/exported.js'; import '@agoric/zoe/src/contracts/exported.js'; import { AmountMath } from '@agoric/ertp'; -import { mustMatch, makeScalarMapStore } from '@agoric/store'; -import { M, prepareExoClassKit, provide } from '@agoric/vat-data'; +import { mustMatch } from '@agoric/store'; +import { + M, + prepareExoClassKit, + provideDurableMapStore, +} from '@agoric/vat-data'; import { assertAllDefined, makeTracer } from '@agoric/internal'; import { @@ -18,7 +22,6 @@ import { } from '@agoric/zoe/src/contractSupport/index.js'; import { E } from '@endo/captp'; import { observeNotifier } from '@agoric/notifier'; -import { ToFarFunction } from '@endo/marshal'; import { makeNatAmountShape } from '../contractSupport.js'; import { preparePriceBook, prepareScaledBidBook } from './offerBook.js'; @@ -69,8 +72,8 @@ const trace = makeTracer('AucBook', true); * @param {Brand<'nat'>} collateralBrand */ export const makeOfferSpecShape = (bidBrand, collateralBrand) => { - const bidAmountShape = makeNatAmountShape(bidBrand); - const collateralAmountShape = makeNatAmountShape(collateralBrand); + const bidAmountShape = makeNatAmountShape(bidBrand, 0n); + const collateralAmountShape = makeNatAmountShape(collateralBrand, 0n); return M.splitRecord( { maxBuy: collateralAmountShape }, { @@ -131,9 +134,7 @@ export const makeOfferSpecShape = (bidBrand, collateralBrand) => { * @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit} makeRecorderKit */ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { - const bidDataKits = provide(baggage, 'bidDataKits', () => - makeScalarMapStore('BidDataKits'), - ); + const bidDataKits = provideDurableMapStore(baggage, 'bidDataKits'); const makeScaledBidBook = prepareScaledBidBook(baggage, makeRecorderKit); const makePriceBook = preparePriceBook(baggage, makeRecorderKit); @@ -166,7 +167,7 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { * @param {Brand<'nat'>} bidBrand * @param {Brand<'nat'>} collateralBrand * @param {PriceAuthority} pAuthority - * @param {Array>} nodes + * @param {Array} nodes */ (bidBrand, collateralBrand, pAuthority, nodes) => { assertAllDefined({ bidBrand, collateralBrand, pAuthority }); @@ -184,33 +185,28 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { const { zcfSeat: bidHoldingSeat } = zcf.makeEmptySeatKit(); const [scheduleNode, bidsNode] = nodes; - const bidAmountShape = makeNatAmountShape(bidBrand); - const collateralAmountShape = makeNatAmountShape(collateralBrand); - const makeBidNode = ToFarFunction('makeBidNode', bidId => - E(bidsNode).makeChildNode(`bids${bidId}`), - ); + const bidAmountShape = makeNatAmountShape(bidBrand, 0n); + const collateralAmountShape = makeNatAmountShape(collateralBrand, 0n); const scaledBidBook = makeScaledBidBook( makeBrandedRatioPattern(bidAmountShape, bidAmountShape), collateralBrand, - makeBidNode, + bidsNode, ); const priceBook = makePriceBook( makeBrandedRatioPattern(bidAmountShape, collateralAmountShape), collateralBrand, - makeBidNode, + bidsNode, ); const bookDataKit = makeRecorderKit( - // @ts-expect-error ERef should be acceptable scheduleNode, /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( M.any() ), ); const bidsDataKit = makeRecorderKit( - // @ts-expect-error ERef should be acceptable bidsNode, /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( M.any() diff --git a/packages/inter-protocol/src/auction/auctioneer.js b/packages/inter-protocol/src/auction/auctioneer.js index 52b27bb7ece..6abdaf5da4c 100644 --- a/packages/inter-protocol/src/auction/auctioneer.js +++ b/packages/inter-protocol/src/auction/auctioneer.js @@ -691,9 +691,12 @@ export const start = async (zcf, privateArgs, baggage) => { const bookId = `book${bookCounter}`; bookCounter += 1; - const bookNode = E(privateArgs.storageNode).makeChildNode(bookId); - const scheduleNode = E(bookNode).makeChildNode('schedule'); - const bidsNode = E(bookNode).makeChildNode('bids'); + const bookNodeP = E(privateArgs.storageNode).makeChildNode(bookId); + const [scheduleNode, bidsNode] = await Promise.all([ + bookNodeP, + E(bookNodeP).makeChildNode('schedule'), + E(bookNodeP).makeChildNode('bids'), + ]); const newBook = await makeAuctionBook( brands.Bid, diff --git a/packages/inter-protocol/src/auction/offerBook.js b/packages/inter-protocol/src/auction/offerBook.js index ffa88c3cfb1..75d4abf90f1 100644 --- a/packages/inter-protocol/src/auction/offerBook.js +++ b/packages/inter-protocol/src/auction/offerBook.js @@ -1,13 +1,17 @@ // book of offers to buy liquidating vaults with prices in terms of // discount/markup from the current oracle price. -import { AmountMath } from '@agoric/ertp'; +import { E } from '@endo/captp'; +import { AmountMath, BrandShape } from '@agoric/ertp'; +import { StorageNodeShape } from '@agoric/internal'; import { M, mustMatch } from '@agoric/store'; import { makeScalarBigMapStore, + makeScalarMapStore, prepareExoClass, provide, } from '@agoric/vat-data'; +import { makePromiseKit } from '@endo/promise-kit'; import { toBidScalingComparator, @@ -39,11 +43,25 @@ const nextSequenceNumber = baggage => { const ScaledBidBookStateShape = harden({ bidScalingPattern: M.any(), - collateralBrand: M.any(), + collateralBrand: BrandShape, records: M.any(), - makeBidNode: M.any(), + bidsNode: StorageNodeShape, }); +const makeBidNode = (bidsNode, bidId) => + E(bidsNode).makeChildNode(`bid${bidId}`); + +const makeGetBidDataRecorder = (bidDataKits, bidDataKitPromises) => { + return key => { + if (bidDataKitPromises.has(key)) { + return E.get(bidDataKitPromises.get(key)).recorder; + } + return bidDataKits.get(key).recorder; + }; +}; + +/** @typedef {ReturnType} RecorderKit */ + /** * Prices in this book are expressed as percentage of the full oracle price * snapshot taken when the auction started. .4 is 60% off. 1.1 is 10% above par. @@ -57,23 +75,28 @@ export const prepareScaledBidBook = (baggage, makeRecorderKit) => { // each offer for uniqueness. const bidDataKits = baggage.get('bidDataKits'); + /** @type {MapStore>} */ + const bidDataKitPromises = makeScalarMapStore('bidDataKit Promises'); + const getBidDataRecorder = makeGetBidDataRecorder( + bidDataKits, + bidDataKitPromises, + ); return prepareExoClass( baggage, 'scaledBidBook', undefined, /** - * * @param {Pattern} bidScalingPattern * @param {Brand} collateralBrand - * @param {(BigInteger) => Promise} makeBidNode + * @param {StorageNode} bidsNode */ - (bidScalingPattern, collateralBrand, makeBidNode) => ({ + (bidScalingPattern, collateralBrand, bidsNode) => ({ bidScalingPattern, collateralBrand, /** @type {MapStore} */ records: makeScalarBigMapStore('scaledBidRecords', { durable: true }), - makeBidNode, + bidsNode, }), { /** @@ -84,23 +107,29 @@ export const prepareScaledBidBook = (baggage, makeRecorderKit) => { * @param {Timestamp} timestamp */ add(seat, bidScaling, wanted, exitAfterBuy, timestamp) { - const { bidScalingPattern, collateralBrand, records, makeBidNode } = + const { bidScalingPattern, collateralBrand, records, bidsNode } = this.state; mustMatch(bidScaling, bidScalingPattern); const seqNum = nextSequenceNumber(baggage); const key = toScaledRateOfferKey(bidScaling, seqNum); - // @ts-expect-error makeRecorderKit accepts ERef - const bidDataKit = makeRecorderKit(makeBidNode(seqNum), M.any()); - bidDataKits.init(key, bidDataKit); + /** @type {PromiseKit} */ + const bidDataKitP = makePromiseKit(); + bidDataKitPromises.init(key, bidDataKitP.promise); + E.when(makeBidNode(bidsNode, seqNum), childBidNode => { + const recorderKit = makeRecorderKit(childBidNode); + bidDataKits.init(key, recorderKit); + bidDataKitP.resolve(recorderKit); + bidDataKitPromises.delete(key); + return recorderKit; + }); - const empty = AmountMath.makeEmpty(collateralBrand); /** @type {BidderRecord} */ const bidderRecord = { bidScaling, price: undefined, - received: empty, + received: AmountMath.makeEmpty(collateralBrand), seat, seqNum, wanted, @@ -108,7 +137,6 @@ export const prepareScaledBidBook = (baggage, makeRecorderKit) => { timestamp, }; records.init(key, harden(bidderRecord)); - bidDataKits.init(seqNum); return key; }, /** @param {Ratio} bidScaling */ @@ -119,7 +147,7 @@ export const prepareScaledBidBook = (baggage, makeRecorderKit) => { publishOffer(record) { const key = toScaledRateOfferKey(record.bidScaling, record.seqNum); - bidDataKits.get(key).recorder.write( + return E(getBidDataRecorder(key)).write( harden({ bidScaling: record.bidScaling, wanted: record.wanted, @@ -161,7 +189,9 @@ export const prepareScaledBidBook = (baggage, makeRecorderKit) => { for (const [key, { seat }] of records.entries()) { if (!seat.hasExited()) { seat.exit(); - bidDataKits.delete(key); + if (bidDataKits.has(key)) { + bidDataKits.delete(key); + } records.delete(key); } } @@ -175,9 +205,9 @@ export const prepareScaledBidBook = (baggage, makeRecorderKit) => { const PriceBookStateShape = harden({ priceRatioPattern: M.any(), - collateralBrand: M.any(), + collateralBrand: BrandShape, records: M.any(), - makeBidNode: M.any(), + bidsNode: StorageNodeShape, }); /** @@ -189,23 +219,28 @@ const PriceBookStateShape = harden({ */ export const preparePriceBook = (baggage, makeRecorderKit) => { const bidDataKits = baggage.get('bidDataKits'); + /** @type {MapStore>} */ + const bidDataKitPromises = makeScalarMapStore('bidDataKit Promises'); + const getBidDataRecorder = makeGetBidDataRecorder( + bidDataKits, + bidDataKitPromises, + ); return prepareExoClass( baggage, 'priceBook', undefined, /** - * * @param {Pattern} priceRatioPattern * @param {Brand} collateralBrand - * @param {(BigInteger) => Promise} makeBidNode + * @param {StorageNode} bidsNode */ - (priceRatioPattern, collateralBrand, makeBidNode) => ({ + (priceRatioPattern, collateralBrand, bidsNode) => ({ priceRatioPattern, collateralBrand, /** @type {MapStore} */ records: makeScalarBigMapStore('scaledBidRecords', { durable: true }), - makeBidNode, + bidsNode, }), { /** @@ -216,33 +251,36 @@ export const preparePriceBook = (baggage, makeRecorderKit) => { * @param {Timestamp} timestamp */ add(seat, price, wanted, exitAfterBuy, timestamp) { - const { priceRatioPattern, collateralBrand, records, makeBidNode } = + const { priceRatioPattern, collateralBrand, records, bidsNode } = this.state; mustMatch(price, priceRatioPattern); const seqNum = nextSequenceNumber(baggage); const key = toPriceOfferKey(price, seqNum); - // @ts-expect-error makeRecorderKit accepts ERef - const bidDataKit = makeRecorderKit(makeBidNode(seqNum), M.any()); - bidDataKits.init(key, bidDataKit); - - const empty = AmountMath.makeEmpty(collateralBrand); - records.init( - key, - harden({ - bidScaling: undefined, - price, - received: empty, - seat, - seqNum, - wanted, - exitAfterBuy, - timestamp, - }), - ); + /** @type {PromiseKit} */ + const bidDataKitP = makePromiseKit(); + bidDataKitPromises.init(key, bidDataKitP.promise); + E.when(makeBidNode(bidsNode, seqNum), childBidNode => { + const recorderKit = makeRecorderKit(childBidNode); + bidDataKits.init(key, recorderKit); + bidDataKitP.resolve(recorderKit); + bidDataKitPromises.delete(key); + return recorderKit; + }); - bidDataKits.init(seqNum); + /** @type {BidderRecord} */ + const bidderRecord = harden({ + bidScaling: undefined, + price, + received: AmountMath.makeEmpty(collateralBrand), + seat, + seqNum, + wanted, + exitAfterBuy, + timestamp, + }); + records.init(key, bidderRecord); return key; }, offersAbove(price) { @@ -252,7 +290,7 @@ export const preparePriceBook = (baggage, makeRecorderKit) => { publishOffer(record) { const key = toPriceOfferKey(record.price, record.seqNum); - bidDataKits.get(key).recorder.write( + return E(getBidDataRecorder(key)).write( harden({ price: record.price, wanted: record.wanted, @@ -293,7 +331,9 @@ export const preparePriceBook = (baggage, makeRecorderKit) => { for (const [key, { seat }] of records.entries()) { if (!seat.hasExited()) { seat.exit(); - bidDataKits.delete(key); + if (bidDataKits.has(key)) { + bidDataKits.delete(key); + } records.delete(key); } } diff --git a/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.md b/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.md index c9eaedc9624..8ffb6ec2928 100644 --- a/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.md +++ b/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.md @@ -13,7 +13,43 @@ Generated by [AVA](https://avajs.dev). [ [ - 'published.auction.book0.bids.bids1001', + 'published.auction.book0', + { + collateralAvailable: { + brand: Object @Alleged: Collateral brand {}, + value: 0n, + }, + currentPriceLevel: { + denominator: { + brand: Object @Alleged: Collateral brand {}, + value: 10000000000000n, + }, + numerator: { + brand: Object @Alleged: Bid brand {}, + value: 9350000000000n, + }, + }, + proceedsRaised: undefined, + remainingProceedsGoal: null, + startCollateral: { + brand: Object @Alleged: Collateral brand {}, + value: 100n, + }, + startPrice: { + denominator: { + brand: Object @Alleged: Collateral brand {}, + value: 1000000000n, + }, + numerator: { + brand: Object @Alleged: Bid brand {}, + value: 1100000000n, + }, + }, + startProceedsGoal: null, + }, + ], + [ + 'published.auction.book0.schedule.bid1001', { balance: { brand: Object @Alleged: Bid brand {}, @@ -42,7 +78,7 @@ Generated by [AVA](https://avajs.dev). }, ], [ - 'published.auction.book0.bids.bids1002', + 'published.auction.book0.schedule.bid1002', { balance: { brand: Object @Alleged: Bid brand {}, @@ -71,7 +107,7 @@ Generated by [AVA](https://avajs.dev). }, ], [ - 'published.auction.book0.bids.bids1003', + 'published.auction.book0.schedule.bid1003', { balance: { brand: Object @Alleged: Bid brand {}, @@ -100,7 +136,7 @@ Generated by [AVA](https://avajs.dev). }, ], [ - 'published.auction.book0.bids.bids1004', + 'published.auction.book0.schedule.bid1004', { balance: { brand: Object @Alleged: Bid brand {}, @@ -128,42 +164,6 @@ Generated by [AVA](https://avajs.dev). }, }, ], - [ - 'published.auction.book0.schedule', - { - collateralAvailable: { - brand: Object @Alleged: Collateral brand {}, - value: 0n, - }, - currentPriceLevel: { - denominator: { - brand: Object @Alleged: Collateral brand {}, - value: 10000000000000n, - }, - numerator: { - brand: Object @Alleged: Bid brand {}, - value: 9350000000000n, - }, - }, - proceedsRaised: undefined, - remainingProceedsGoal: null, - startCollateral: { - brand: Object @Alleged: Collateral brand {}, - value: 100n, - }, - startPrice: { - denominator: { - brand: Object @Alleged: Collateral brand {}, - value: 1000000000n, - }, - numerator: { - brand: Object @Alleged: Bid brand {}, - value: 1100000000n, - }, - }, - startProceedsGoal: null, - }, - ], [ 'published.auction.governance', { diff --git a/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.snap b/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.snap new file mode 100644 index 00000000000..23e4e632a1c Binary files /dev/null and b/packages/inter-protocol/test/auction/snapshots/test-auctionContract.js.snap differ