From c88bea944c4a2c81352080c9cc3bd884d8ad81cc Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 7 Oct 2024 13:57:00 -0700 Subject: [PATCH 1/3] chore(types): types-index with types.ts --- packages/ERTP/src/types-index.d.ts | 2 + packages/ERTP/src/types-index.js | 2 + packages/ERTP/src/types.js | 420 -------------------------- packages/ERTP/src/types.ts | 469 +++++++++++++++++++++++++++++ 4 files changed, 473 insertions(+), 420 deletions(-) create mode 100644 packages/ERTP/src/types-index.d.ts create mode 100644 packages/ERTP/src/types-index.js delete mode 100644 packages/ERTP/src/types.js create mode 100644 packages/ERTP/src/types.ts diff --git a/packages/ERTP/src/types-index.d.ts b/packages/ERTP/src/types-index.d.ts new file mode 100644 index 00000000000..0e98a59385a --- /dev/null +++ b/packages/ERTP/src/types-index.d.ts @@ -0,0 +1,2 @@ +// Export all the types this package provides +export * from './types.js'; diff --git a/packages/ERTP/src/types-index.js b/packages/ERTP/src/types-index.js new file mode 100644 index 00000000000..d382eb5feaf --- /dev/null +++ b/packages/ERTP/src/types-index.js @@ -0,0 +1,2 @@ +// Empty JS file to correspond with its .d.ts twin +export {}; diff --git a/packages/ERTP/src/types.js b/packages/ERTP/src/types.js deleted file mode 100644 index 3fa88b16517..00000000000 --- a/packages/ERTP/src/types.js +++ /dev/null @@ -1,420 +0,0 @@ -// @jessie-check - -// Ensure this is a module. -export {}; - -/// -/** - * @import {Passable, RemotableObject} from '@endo/pass-style'; - * @import {CopyBag, CopySet, Key} from '@endo/patterns'; - * @import {LatestTopic, NotifierRecord} from '@agoric/notifier'; - */ - -/** @typedef {{ brand: Brand<'nat'>; value: bigint }} NatAmount */ -/** - * @template {Key} K - * @typedef {{ brand: Brand<'set'>; value: K[] }} SetAmount - */ -/** - * @template {Key} K - * @typedef {{ brand: Brand<'copySet'>; value: CopySet }} CopySetAmount - */ -/** - * @template {Key} K - * @typedef {{ brand: Brand<'copyBag'>; value: CopyBag }} CopyBagAmount - */ -/** @typedef {{ brand: Brand; value: any }} AnyAmount */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] - * @typedef {K extends 'nat' - * ? NatAmount - * : K extends 'set' - * ? SetAmount - * : K extends 'copySet' - * ? CopySetAmount - * : K extends 'copyBag' - * ? CopyBagAmount - * : AnyAmount} Amount - * Amounts are descriptions of digital assets, answering the questions "how - * much" and "of what kind". Amounts are values labeled with a brand. - * AmountMath executes the logic of how amounts are changed when digital - * assets are merged, separated, or otherwise manipulated. For example, a - * deposit of 2 bucks into a purse that already has 3 bucks gives a new purse - * balance of 5 bucks. An empty purse has 0 bucks. AmountMath relies heavily - * on polymorphic MathHelpers, which manipulate the unbranded portion. - */ - -/** - * @typedef {NatValue | SetValue | CopySet | import('@endo/patterns').CopyBag} AmountValue - * An `AmountValue` describes a set or quantity of assets that can be owned or - * shared. - * - * A fungible `AmountValue` uses a non-negative bigint to represent a quantity - * of that many assets. - * - * A non-fungible `AmountValue` uses an array or CopySet of `Key`s to represent - * a set of whatever asset each key represents. A `Key` is a passable value - * that can be used as an element in a set (SetStore or CopySet) or as the key - * in a map (MapStore or CopyMap). - * - * `SetValue` is for the deprecated set representation, using an array directly - * to represent the array of its elements. `CopySet` is the proper - * representation using a CopySet. - * - * A semi-fungible `CopyBag` is represented as a `CopyBag` of `Key` objects. - * "Bag" is synonymous with MultiSet, where an element of a bag can be present - * once or more times, i.e., some positive bigint number of times, - * representing that quantity of the asset represented by that key. - */ - -/** - * @typedef {'nat' | 'set' | 'copySet' | 'copyBag'} AssetKind See doc-comment - * for `AmountValue`. - */ - -/** - * @template {AssetKind} K - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {K extends 'nat' - * ? NatValue - * : K extends 'set' - * ? SetValue - * : K extends 'copySet' - * ? CopySet - * : K extends 'copyBag' - * ? CopyBag - * : never} AssetValueForKind - */ - -/** - * @template {AmountValue} V - * @typedef {V extends NatValue - * ? 'nat' - * : V extends SetValue - * ? 'set' - * : V extends CopySet - * ? 'copySet' - * : V extends import('@endo/patterns').CopyBag - * ? 'copyBag' - * : never} AssetKindForValue - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @typedef {object} DisplayInfo - * @property {number} [decimalPlaces] Tells the display software how many - * decimal places to move the decimal over to the left, or in other words, - * which position corresponds to whole numbers. We require fungible digital - * assets to be represented in integers, in the smallest unit (i.e. USD might - * be represented in mill, a thousandth of a dollar. In that case, - * `decimalPlaces` would be 3.) This property is optional, and for - * non-fungible digital assets, should not be specified. The decimalPlaces - * property should be used for _display purposes only_. Any other use is an - * anti-pattern. - * @property {K} assetKind - the kind of asset, either AssetKind.NAT (fungible) - * or AssetKind.SET or AssetKind.COPY_SET (non-fungible) - */ - -// XXX hack around JSDoc union handling -/** - * @template {AssetKind} K - * @typedef {object} BrandMethods - * @property {(allegedIssuer: ERef>) => Promise} isMyIssuer - * Should be used with `issuer.getBrand` to ensure an issuer and brand match. - * @property {() => string} getAllegedName - * @property {() => DisplayInfo} getDisplayInfo Give information to UI on how - * to display the amount. - * @property {() => Pattern} getAmountShape - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @typedef {RemotableObject & BrandMethods} Brand The brand identifies the - * kind of issuer, and has a function to get the alleged name for the kind of - * asset described. The alleged name (such as 'BTC' or 'moola') is provided by - * the maker of the issuer and should not be trusted as accurate. - * - * Every amount created by a particular issuer will share the same brand, but - * recipients cannot rely on the brand to verify that a purported amount - * represents the issuer they intended, since the same brand can be reused by - * a misbehaving issuer. - */ - -// /////////////////////////// Issuer ////////////////////////////////////////// - -/** - * @callback IssuerIsLive Return true if the payment continues to exist. - * - * If the payment is a promise, the operation will proceed upon fulfillment. - * @param {ERef} payment - * @returns {Promise} - */ -/** - * @template {AssetKind} K - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @callback IssuerGetAmountOf Get the amount of digital assets in the payment. - * Because the payment is not trusted, we cannot call a method on it directly, - * and must use the issuer instead. - * - * If the payment is a promise, the operation will proceed upon fulfillment. - * @param {ERef>} payment - * @returns {Promise>} - */ - -/** - * @callback IssuerBurn Burn all of the digital assets in the payment. - * `optAmountShape` is optional. If the `optAmountShape` pattern is present, - * the amount of the digital assets in the payment must match - * `optAmountShape`, to prevent sending the wrong payment and other - * confusion. - * - * If the payment is a promise, the operation will proceed upon fulfillment. - * - * As always with optional `Pattern` arguments, keep in mind that technically - * the value `undefined` itself is a valid `Key` and therefore a valid - * `Pattern`. But in optional pattern position, a top level `undefined` will - * be interpreted as absence. If you want to express a `Pattern` that will - * match only `undefined`, use `M.undefined()` instead. - * @param {ERef} payment - * @param {Pattern} [optAmountShape] - * @returns {Promise} - */ - -/** - * @template {AssetKind} K - * @template {Key} M - * @typedef {object} IssuerMethods Work around JSDoc union handling - * @property {() => Brand} getBrand Get the Brand for this Issuer. The Brand - * indicates the type of digital asset and is shared by the mint, the issuer, - * and any purses and payments of this particular kind. The brand is not - * closely held, so this function should not be trusted to identify an issuer - * alone. Fake digital assets and amount can use another issuer's brand. - * @property {() => string} getAllegedName Get the allegedName for this - * mint/issuer - * @property {() => K} getAssetKind Get the kind of MathHelpers used by this - * Issuer. - * @property {() => DisplayInfo} getDisplayInfo Give information to UI on how - * to display amounts for this issuer. - * @property {() => Purse} makeEmptyPurse Make an empty purse of this - * brand. - * @property {IssuerIsLive} isLive - * @property {IssuerGetAmountOf} getAmountOf - * @property {IssuerBurn} burn - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {RemotableObject & IssuerMethods} Issuer The issuer cannot - * mint a new amount, but it can create empty purses and payments. The issuer - * can also transform payments (splitting payments, combining payments, - * burning payments, and claiming payments exclusively). The issuer should be - * gotten from a trusted source and then relied upon as the decider of whether - * an untrusted payment is valid. - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @typedef {object} PaymentLedger - * @property {Mint} mint - * @property {Purse} mintRecoveryPurse Externally useful only if this issuer - * uses recovery sets. Can be used to get the recovery set associated with - * minted payments that are still live. - * @property {Issuer} issuer - * @property {Brand} brand - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {object} IssuerKit - * @property {Mint} mint - * @property {Purse} mintRecoveryPurse Externally useful only if this - * issuer uses recovery sets. Can be used to get the recovery set associated - * with minted payments that are still live. - * @property {Issuer} issuer - * @property {Brand} brand - * @property {DisplayInfo} displayInfo - */ - -/** - * @typedef {object} AdditionalDisplayInfo - * @property {number} [decimalPlaces] Tells the display software how many - * decimal places to move the decimal over to the left, or in other words, - * which position corresponds to whole numbers. We require fungible digital - * assets to be represented in integers, in the smallest unit (i.e. USD might - * be represented in mill, a thousandth of a dollar. In that case, - * `decimalPlaces` would be 3.) This property is optional, and for - * non-fungible digital assets, should not be specified. The decimalPlaces - * property should be used for _display purposes only_. Any other use is an - * anti-pattern. - * @property {AssetKind} [assetKind] - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {object} Mint Holding a Mint carries the right to issue new digital - * assets. These assets all have the same kind, which is called a Brand. - * @property {() => Issuer} getIssuer Gets the Issuer for this mint. - * @property {(newAmount: Amount) => Payment} mintPayment Creates a new - * Payment containing newly minted amount. - */ - -// /////////////////////////// Purse / Payment ///////////////////////////////// - -/** - * Issuers first became durable with mandatory recovery sets. Later they were - * made optional, but there is no support for converting from one state to the - * other. Thus, absence of a `RecoverySetsOption` state is equivalent to - * `'hasRecoverySets'`. In the absence of a `recoverySetsOption` parameter, - * upgradeIssuerKit defaults to the predecessor's `RecoverySetsOption` state, or - * `'hasRecoverySets'` if none. - * - * At this time, issuers started in one of the states (`'noRecoverySets'`, or - * `'hasRecoverySets'`) cannot be converted to the other on upgrade. If this - * transition is needed, it can likely be supported in a future upgrade. File an - * issue on github and explain what you need and why. - * - * @typedef {'hasRecoverySets' | 'noRecoverySets'} RecoverySetsOption - */ - -// /////////////////////////// Purse / Payment ///////////////////////////////// - -/** - * @callback DepositFacetReceive - * @param {Payment} payment - * @param {Pattern} [optAmountShape] - * @returns {Amount} - */ - -/** - * @typedef {object} DepositFacet - * @property {DepositFacetReceive} receive Deposit all the contents of payment - * into the purse that made this facet, returning the amount. If the optional - * argument `optAmount` does not equal the amount of digital assets in the - * payment, throw an error. - * - * If payment is a promise, throw an error. - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {RemotableObject & PurseMethods} Purse Purses hold amount of - * digital assets of the same brand, but unlike Payments, they are not meant - * to be sent to others. To transfer digital assets, a Payment should be - * withdrawn from a Purse. The amount of digital assets in a purse can change - * through the action of deposit() and withdraw(). - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {object} PurseMethods The primary use for Purses and Payments is for - * currency-like and goods-like digital assets, but they can also be used to - * represent other kinds of rights, such as the right to participate in a - * particular contract. - * @property {() => Brand} getAllegedBrand Get the alleged Brand for this - * Purse - * @property {() => Amount} getCurrentAmount Get the amount contained in - * this purse. - * @property {() => LatestTopic>} getCurrentAmountNotifier Get a - * lossy notifier for changes to this purse's balance. - * @property {

