diff --git a/packages/zoe/exported.d.ts b/packages/zoe/exported.d.ts index fefbe33ba91..6c64a14dc52 100644 --- a/packages/zoe/exported.d.ts +++ b/packages/zoe/exported.d.ts @@ -10,8 +10,6 @@ import { AmountKeywordRecord as _AmountKeywordRecord, ContractMeta as _ContractMeta, Handle as _Handle, - Installation as _Installation, - Instance as _Instance, Invitation as _Invitation, InvitationAmount as _InvitationAmount, IssuerKeywordRecord as _IssuerKeywordRecord, @@ -26,6 +24,11 @@ import { ZoeService as _ZoeService, } from './src/types-index.js'; +import { + Installation as _Installation, + Instance as _Instance, +} from './src/zoeService/utils.js'; + declare global { // @ts-ignore TS2666: Exports and export assignments are not permitted in module augmentations. export { @@ -35,7 +38,7 @@ declare global { _FeeIssuerConfig as FeeIssuerConfig, _Handle as Handle, _Installation as Installation, - _Instance as Instance, + // _Instance as Instance, _Invitation as Invitation, _InvitationAmount as InvitationAmount, _IssuerKeywordRecord as IssuerKeywordRecord, diff --git a/packages/zoe/src/cleanProposal.js b/packages/zoe/src/cleanProposal.js index 6555a75734c..7bdb94b166e 100644 --- a/packages/zoe/src/cleanProposal.js +++ b/packages/zoe/src/cleanProposal.js @@ -1,8 +1,8 @@ import { assert, q, Fail } from '@endo/errors'; +import { assertRecord } from '@endo/marshal'; +import { assertKey, M, mustMatch, isKey } from '@endo/patterns'; import { AmountMath, getAssetKind } from '@agoric/ertp'; import { objectMap } from '@agoric/internal'; -import { assertRecord } from '@endo/marshal'; -import { assertKey, assertPattern, mustMatch, isKey } from '@agoric/store'; import { FullProposalShape } from './typeGuards.js'; const { ownKeys } = Reflect; @@ -56,25 +56,26 @@ export const cleanKeywords = uncleanKeywordRecord => { return /** @type {string[]} */ (keywords); }; -export const coerceAmountPatternKeywordRecord = ( - allegedAmountKeywordRecord, +export const coerceAmountBoundKeywordRecord = ( + allegedAmountBoundKeywordRecord, getAssetKindByBrand, ) => { - cleanKeywords(allegedAmountKeywordRecord); + cleanKeywords(allegedAmountBoundKeywordRecord); // FIXME objectMap should constrain the mapping function by the record's type - return objectMap(allegedAmountKeywordRecord, amount => { + return objectMap(allegedAmountBoundKeywordRecord, amount => { + const { brand, value } = amount; // Check that each value can be coerced using the AmountMath // indicated by brand. `AmountMath.coerce` throws if coercion fails. if (isKey(amount)) { - const brandAssetKind = getAssetKindByBrand(amount.brand); + const brandAssetKind = getAssetKindByBrand(brand); const assetKind = getAssetKind(amount); // TODO: replace this assertion with a check of the assetKind // property on the brand, when that exists. assetKind === brandAssetKind || Fail`The amount ${amount} did not have the assetKind of the brand ${brandAssetKind}`; - return AmountMath.coerce(amount.brand, amount); + return AmountMath.coerce(brand, amount); } else { - assertPattern(amount); + mustMatch(value, M.kind('match:containerHas')); return amount; } }); @@ -90,7 +91,7 @@ export const coerceAmountKeywordRecord = ( allegedAmountKeywordRecord, getAssetKindByBrand, ) => { - const result = coerceAmountPatternKeywordRecord( + const result = coerceAmountBoundKeywordRecord( allegedAmountKeywordRecord, getAssetKindByBrand, ); @@ -153,10 +154,7 @@ export const cleanProposal = (proposal, getAssetKindByBrand) => { ownKeys(rest).length === 0 || Fail`${proposal} - Must only have want:, give:, exit: properties: ${rest}`; - const cleanedWant = coerceAmountPatternKeywordRecord( - want, - getAssetKindByBrand, - ); + const cleanedWant = coerceAmountBoundKeywordRecord(want, getAssetKindByBrand); const cleanedGive = coerceAmountKeywordRecord(give, getAssetKindByBrand); const cleanedProposal = harden({ diff --git a/packages/zoe/src/contractFacet/offerSafety.js b/packages/zoe/src/contractFacet/offerSafety.js index 6171b00287d..8829ecd47e8 100644 --- a/packages/zoe/src/contractFacet/offerSafety.js +++ b/packages/zoe/src/contractFacet/offerSafety.js @@ -1,5 +1,9 @@ import { AmountMath } from '@agoric/ertp'; +/** + * @import { AmountKeywordRecord, AmountBoundKeywordRecord } from '../zoeService/types.js' + */ + /** * Helper to perform satisfiesWant and satisfiesGive. Is * allocationAmount greater than or equal to requiredAmount for every @@ -9,19 +13,19 @@ import { AmountMath } from '@agoric/ertp'; * isOfferSafe will still be boolean. When we have Multiples, satisfiesWant and * satisfiesGive will tell how many times the offer was matched. * - * @param {AmountKeywordRecord} giveOrWant + * @param {AmountBoundKeywordRecord} giveOrWant * @param {AmountKeywordRecord} allocation * @returns {0|1} */ const satisfiesInternal = (giveOrWant = {}, allocation) => { - const isGTEByKeyword = ([keyword, requiredAmount]) => { + const isGTEByKeyword = ([keyword, requiredAmountBound]) => { // If there is no allocation for a keyword, we know the giveOrWant // is not satisfied without checking further. if (allocation[keyword] === undefined) { return 0; } const allocationAmount = allocation[keyword]; - return AmountMath.isGTE(allocationAmount, requiredAmount) ? 1 : 0; + return AmountMath.isGTE(allocationAmount, requiredAmountBound) ? 1 : 0; }; return Object.entries(giveOrWant).every(isGTEByKeyword) ? 1 : 0; }; diff --git a/packages/zoe/src/contractFacet/reallocate.js b/packages/zoe/src/contractFacet/reallocate.js index e9565f4e904..b38ce9561b8 100644 --- a/packages/zoe/src/contractFacet/reallocate.js +++ b/packages/zoe/src/contractFacet/reallocate.js @@ -3,6 +3,7 @@ import { makeScalarMapStore } from '@agoric/vat-data'; import { assertRightsConserved } from './rightsConservation.js'; import { addToAllocation, subtractFromAllocation } from './allocationMath.js'; +import { mustBeKey } from '../contractSupport/zoeHelpers.js'; /** * @import {MapStore} from '@agoric/swingset-liveslots'; @@ -47,11 +48,15 @@ export const makeAllocationMap = transfers => { allocations.set(seat, [newIncr, decr]); }; - for (const [fromSeat, toSeat, fromAmounts, toAmounts] of transfers) { + for (const [fromSeat, toSeat, fromAmountBounds, toAmounts] of transfers) { if (fromSeat) { - if (!fromAmounts) { + if (!fromAmountBounds) { throw Fail`Transfer from ${fromSeat} must say how much`; } + const fromAmounts = mustBeKey( + fromAmountBounds, + 'TODO: atomicRearange does not yet support AmountBounds', + ); decrementAllocation(fromSeat, fromAmounts); if (toSeat) { // Conserved transfer between seats @@ -74,8 +79,8 @@ export const makeAllocationMap = transfers => { } else { toSeat || Fail`Transfer must have at least one of fromSeat or toSeat`; // Transfer only to toSeat - !fromAmounts || - Fail`Transfer without fromSeat cannot have fromAmounts ${fromAmounts}`; + !fromAmountBounds || + Fail`Transfer without fromSeat cannot have fromAmountBounds ${fromAmountBounds}`; toAmounts || Fail`Transfer to ${toSeat} must say how much`; incrementAllocation(toSeat, toAmounts); } @@ -93,5 +98,5 @@ export const makeAllocationMap = transfers => { } resultingAllocations.push([seat, newAlloc]); } - return resultingAllocations; + return harden(resultingAllocations); }; diff --git a/packages/zoe/src/contractFacet/types.ts b/packages/zoe/src/contractFacet/types.ts index 9a2234b8b79..a59c41befb9 100644 --- a/packages/zoe/src/contractFacet/types.ts +++ b/packages/zoe/src/contractFacet/types.ts @@ -14,9 +14,9 @@ import type { Passable } from '@endo/pass-style'; import type { Key, Pattern } from '@endo/patterns'; import type { AmountKeywordRecord, + AmountBoundKeywordRecord, ExitRule, FeeMintAccess, - Instance, InvitationDetails, Keyword, ProposalRecord, @@ -24,7 +24,7 @@ import type { UserSeat, ZoeService, } from '../types-index.js'; -import type { ContractStartFunction } from '../zoeService/utils.js'; +import type { ContractStartFunction, Instance } from '../zoeService/utils.js'; /** * Any passable non-thenable. Often an explanatory string. @@ -121,7 +121,7 @@ export type ZCF> = { export type TransferPart = [ fromSeat?: ZCFSeat, toSeat?: ZCFSeat, - fromAmounts?: AmountKeywordRecord, + fromAmounts?: AmountBoundKeywordRecord, toAmounts?: AmountKeywordRecord, ]; diff --git a/packages/zoe/src/contractSupport/atomicTransfer.js b/packages/zoe/src/contractSupport/atomicTransfer.js index 9b26908afef..2250ce41b09 100644 --- a/packages/zoe/src/contractSupport/atomicTransfer.js +++ b/packages/zoe/src/contractSupport/atomicTransfer.js @@ -1,12 +1,20 @@ import { M } from '@agoric/store'; -import { AmountKeywordRecordShape, SeatShape } from '../typeGuards.js'; +import { + AmountKeywordRecordShape, + AmountBoundKeywordRecordShape, + SeatShape, +} from '../typeGuards.js'; /** - * @import {TransferPart, ZCF, ZCFSeat} from '@agoric/zoe'; + * @import {TransferPart, ZCF, ZCFSeat, AmountBoundKeywordRecord} from '@agoric/zoe'; */ export const TransferPartShape = M.splitArray( - harden([M.opt(SeatShape), M.opt(SeatShape), M.opt(AmountKeywordRecordShape)]), + harden([ + M.opt(SeatShape), + M.opt(SeatShape), + M.opt(AmountBoundKeywordRecordShape), + ]), harden([M.opt(AmountKeywordRecordShape)]), ); @@ -62,11 +70,11 @@ export const atomicRearrange = (zcf, transfers) => { * `fromOnly` are non-optional, as otherwise it doesn't make much sense. * * @param {ZCFSeat} fromSeat - * @param {AmountKeywordRecord} fromAmounts + * @param {AmountBoundKeywordRecord} fromAmountBounds * @returns {TransferPart} */ -export const fromOnly = (fromSeat, fromAmounts) => - harden([fromSeat, undefined, fromAmounts]); +export const fromOnly = (fromSeat, fromAmountBounds) => + harden([fromSeat, undefined, fromAmountBounds]); /** * Sometimes a TransferPart in an atomicRearrange only expresses what amounts diff --git a/packages/zoe/src/contractSupport/zoeHelpers.js b/packages/zoe/src/contractSupport/zoeHelpers.js index 59f3e1ca484..5817f20a682 100644 --- a/packages/zoe/src/contractSupport/zoeHelpers.js +++ b/packages/zoe/src/contractSupport/zoeHelpers.js @@ -1,7 +1,7 @@ -import { Fail } from '@endo/errors'; +import { Fail, b } from '@endo/errors'; import { E } from '@endo/eventual-send'; import { makePromiseKit } from '@endo/promise-kit'; -import { mustMatch, keyEQ } from '@agoric/store'; +import { mustMatch, keyEQ, isKey, isPattern } from '@endo/patterns'; import { AssetKind } from '@agoric/ertp'; import { fromUniqueEntries } from '@agoric/internal'; import { satisfiesWant } from '../contractFacet/offerSafety.js'; @@ -9,7 +9,7 @@ import { atomicTransfer, fromOnly, toOnly } from './atomicTransfer.js'; /** * @import {Pattern} from '@endo/patterns'; - * @import {ContractMeta, Invitation, Proposal, ZCF, ZCFSeat} from '@agoric/zoe'; + * @import {Invitation, Proposal, ZCF, ZCFSeat, AmountBoundKeywordRecord} from '@agoric/zoe'; */ export const defaultAcceptanceMsg = `The offer has been accepted. Once the contract has been completed, please check your payout`; @@ -73,16 +73,42 @@ export const swap = (zcf, leftSeat, rightSeat) => { return defaultAcceptanceMsg; }; +/** + * @param {AmountBoundKeywordRecord} want + * @param {string} complaint + * @returns {AmountKeywordRecord} + */ +export const mustBeKey = (want, complaint) => { + if (isKey(want)) { + return want; + } + if (isPattern(want)) { + throw Fail`${b(complaint)}: ${want}`; + } + throw Fail`Must be key: ${want}`; +}; +harden(mustBeKey); + /** @type {SwapExact} */ export const swapExact = (zcf, leftSeat, rightSeat) => { try { + const { give: rightGive, want: rightWantBound } = rightSeat.getProposal(); + const { give: leftGive, want: leftWantBound } = leftSeat.getProposal(); + const rightWant = mustBeKey( + rightWantBound, + 'TODO: swapExact does not yet support want patterns', + ); + const leftWant = mustBeKey( + leftWantBound, + 'TODO: swapExact does not yet support want patterns', + ); zcf.atomicRearrange( harden([ - fromOnly(rightSeat, rightSeat.getProposal().give), - fromOnly(leftSeat, leftSeat.getProposal().give), + fromOnly(rightSeat, rightGive), + fromOnly(leftSeat, leftGive), - toOnly(leftSeat, leftSeat.getProposal().want), - toOnly(rightSeat, rightSeat.getProposal().want), + toOnly(leftSeat, leftWant), + toOnly(rightSeat, rightWant), ]), ); } catch (err) { diff --git a/packages/zoe/src/contracts/auction/firstPriceLogic.js b/packages/zoe/src/contracts/auction/firstPriceLogic.js index bc08fe92948..43010179723 100644 --- a/packages/zoe/src/contracts/auction/firstPriceLogic.js +++ b/packages/zoe/src/contracts/auction/firstPriceLogic.js @@ -11,8 +11,7 @@ export const calcWinnerAndClose = (zcf, sellSeat, bidSeats) => { want: { Ask: minBid }, } = sellSeat.getProposal(); - /** @type {Brand<'nat'>} */ - const bidBrand = minBid.brand; + const bidBrand = /** @type {Brand<'nat'>} */ (minBid.brand); const emptyBid = AmountMath.makeEmpty(bidBrand); let highestBid = emptyBid; diff --git a/packages/zoe/src/contracts/auction/secondPriceLogic.js b/packages/zoe/src/contracts/auction/secondPriceLogic.js index 5a0cfb3aef3..5db979bb3ed 100644 --- a/packages/zoe/src/contracts/auction/secondPriceLogic.js +++ b/packages/zoe/src/contracts/auction/secondPriceLogic.js @@ -11,8 +11,7 @@ export const calcWinnerAndClose = (zcf, sellSeat, bidSeats) => { want: { Ask: minBid }, } = sellSeat.getProposal(); - /** @type {Brand<'nat'>} */ - const bidBrand = minBid.brand; + const bidBrand = /** @type {Brand<'nat'>} */ (minBid.brand); const emptyBid = AmountMath.makeEmpty(bidBrand); let highestBid = emptyBid; diff --git a/packages/zoe/src/contracts/loan/borrow.js b/packages/zoe/src/contracts/loan/borrow.js index aca8c6fc426..f1264d99923 100644 --- a/packages/zoe/src/contracts/loan/borrow.js +++ b/packages/zoe/src/contracts/loan/borrow.js @@ -2,6 +2,7 @@ import { assert, Fail } from '@endo/errors'; import { E } from '@endo/eventual-send'; import { Far } from '@endo/marshal'; import { makePromiseKit } from '@endo/promise-kit'; +import { M, mustMatch } from '@endo/patterns'; import { AmountMath } from '@agoric/ertp'; import { @@ -16,6 +17,10 @@ import { calculateInterest, makeDebtCalculator } from './updateDebt.js'; import { makeCloseLoanInvitation } from './close.js'; import { makeAddCollateralInvitation } from './addCollateral.js'; +/** + * @import {NatAmount} from '@agoric/ertp'; + */ + /** @type {MakeBorrowInvitation} */ export const makeBorrowInvitation = (zcf, config) => { const { @@ -28,7 +33,9 @@ export const makeBorrowInvitation = (zcf, config) => { } = config; // We can only lend what the lender has already escrowed. - const maxLoan = lenderSeat.getAmountAllocated('Loan'); + const maxLoan = /** @type {NatAmount} */ ( + lenderSeat.getAmountAllocated('Loan') + ); /** @type {OfferHandler} */ const borrow = async borrowerSeat => { @@ -43,7 +50,10 @@ export const makeBorrowInvitation = (zcf, config) => { borrowerSeat.getProposal().give.Collateral.brand ), ); - const loanWanted = borrowerSeat.getProposal().want.Loan; + const loanWanted = /** @type {NatAmount} */ ( + borrowerSeat.getProposal().want.Loan + ); + mustMatch(loanWanted.value, M.nat()); const loanBrand = zcf.getTerms().brands.Loan; // The value of the collateral in the Loan brand diff --git a/packages/zoe/src/typeGuards.js b/packages/zoe/src/typeGuards.js index f389102d303..c790079b009 100644 --- a/packages/zoe/src/typeGuards.js +++ b/packages/zoe/src/typeGuards.js @@ -1,6 +1,7 @@ // @jessie-check import { + AmountBoundShape, AmountShape, AssetKindShape, BrandShape, @@ -31,9 +32,9 @@ export const InstallationShape = M.remotable('Installation'); export const SeatShape = M.remotable('Seat'); export const AmountKeywordRecordShape = M.recordOf(KeywordShape, AmountShape); -export const AmountPatternKeywordRecordShape = M.recordOf( +export const AmountBoundKeywordRecordShape = M.recordOf( KeywordShape, - M.pattern(), + AmountBoundShape, ); export const PaymentPKeywordRecordShape = M.recordOf( KeywordShape, @@ -80,7 +81,7 @@ export const TimerShape = makeHandleShape('timer'); * @see {ProposalRecord} type */ export const FullProposalShape = { - want: AmountPatternKeywordRecordShape, + want: AmountBoundKeywordRecordShape, give: AmountKeywordRecordShape, // To accept only one, we could use M.or rather than M.splitRecord, // but the error messages would have been worse. Rather, diff --git a/packages/zoe/src/types.ts b/packages/zoe/src/types.ts index ab9e22c451c..106b6cf9149 100644 --- a/packages/zoe/src/types.ts +++ b/packages/zoe/src/types.ts @@ -1,5 +1,6 @@ -import type { Brand, Issuer } from '@agoric/ertp'; +import type { ERef } from '@endo/eventual-send'; import type { RemotableObject } from '@endo/pass-style'; +import type { Issuer, Brand } from '@agoric/ertp'; /** * Alias for RemotableObject @@ -17,10 +18,7 @@ export type Keyword = string; */ export type InvitationHandle = Handle<'Invitation'>; export type IssuerKeywordRecord = Record>; -export type IssuerPKeywordRecord = Record< - Keyword, - import('@endo/far').ERef> ->; +export type IssuerPKeywordRecord = Record>>; export type BrandKeywordRecord = Record>; export type StandardTerms = { /** diff --git a/packages/zoe/src/zoeService/escrowStorage.js b/packages/zoe/src/zoeService/escrowStorage.js index 944cc6cfdd8..da3afe7d148 100644 --- a/packages/zoe/src/zoeService/escrowStorage.js +++ b/packages/zoe/src/zoeService/escrowStorage.js @@ -1,4 +1,4 @@ -import { AmountMath } from '@agoric/ertp'; +import { AmountMath, assertValueGetAssetKind } from '@agoric/ertp'; import { E } from '@endo/eventual-send'; import { q, Fail } from '@endo/errors'; import { deeplyFulfilledObject, objectMap } from '@agoric/internal'; @@ -17,8 +17,22 @@ import { cleanKeywords } from '../cleanProposal.js'; * purse per brand. * * @param {import('@agoric/vat-data').Baggage} baggage + * @param {GetAssetKindByBrand} _getAssetKindByBrand */ -export const provideEscrowStorage = baggage => { +export const provideEscrowStorage = (baggage, _getAssetKindByBrand) => { + const getAssetKindByAmount = amount => { + const { /* brand, */ value } = amount; + // const ak1 = getAssetKindByBrand(brand); + const ak2 = assertValueGetAssetKind(value); + // TODO this fails in escrowStorage.test.js, likely meaning we + // don't do enough checking elsewhere that the storage assetKind is the + // assetKind of the value. + // ak1 === ak2 || + // // line break for ease of interactive breakpointing + // Fail`${q(ak1)} must === ${q(ak2)}`; + return ak2; + }; + /** @type {WeakMapStore} */ const brandToPurse = provideDurableWeakMapStore(baggage, 'brandToPurse'); @@ -111,7 +125,9 @@ export const provideEscrowStorage = baggage => { const deposits = await deeplyFulfilledObject(depositPs); const initialAllocation = harden({ - ...objectMap(want, amount => AmountMath.makeEmptyFromAmount(amount)), + ...objectMap(want, amount => + AmountMath.makeEmpty(amount.brand, getAssetKindByAmount(amount)), + ), // Deposits should win in case of overlapping give/want keywords // (which are not allowed as of 2024-01). ...deposits, diff --git a/packages/zoe/src/zoeService/internal-types.js b/packages/zoe/src/zoeService/internal-types.js index 3e469530b47..e1fcad0750e 100644 --- a/packages/zoe/src/zoeService/internal-types.js +++ b/packages/zoe/src/zoeService/internal-types.js @@ -2,8 +2,9 @@ /// /** + * @import {VatAdminFacet, } from '@agoric/swingset-vat'; * @import {FeeMintAccess, GetBrands, GetBundleIDFromInstallation, GetIssuers, InstallBundle, InstallBundleID, SourceBundle} from './types.js'; - * @import {InstanceRecord} from './utils.js'; + * @import {Instance, InstanceRecord, GetPublicFacet, GetTerms} from './utils.js'; */ /** @@ -95,7 +96,7 @@ * @property {ZoeInstanceAdminMakeInvitation} makeInvitation * @property {() => Issuer} getInvitationIssuer * @property {() => object} getRoot of CreateVatResults - * @property {() => import('@agoric/swingset-vat').VatAdminFacet} getAdminNode of CreateVatResults + * @property {() => VatAdminFacet} getAdminNode of CreateVatResults */ /** @@ -129,16 +130,16 @@ * @property {InstallBundle} installBundle * @property {InstallBundleID} installBundleID * @property {GetBundleIDFromInstallation} getBundleIDFromInstallation - * @property {import('./utils.js').GetPublicFacet} getPublicFacet + * @property {GetPublicFacet} getPublicFacet * @property {GetBrands} getBrands * @property {GetIssuers} getIssuers - * @property {import('./utils.js').GetTerms} getTerms - * @property {(instance: import('./utils.js').Instance) => string[]} getOfferFilter + * @property {GetTerms} getTerms + * @property {(instance: Instance) => string[]} getOfferFilter * @property {(instance: Instance, strings: string[]) => any} setOfferFilter - * @property {(instance: import('./utils.js').Instance) => Promise} getInstallationForInstance + * @property {(instance: Instance) => Promise} getInstallationForInstance * @property {GetInstanceAdmin} getInstanceAdmin * @property {UnwrapInstallation} unwrapInstallation - * @property {(invitationHandle: InvitationHandle) => import('@endo/patterns').Pattern | undefined} getProposalShapeForInvitation + * @property {(invitationHandle: InvitationHandle) => Pattern | undefined} getProposalShapeForInvitation */ /** diff --git a/packages/zoe/src/zoeService/types.ts b/packages/zoe/src/zoeService/types.ts index 2ad91022534..d36845aef1a 100644 --- a/packages/zoe/src/zoeService/types.ts +++ b/packages/zoe/src/zoeService/types.ts @@ -1,15 +1,24 @@ +import type { ERef, EReturn } from '@endo/eventual-send'; +import type { Pattern } from '@endo/patterns'; import type { - AnyAmount, + Amount, + AmountBound, AssetKind, DisplayInfo, Issuer, NatValue, Payment, } from '@agoric/ertp'; +import type { TimerService, Timestamp } from '@agoric/time'; import type { Subscriber } from '@agoric/notifier'; -import type { ERef, EReturn } from '@endo/eventual-send'; -import type { Bundle, BundleID } from '@agoric/swingset-vat'; -import type { ContractStartFunction, StartParams } from './utils.js'; +import type { Bundle, BundleID, BundleCap } from '@agoric/swingset-vat'; +import type { + ContractStartFunction, + StartParams, + StartInstance, + Instance, + Installation, +} from './utils.js'; import type { Keyword, InvitationHandle, @@ -17,7 +26,7 @@ import type { Handle, IssuerKeywordRecord, } from '../types.js'; -import type { Allocation } from '../types-index.js'; +import type { Allocation, Invitation, Completion } from '../types-index.js'; /** * @see {@link https://github.com/sindresorhus/type-fest/blob/main/source/is-any.d.ts} @@ -49,16 +58,14 @@ export type ZoeService = { getInvitationIssuer: GetInvitationIssuer; install: InstallBundle; installBundleID: InstallBundleID; - startInstance: import('./utils.js').StartInstance; + startInstance: StartInstance; offer: Offer; getPublicFacet: ( instance: ERef, ) => Promise< IsAny extends true ? any - : I extends import('./utils.js').Instance< - infer SF extends ContractStartFunction - > + : I extends Instance ? IsAny extends true ? unknown : EReturn['publicFacet'] @@ -70,9 +77,7 @@ export type ZoeService = { instance: I, ) => IsAny extends true ? Promise - : I extends ERef< - import('./utils.js').Instance - > + : I extends ERef> ? IsAny extends true ? Promise : Promise['terms']> @@ -95,7 +100,7 @@ export type ZoeService = { */ getProposalShapeForInvitation: ( invitationHandle: InvitationHandle, - ) => import('@endo/patterns').Pattern | undefined; + ) => Pattern | undefined; }; type GetInvitationIssuer = () => Promise>; type GetFeeIssuer = () => Promise>; @@ -103,22 +108,22 @@ type GetConfiguration = () => { feeIssuerConfig: FeeIssuerConfig; }; export type GetIssuers = ( - instance: import('./utils.js').Instance, + instance: Instance, ) => Promise; export type GetBrands = ( - instance: import('./utils.js').Instance, + instance: Instance, ) => Promise; type GetInstallationForInstance = ( - instance: import('./utils.js').Instance, + instance: Instance, ) => Promise; export type GetInstance = ( - invitation: ERef, -) => Promise>; + invitation: ERef, +) => Promise>; export type GetInstallation = ( - invitation: ERef, + invitation: ERef, ) => Promise; export type GetInvitationDetails = ( - invitation: ERef>, + invitation: ERef>, ) => Promise; /** * Create an installation by safely evaluating the code and @@ -158,7 +163,7 @@ export type GetBundleIDFromInstallation = ( * expected for every rule under `give`. */ export type Offer = ( - invitation: ERef>, + invitation: ERef>, proposal?: Proposal, paymentKeywordRecord?: PaymentPKeywordRecord, offerArgs?: Args, @@ -227,12 +232,12 @@ export type UserSeat = { * returns a subscriber that * will be notified when the seat has exited or failed. */ - getExitSubscriber: () => Subscriber; + getExitSubscriber: () => Subscriber; }; export type Proposal = Partial; export type ProposalRecord = { give: AmountKeywordRecord; - want: AmountKeywordRecord; + want: AmountBoundKeywordRecord; exit: ExitRule; }; /** @@ -240,7 +245,8 @@ export type ProposalRecord = { * { Asset: AmountMath.make(assetBrand, 5n), Price: * AmountMath.make(priceBrand, 9n) } */ -export type AmountKeywordRecord = Record; +export type AmountKeywordRecord = Record; +export type AmountBoundKeywordRecord = Record; export type Waker = { wake: () => void; }; @@ -252,8 +258,8 @@ export type WaivedExitRule = { }; export type AfterDeadlineExitRule = { afterDeadline: { - timer: import('@agoric/time').TimerService; - deadline: import('@agoric/time').Timestamp; + timer: TimerService; + deadline: Timestamp; }; }; /** @@ -268,10 +274,9 @@ export type ExitRule = | OnDemandExitRule | WaivedExitRule | AfterDeadlineExitRule; -export type Instance = import('./utils.js').Instance; export type ZCFSpec = | { - bundleCap: import('@agoric/swingset-vat').BundleCap; + bundleCap: BundleCap; } | { name: string; @@ -288,14 +293,11 @@ export type PaymentPKeywordRecord = Record>>; export type PaymentKeywordRecord = Record>; export type InvitationDetails = { installation: Installation; - instance: import('./utils.js').Instance; + instance: Instance; handle: InvitationHandle; description: string; customDetails?: Record | undefined; }; -export type Installation = import('./utils.js').Installation; -export type InstallationStart = - import('./utils.js').InstallationStart; export type FeeIssuerConfig = { name: string; assetKind: AssetKind; diff --git a/packages/zoe/src/zoeService/utils.d.ts b/packages/zoe/src/zoeService/utils.d.ts index 5d50dfef140..9b8dec95db1 100644 --- a/packages/zoe/src/zoeService/utils.d.ts +++ b/packages/zoe/src/zoeService/utils.d.ts @@ -12,14 +12,14 @@ type SourceBundle = Record; /** * Installation of a contract, typed by its start function. */ -export type Installation = +export type Installation = TagContainer & RemotableObject & { getBundle: () => SourceBundle; getBundleLabel: () => string; }; -export type Instance = +export type Instance = TagContainer & RemotableObject & Handle<'Instance'>; export type InstallationStart = diff --git a/packages/zoe/src/zoeService/zoeStorageManager.js b/packages/zoe/src/zoeService/zoeStorageManager.js index 9a422b61823..aad22fe1576 100644 --- a/packages/zoe/src/zoeService/zoeStorageManager.js +++ b/packages/zoe/src/zoeService/zoeStorageManager.js @@ -79,7 +79,9 @@ export const makeZoeStorageManager = ( // EscrowStorage holds the purses that Zoe uses for escrow. This // object should be closely held and tracked: all of the digital // assets that users escrow are contained within these purses. - const escrowStorage = provideEscrowStorage(zoeBaggage); + const escrowStorage = provideEscrowStorage(zoeBaggage, brand => + issuerStorage.getAssetKindByBrand(brand), + ); // Add a purse for escrowing user funds (not for fees). Create the // local, non-remote escrow purse for the fee mint immediately. diff --git a/packages/zoe/test/types.test-d.ts b/packages/zoe/test/types.test-d.ts index 425b8d47214..70f5d9f0c38 100644 --- a/packages/zoe/test/types.test-d.ts +++ b/packages/zoe/test/types.test-d.ts @@ -11,10 +11,9 @@ import { M, type Key } from '@endo/patterns'; // 'prepare' is deprecated but still supported import type { Brand } from '@agoric/ertp'; import type { prepare as scaledPriceAuthorityStart } from '../src/contracts/scaledPriceAuthority.js'; -import type { Instance } from '../src/zoeService/utils.js'; +import type { Instance, Installation } from '../src/zoeService/utils.js'; import type { ContractMeta, - Installation, Invitation, ZCF, ZCFSeat, diff --git a/packages/zoe/test/unitTests/cleanProposal.test.js b/packages/zoe/test/unitTests/cleanProposal.test.js index 581220705d8..491157ef3a5 100644 --- a/packages/zoe/test/unitTests/cleanProposal.test.js +++ b/packages/zoe/test/unitTests/cleanProposal.test.js @@ -5,6 +5,10 @@ import { cleanProposal } from '../../src/cleanProposal.js'; import { setup } from './setupBasicMints.js'; import buildManualTimer from '../../tools/manualTimer.js'; +/** + * @import {HasBound} from '@agoric/ertp'; + */ + const proposeGood = (t, proposal, assetKind, expected) => t.deepEqual( cleanProposal(harden(proposal), () => assetKind), @@ -96,17 +100,20 @@ test('cleanProposal - wrong assetKind', t => { test('cleanProposal - want patterns', t => { const { moola, simoleans } = setup(); const timer = buildManualTimer(t.log); + const wantAsset2HasBound = simoleans( + /** @type {HasBound} */ (M.containerHas(M.any())), + ); proposeGood( t, { - want: { Asset2: M.any() }, + want: { Asset2: wantAsset2HasBound }, give: { Price2: moola(3n) }, exit: { afterDeadline: { timer, deadline: 100n } }, }, 'nat', { - want: { Asset2: M.any() }, + want: { Asset2: wantAsset2HasBound }, give: { Price2: moola(3n) }, exit: { afterDeadline: { timer, deadline: 100n } }, }, @@ -127,22 +134,22 @@ test('cleanProposal - want patterns', t => { t, { want: { Asset2: simoleans(1n) }, - give: { Price2: M.any() }, + give: { Price2: moola(M.containerHas(M.any())) }, exit: { afterDeadline: { timer, deadline: 100n } }, }, 'nat', - 'A passable tagged "match:any" is not a key: "[match:any]"', + 'A passable tagged "match:containerHas" is not a key: "[match:containerHas]"', ); proposeBad( t, { want: { Asset2: simoleans(1n) }, - give: { Price2: M.any() }, + give: { Price2: moola(M.containerHas(M.any())) }, exit: { afterDeadline: { timer, deadline: M.any() } }, }, 'nat', - 'A passable tagged "match:any" is not a key: "[match:any]"', + 'A passable tagged "match:containerHas" is not a key: "[match:containerHas]"', ); }); diff --git a/packages/zoe/test/unitTests/setupBasicMints.js b/packages/zoe/test/unitTests/setupBasicMints.js index a16f9ee1826..93102c01354 100644 --- a/packages/zoe/test/unitTests/setupBasicMints.js +++ b/packages/zoe/test/unitTests/setupBasicMints.js @@ -1,10 +1,11 @@ -import { makeIssuerKit, AmountMath } from '@agoric/ertp'; +import { makeIssuerKit } from '@agoric/ertp'; import { makeScalarMapStore } from '@agoric/store'; import { makeZoeForTest } from '../../tools/setup-zoe.js'; import { makeFakeVatAdmin } from '../../tools/fakeVatAdmin.js'; /** * @import {MapStore} from '@agoric/swingset-liveslots'; + * @import {AmountBound, AmountValueBound} from '@agoric/ertp'; */ export const setup = () => { @@ -26,8 +27,13 @@ export const setup = () => { const { admin: fakeVatAdmin, vatAdminState } = makeFakeVatAdmin(); const zoe = makeZoeForTest(fakeVatAdmin); - /** @type {(brand: Brand<'nat'>) => (value: bigint) => Amount<'nat'>} */ - const makeSimpleMake = brand => value => AmountMath.make(brand, value); + // The following more correct type causes an explosion of new lint errors + // /** + // * @template {AssetKind} [AK='nat'] + // * @param {Brand} brand + // * @returns {(value: AmountValueBound) => AmountBound} + // */ + const makeSimpleMake = brand => value => harden({ brand, value }); const result = { moolaIssuer: moolaKit.issuer, diff --git a/packages/zoe/test/unitTests/zoe/escrowStorage.test.js b/packages/zoe/test/unitTests/zoe/escrowStorage.test.js index 62f6d576162..46d678678c2 100644 --- a/packages/zoe/test/unitTests/zoe/escrowStorage.test.js +++ b/packages/zoe/test/unitTests/zoe/escrowStorage.test.js @@ -1,7 +1,8 @@ import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; -import { AmountMath, makeIssuerKit, AssetKind } from '@agoric/ertp'; - +import { Fail } from '@endo/errors'; import { E } from '@endo/eventual-send'; + +import { AmountMath, makeIssuerKit, AssetKind } from '@agoric/ertp'; import { makeScalarBigMapStore } from '@agoric/vat-data'; import { provideEscrowStorage } from '../../../src/zoeService/escrowStorage.js'; import { @@ -13,6 +14,7 @@ test('provideEscrowStorage', async t => { const { createPurse, provideLocalPurse, withdrawPayments, depositPayments } = provideEscrowStorage( makeScalarBigMapStore('zoe baggage', { durable: true }), + _brand => 'nat', ); const stableKit = makeIssuerKit( @@ -138,6 +140,7 @@ const setupPurses = async createPurse => { test('payments without matching give keywords', async t => { const { createPurse, depositPayments } = provideEscrowStorage( makeScalarBigMapStore('zoe baggage', { durable: true }), + _brand => Fail`Not in a mock test`, ); const { ticketKit, stableKit } = await setupPurses(createPurse); @@ -175,6 +178,7 @@ test('payments without matching give keywords', async t => { test(`give keywords without matching payments`, async t => { const { createPurse, depositPayments } = provideEscrowStorage( makeScalarBigMapStore('zoe baggage', { durable: true }), + _brand => Fail`Not in a mock test`, ); const { ticketKit, stableKit } = await setupPurses(createPurse); diff --git a/packages/zoe/tools/setup-zoe.js b/packages/zoe/tools/setup-zoe.js index d589064950f..f2651a87966 100644 --- a/packages/zoe/tools/setup-zoe.js +++ b/packages/zoe/tools/setup-zoe.js @@ -8,7 +8,8 @@ import fakeVatAdmin, { makeFakeVatAdmin } from './fakeVatAdmin.js'; /** * @import {EndoZipBase64Bundle, TestBundle} from '@agoric/swingset-vat'; - * @import {FeeIssuerConfig, Installation} from '../src/types-index.js'; + * @import {Installation} from '../src/zoeService/utils.js'; + * @import {FeeIssuerConfig} from '../src/types-index.js'; */ /**