>( - * payment: P, - * optAmountShape?: Pattern, - * ) => P extends Payment ? Amount : never} deposit - * Deposit all the contents of payment into this purse, returning the amount. If - * the optional argument `optAmount` does not equal the amount of digital - * assets in the payment, throw an error. - * - * If payment is a promise, throw an error. - * @property {() => DepositFacet} getDepositFacet Return an object whose - * `receive` method deposits to the current Purse. - * @property {(amount: Amount) => Payment} withdraw Withdraw amount - * from this purse into a new Payment. - * @property {() => CopySet>} getRecoverySet The set of payments - * withdrawn from this purse that are still live. These are the payments that - * can still be recovered in emergencies by, for example, depositing into this - * purse. Such a deposit action is like canceling an outstanding check because - * you're tired of waiting for it. Once your cancellation is acknowledged, you - * can spend the assets at stake on other things. Afterwards, if the recipient - * of the original check finally gets around to depositing it, their deposit - * fails. - * - * Returns an empty set if this issuer does not support recovery sets. - * @property {() => Amount} recoverAll For use in emergencies, such as - * coming back from a traumatic crash and upgrade. This deposits all the - * payments in this purse's recovery set into the purse itself, returning the - * total amount of assets recovered. - * - * Returns an empty amount if this issuer does not support recovery sets. - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @template {Key} [M=Key] member kind, for Amounts that have member values - * @typedef {RemotableObject & PaymentMethods} Payment Payments hold amount - * of digital assets of the same brand in transit. Payments can be deposited - * in purses, split into multiple payments, combined, and claimed (getting an - * exclusive payment). Payments are linear, meaning that either a payment has - * the same amount of digital assets it started with, or it is used up - * entirely. It is impossible to partially use a payment. - * - * Payments are often received from other actors and therefore should not be - * trusted themselves. To get the amount of digital assets in a payment, use - * the trusted issuer: issuer.getAmountOf(payment), - * - * Payments can be converted to Purses by getting a trusted issuer and calling - * `issuer.makeEmptyPurse()` to create a purse, then - * `purse.deposit(payment)`. - */ - -/** - * @template {AssetKind} [K=AssetKind] - * @typedef {object} PaymentMethods - * @property {() => Brand} getAllegedBrand Get the allegedBrand, indicating - * the type of digital asset this payment purports to be, and which issuer to - * use. Because payments are not trusted, any method calls on payments should - * be treated with suspicion and verified elsewhere. - */ - -// /////////////////////////// MathHelpers ///////////////////////////////////// - -/** - * @template {AmountValue} V - * @typedef {object} MathHelpers All of the difference in how digital asset - * amount are manipulated can be reduced to the behavior of the math on - * values. We extract this custom logic into mathHelpers. MathHelpers are - * about value arithmetic, whereas AmountMath is about amounts, which are the - * values labeled with a brand. AmountMath use mathHelpers to do their value - * arithmetic, and then brand the results, making a new amount. - * - * The MathHelpers are designed to be called only from AmountMath, and so all - * methods but coerce can assume their inputs are valid. They only need to do - * output validation, and only when there is a possibility of invalid output. - * @property {(allegedValue: V) => V} doCoerce Check the kind of this value and - * throw if it is not the expected kind. - * @property {() => V} doMakeEmpty Get the representation for the identity - * element (often 0 or an empty array) - * @property {(value: V) => boolean} doIsEmpty Is the value the identity - * element? - * @property {(left: V, right: V) => boolean} doIsGTE Is the left greater than - * or equal to the right? - * @property {(left: V, right: V) => boolean} doIsEqual Does left equal right? - * @property {(left: V, right: V) => V} doAdd Return the left combined with the - * right. - * @property {(left: V, right: V) => V} doSubtract Return what remains after - * removing the right from the left. If something in the right was not in the - * left, we throw an error. - */ - -/** @typedef {bigint} NatValue */ - -/** - * @template {Key} [K=Key] - * @typedef {K[]} SetValue - */ diff --git a/packages/ERTP/src/types.ts b/packages/ERTP/src/types.ts new file mode 100644 index 00000000000..d7076b61ce5 --- /dev/null +++ b/packages/ERTP/src/types.ts @@ -0,0 +1,469 @@ +/* eslint-disable no-use-before-define */ +import type { LatestTopic } from '@agoric/notifier'; +import type { ERef } from '@endo/far'; +import type { RemotableObject } from '@endo/pass-style'; +import type { CopyBag, CopySet, Key, Pattern } from '@endo/patterns'; + +export type NatAmount = { + brand: Brand<'nat'>; + value: bigint; +}; +export type SetAmount = { + brand: Brand<'set'>; + value: K[]; +}; +export type CopySetAmount = { + brand: Brand<'copySet'>; + value: CopySet; +}; +export type CopyBagAmount = { + brand: Brand<'copyBag'>; + value: CopyBag; +}; +export type AnyAmount = { + brand: Brand; + value: any; +}; +/** + * Amounts are descriptions of digital assets, answering the questions "how + * much" and "of what kind". Amounts are values labeled with a brand. + * AmountMath executes the logic of how amounts are changed when digital + * assets are merged, separated, or otherwise manipulated. For example, a + * deposit of 2 bucks into a purse that already has 3 bucks gives a new purse + * balance of 5 bucks. An empty purse has 0 bucks. AmountMath relies heavily + * on polymorphic MathHelpers, which manipulate the unbranded portion. + */ +export type Amount< + K extends AssetKind = AssetKind, + M extends Key = Key, +> = K extends 'nat' + ? NatAmount + : K extends 'set' + ? SetAmount + : K extends 'copySet' + ? CopySetAmount + : K extends 'copyBag' + ? CopyBagAmount + : AnyAmount; +/** + * An `AmountValue` describes a set or quantity of assets that can be owned or + * shared. + * + * A fungible `AmountValue` uses a non-negative bigint to represent a quantity + * of that many assets. + * + * A non-fungible `AmountValue` uses an array or CopySet of `Key`s to represent + * a set of whatever asset each key represents. A `Key` is a passable value + * that can be used as an element in a set (SetStore or CopySet) or as the key + * in a map (MapStore or CopyMap). + * + * `SetValue` is for the deprecated set representation, using an array directly + * to represent the array of its elements. `CopySet` is the proper + * representation using a CopySet. + * + * A semi-fungible `CopyBag` is represented as a `CopyBag` of `Key` objects. + * "Bag" is synonymous with MultiSet, where an element of a bag can be present + * once or more times, i.e., some positive bigint number of times, + * representing that quantity of the asset represented by that key. + */ +export type AmountValue = + | NatValue + | SetValue + | CopySet + | import('@endo/patterns').CopyBag; +/** + * See doc-comment + * for `AmountValue`. + */ +export type AssetKind = 'nat' | 'set' | 'copySet' | 'copyBag'; +export type AssetValueForKind< + K extends AssetKind, + M extends Key = Key, +> = K extends 'nat' + ? NatValue + : K extends 'set' + ? SetValue + : K extends 'copySet' + ? CopySet + : K extends 'copyBag' + ? CopyBag + : never; +export type AssetKindForValue = V extends NatValue + ? 'nat' + : V extends SetValue + ? 'set' + : V extends CopySet + ? 'copySet' + : V extends import('@endo/patterns').CopyBag + ? 'copyBag' + : never; +export type DisplayInfo = { + /** + * Tells the display software how many + * decimal places to move the decimal over to the left, or in other words, + * which position corresponds to whole numbers. We require fungible digital + * assets to be represented in integers, in the smallest unit (i.e. USD might + * be represented in mill, a thousandth of a dollar. In that case, + * `decimalPlaces` would be 3.) This property is optional, and for + * non-fungible digital assets, should not be specified. The decimalPlaces + * property should be used for _display purposes only_. Any other use is an + * anti-pattern. + */ + decimalPlaces?: number | undefined; + /** + * - the kind of asset, either AssetKind.NAT (fungible) + * or AssetKind.SET or AssetKind.COPY_SET (non-fungible) + */ + assetKind: K; +}; +export type BrandMethods = { + /** + * Should be used with `issuer.getBrand` to ensure an issuer and brand match. + */ + isMyIssuer: (allegedIssuer: ERef>) => Promise; + getAllegedName: () => string; + /** + * Give information to UI on how + * to display the amount. + */ + getDisplayInfo: () => DisplayInfo; + getAmountShape: () => Pattern; +}; +/** + * The brand identifies the + * kind of issuer, and has a function to get the alleged name for the kind of + * asset described. The alleged name (such as 'BTC' or 'moola') is provided by + * the maker of the issuer and should not be trusted as accurate. + * + * Every amount created by a particular issuer will share the same brand, but + * recipients cannot rely on the brand to verify that a purported amount + * represents the issuer they intended, since the same brand can be reused by + * a misbehaving issuer. + */ +export type Brand = RemotableObject & + BrandMethods; +/** + * Return true if the payment continues to exist. + * + * If the payment is a promise, the operation will proceed upon fulfillment. + */ +export type IssuerIsLive = (payment: ERef) => Promise; +/** + * Get the amount of digital assets in the payment. + * Because the payment is not trusted, we cannot call a method on it directly, + * and must use the issuer instead. + * + * If the payment is a promise, the operation will proceed upon fulfillment. + */ +export type IssuerGetAmountOf = ( + payment: ERef>, +) => Promise>; +/** + * Burn all of the digital assets in the payment. + * `optAmountShape` is optional. If the `optAmountShape` pattern is present, + * the amount of the digital assets in the payment must match + * `optAmountShape`, to prevent sending the wrong payment and other + * confusion. + * + * If the payment is a promise, the operation will proceed upon fulfillment. + * + * As always with optional `Pattern` arguments, keep in mind that technically + * the value `undefined` itself is a valid `Key` and therefore a valid + * `Pattern`. But in optional pattern position, a top level `undefined` will + * be interpreted as absence. If you want to express a `Pattern` that will + * match only `undefined`, use `M.undefined()` instead. + */ +export type IssuerBurn = ( + payment: ERef, + optAmountShape?: Pattern, +) => Promise; +/** + * Work around JSDoc union handling + */ +export type IssuerMethods = { + /** + * Get the Brand for this Issuer. The Brand + * indicates the type of digital asset and is shared by the mint, the issuer, + * and any purses and payments of this particular kind. The brand is not + * closely held, so this function should not be trusted to identify an issuer + * alone. Fake digital assets and amount can use another issuer's brand. + */ + getBrand: () => Brand; + /** + * Get the allegedName for this + * mint/issuer + */ + getAllegedName: () => string; + /** + * Get the kind of MathHelpers used by this + * Issuer. + */ + getAssetKind: () => K; + /** + * Give information to UI on how + * to display amounts for this issuer. + */ + getDisplayInfo: () => DisplayInfo; + /** + * Make an empty purse of this + * brand. + */ + makeEmptyPurse: () => Purse; + isLive: IssuerIsLive; + getAmountOf: IssuerGetAmountOf; + burn: IssuerBurn; +}; +/** + * The issuer cannot + * mint a new amount, but it can create empty purses and payments. The issuer + * can also transform payments (splitting payments, combining payments, + * burning payments, and claiming payments exclusively). The issuer should be + * gotten from a trusted source and then relied upon as the decider of whether + * an untrusted payment is valid. + */ +export type Issuer< + K extends AssetKind = AssetKind, + M extends Key = Key, +> = RemotableObject & IssuerMethods; +export type PaymentLedger = { + mint: Mint; + /** + * Externally useful only if this issuer + * uses recovery sets. Can be used to get the recovery set associated with + * minted payments that are still live. + */ + mintRecoveryPurse: Purse; + issuer: Issuer; + brand: Brand; +}; +export type IssuerKit = { + mint: Mint; + /** + * Externally useful only if this + * issuer uses recovery sets. Can be used to get the recovery set associated + * with minted payments that are still live. + */ + mintRecoveryPurse: Purse; + issuer: Issuer; + brand: Brand; + displayInfo: DisplayInfo; +}; +export type AdditionalDisplayInfo = { + /** + * Tells the display software how many + * decimal places to move the decimal over to the left, or in other words, + * which position corresponds to whole numbers. We require fungible digital + * assets to be represented in integers, in the smallest unit (i.e. USD might + * be represented in mill, a thousandth of a dollar. In that case, + * `decimalPlaces` would be 3.) This property is optional, and for + * non-fungible digital assets, should not be specified. The decimalPlaces + * property should be used for _display purposes only_. Any other use is an + * anti-pattern. + */ + decimalPlaces?: number | undefined; + assetKind?: AssetKind | undefined; +}; +/** + * Holding a Mint carries the right to issue new digital + * assets. These assets all have the same kind, which is called a Brand. + */ +export type Mint = { + /** + * Gets the Issuer for this mint. + */ + getIssuer: () => Issuer; + /** + * Creates a new + * Payment containing newly minted amount. + */ + mintPayment: (newAmount: Amount) => Payment; +}; +/** + * Issuers first became durable with mandatory recovery sets. Later they were + * made optional, but there is no support for converting from one state to the + * other. Thus, absence of a `RecoverySetsOption` state is equivalent to + * `'hasRecoverySets'`. In the absence of a `recoverySetsOption` parameter, + * upgradeIssuerKit defaults to the predecessor's `RecoverySetsOption` state, or + * `'hasRecoverySets'` if none. + * + * At this time, issuers started in one of the states (`'noRecoverySets'`, or + * `'hasRecoverySets'`) cannot be converted to the other on upgrade. If this + * transition is needed, it can likely be supported in a future upgrade. File an + * issue on github and explain what you need and why. + */ +export type RecoverySetsOption = 'hasRecoverySets' | 'noRecoverySets'; +export type DepositFacetReceive = ( + payment: Payment, + optAmountShape?: Pattern, +) => Amount; +export type DepositFacet = { + /** + * Deposit all the contents of payment + * into the purse that made this facet, returning the amount. If the optional + * argument `optAmount` does not equal the amount of digital assets in the + * payment, throw an error. + * + * If payment is a promise, throw an error. + */ + receive: DepositFacetReceive; +}; +/** + * Purses hold amount of + * digital assets of the same brand, but unlike Payments, they are not meant + * to be sent to others. To transfer digital assets, a Payment should be + * withdrawn from a Purse. The amount of digital assets in a purse can change + * through the action of deposit() and withdraw(). + */ +export type Purse< + K extends AssetKind = AssetKind, + M extends Key = Key, +> = RemotableObject & PurseMethods; +/** + * The primary use for Purses and Payments is for + * currency-like and goods-like digital assets, but they can also be used to + * represent other kinds of rights, such as the right to participate in a + * particular contract. + */ +export type PurseMethods< + K extends AssetKind = AssetKind, + M extends Key = Key, +> = { + /** + * Get the alleged Brand for this + * Purse + */ + getAllegedBrand: () => Brand; + /** + * Get the amount contained in + * this purse. + */ + getCurrentAmount: () => Amount; + /** + * Get a + * lossy notifier for changes to this purse's balance. + */ + getCurrentAmountNotifier: () => LatestTopic>; + /** + * Deposit all the contents of payment into this purse, returning the amount. If + * the optional argument `optAmount` does not equal the amount of digital + * assets in the payment, throw an error. + * + * If payment is a promise, throw an error. + */ + deposit:

>( + payment: P, + optAmountShape?: Pattern, + ) => P extends Payment ? Amount : never; + /** + * Return an object whose + * `receive` method deposits to the current Purse. + */ + getDepositFacet: () => DepositFacet; + /** + * Withdraw amount + * from this purse into a new Payment. + */ + withdraw: (amount: Amount) => Payment; + /** + * The set of payments + * withdrawn from this purse that are still live. These are the payments that + * can still be recovered in emergencies by, for example, depositing into this + * purse. Such a deposit action is like canceling an outstanding check because + * you're tired of waiting for it. Once your cancellation is acknowledged, you + * can spend the assets at stake on other things. Afterwards, if the recipient + * of the original check finally gets around to depositing it, their deposit + * fails. + * + * Returns an empty set if this issuer does not support recovery sets. + */ + getRecoverySet: () => CopySet>; + /** + * For use in emergencies, such as + * coming back from a traumatic crash and upgrade. This deposits all the + * payments in this purse's recovery set into the purse itself, returning the + * total amount of assets recovered. + * + * Returns an empty amount if this issuer does not support recovery sets. + */ + recoverAll: () => Amount; +}; +/** + * Payments hold amount + * of digital assets of the same brand in transit. Payments can be deposited + * in purses, split into multiple payments, combined, and claimed (getting an + * exclusive payment). Payments are linear, meaning that either a payment has + * the same amount of digital assets it started with, or it is used up + * entirely. It is impossible to partially use a payment. + * + * Payments are often received from other actors and therefore should not be + * trusted themselves. To get the amount of digital assets in a payment, use + * the trusted issuer: issuer.getAmountOf(payment), + * + * Payments can be converted to Purses by getting a trusted issuer and calling + * `issuer.makeEmptyPurse()` to create a purse, then + * `purse.deposit(payment)`. + */ +export type Payment< + K extends AssetKind = AssetKind, + M extends Key = Key, +> = RemotableObject & PaymentMethods; +export type PaymentMethods = { + /** + * Get the allegedBrand, indicating + * the type of digital asset this payment purports to be, and which issuer to + * use. Because payments are not trusted, any method calls on payments should + * be treated with suspicion and verified elsewhere. + */ + getAllegedBrand: () => Brand; +}; +/** + * All of the difference in how digital asset + * amount are manipulated can be reduced to the behavior of the math on + * values. We extract this custom logic into mathHelpers. MathHelpers are + * about value arithmetic, whereas AmountMath is about amounts, which are the + * values labeled with a brand. AmountMath use mathHelpers to do their value + * arithmetic, and then brand the results, making a new amount. + * + * The MathHelpers are designed to be called only from AmountMath, and so all + * methods but coerce can assume their inputs are valid. They only need to do + * output validation, and only when there is a possibility of invalid output. + */ +export type MathHelpers = { + /** + * Check the kind of this value and + * throw if it is not the expected kind. + */ + doCoerce: (allegedValue: V) => V; + /** + * Get the representation for the identity + * element (often 0 or an empty array) + */ + doMakeEmpty: () => V; + /** + * Is the value the identity + * element? + */ + doIsEmpty: (value: V) => boolean; + /** + * Is the left greater than + * or equal to the right? + */ + doIsGTE: (left: V, right: V) => boolean; + /** + * Does left equal right? + */ + doIsEqual: (left: V, right: V) => boolean; + /** + * Return the left combined with the + * right. + */ + doAdd: (left: V, right: V) => V; + /** + * Return what remains after + * removing the right from the left. If something in the right was not in the + * left, we throw an error. + */ + doSubtract: (left: V, right: V) => V; +}; +export type NatValue = bigint; +export type SetValue = K[]; From 4abbe15fff630bf3d2004d9b4bca55d0c7517819 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Apr 2024 14:06:57 -0700 Subject: [PATCH 2/3] chore: deprecate getDisplayInfo() --- packages/ERTP/src/types.ts | 11 +++-------- packages/inter-protocol/src/auction/auctionBook.js | 2 ++ .../inter-protocol/src/proposals/addAssetToVault.js | 3 ++- packages/vats/src/vat-agoricNames.js | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/ERTP/src/types.ts b/packages/ERTP/src/types.ts index d7076b61ce5..d75c300f5fd 100644 --- a/packages/ERTP/src/types.ts +++ b/packages/ERTP/src/types.ts @@ -97,6 +97,7 @@ export type AssetKindForValue = V extends NatValue : V extends import('@endo/patterns').CopyBag ? 'copyBag' : never; +/** @deprecated */ export type DisplayInfo = { /** * Tells the display software how many @@ -122,10 +123,7 @@ export type BrandMethods = { */ isMyIssuer: (allegedIssuer: ERef>) => Promise; getAllegedName: () => string; - /** - * Give information to UI on how - * to display the amount. - */ + /** @deprecated look up in boardAux */ getDisplayInfo: () => DisplayInfo; getAmountShape: () => Pattern; }; @@ -199,10 +197,7 @@ export type IssuerMethods = { * Issuer. */ getAssetKind: () => K; - /** - * Give information to UI on how - * to display amounts for this issuer. - */ + /** @deprecated look up in boardAux */ getDisplayInfo: () => DisplayInfo; /** * Make an empty purse of this diff --git a/packages/inter-protocol/src/auction/auctionBook.js b/packages/inter-protocol/src/auction/auctionBook.js index d5536768e8a..5f82a07789d 100644 --- a/packages/inter-protocol/src/auction/auctionBook.js +++ b/packages/inter-protocol/src/auction/auctionBook.js @@ -461,6 +461,8 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { trace('observing'); void E.when( + // TODO get unit amounts elsewhere https://github.com/Agoric/agoric-sdk/issues/10235 + // or use a hard-coded amount E(collateralBrand).getDisplayInfo(), ({ decimalPlaces = DEFAULT_DECIMALS }) => { const quoteNotifier = E(priceAuthority).makeQuoteNotifier( diff --git a/packages/inter-protocol/src/proposals/addAssetToVault.js b/packages/inter-protocol/src/proposals/addAssetToVault.js index f1481e09099..7bd27147cbd 100644 --- a/packages/inter-protocol/src/proposals/addAssetToVault.js +++ b/packages/inter-protocol/src/proposals/addAssetToVault.js @@ -170,8 +170,9 @@ export const registerScaledPriceAuthority = async ( ]), ]); + // TODO get unit amounts elsewhere https://github.com/Agoric/agoric-sdk/issues/10235 // We need "unit amounts" of each brand in order to get the ratios right. You - // can ignore decimalPlaces when adding and subtracting a brand with itself, + // can ignore unit amounts when adding and subtracting a brand with itself, // but not when creating ratios. const getDecimalP = async brand => { const displayInfo = E(brand).getDisplayInfo(); diff --git a/packages/vats/src/vat-agoricNames.js b/packages/vats/src/vat-agoricNames.js index 601e616ee08..8ac56b7b4b6 100644 --- a/packages/vats/src/vat-agoricNames.js +++ b/packages/vats/src/vat-agoricNames.js @@ -22,7 +22,7 @@ const prepareNatBrand = zone => { const { name } = this.state; return name; }, - // Give information to UI on how to display the amount. + /** @deprecated */ getDisplayInfo() { const { displayInfo } = this.state; return displayInfo; From fff76f3954539f7166516df2bed36fd2ab6d9ac8 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 7 Oct 2024 14:44:14 -0700 Subject: [PATCH 3/3] feat(auctions): observe quote wo/ getDisplayInfo --- .../inter-protocol/src/auction/auctionBook.js | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/packages/inter-protocol/src/auction/auctionBook.js b/packages/inter-protocol/src/auction/auctionBook.js index 5f82a07789d..998cafd3eb5 100644 --- a/packages/inter-protocol/src/auction/auctionBook.js +++ b/packages/inter-protocol/src/auction/auctionBook.js @@ -29,13 +29,13 @@ import { /** * @import {Baggage} from '@agoric/vat-data'; - * @import {PriceAuthority, PriceDescription, PriceQuote, PriceQuoteValue, PriceQuery,} from '@agoric/zoe/tools/types.js'; + * @import {PriceAuthority} from '@agoric/zoe/tools/types.js'; * @import {TypedPattern} from '@agoric/internal'; */ const { makeEmpty } = AmountMath; -const DEFAULT_DECIMALS = 9; +const QUOTE_SCALE = 10n ** 9n; /** * @file The book represents the collateral-specific state of an ongoing @@ -460,41 +460,32 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { trace('observing'); - void E.when( - // TODO get unit amounts elsewhere https://github.com/Agoric/agoric-sdk/issues/10235 - // or use a hard-coded amount - E(collateralBrand).getDisplayInfo(), - ({ decimalPlaces = DEFAULT_DECIMALS }) => { - const quoteNotifier = E(priceAuthority).makeQuoteNotifier( - AmountMath.make(collateralBrand, 10n ** BigInt(decimalPlaces)), - bidBrand, + const quoteNotifier = E(priceAuthority).makeQuoteNotifier( + AmountMath.make(collateralBrand, QUOTE_SCALE), + bidBrand, + ); + void observeNotifier(quoteNotifier, { + updateState: quote => { + trace( + `BOOK notifier ${priceFrom(quote).numerator.value}/${ + priceFrom(quote).denominator.value + }`, ); - void observeNotifier(quoteNotifier, { - updateState: quote => { - trace( - `BOOK notifier ${priceFrom(quote).numerator.value}/${ - priceFrom(quote).denominator.value - }`, - ); - state.updatingOracleQuote = priceFrom(quote); - }, - fail: reason => { - trace( - `Failure from quoteNotifier (${reason}) setting to null`, - ); - // lack of quote will trigger restart - state.updatingOracleQuote = null; - }, - finish: done => { - trace( - `quoteNotifier invoked finish(${done}). setting quote to null`, - ); - // lack of quote will trigger restart - state.updatingOracleQuote = null; - }, - }); + state.updatingOracleQuote = priceFrom(quote); }, - ); + fail: reason => { + trace(`Failure from quoteNotifier (${reason}) setting to null`); + // lack of quote will trigger restart + state.updatingOracleQuote = null; + }, + finish: done => { + trace( + `quoteNotifier invoked finish(${done}). setting quote to null`, + ); + // lack of quote will trigger restart + state.updatingOracleQuote = null; + }, + }); void facets.helper.publishBookData(); },