From 48f54aec7a6b00140edbf3696e5989c2243de73b Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Wed, 5 Aug 2020 14:28:05 -0700 Subject: [PATCH 01/31] refactor: refactor Zoe to use seat objects rather than offerHandles --- packages/ERTP/src/amountMath.chainmail | 115 --- packages/ERTP/src/amountMath.js | 13 +- packages/ERTP/src/issuer.chainmail | 245 ------ packages/ERTP/src/issuer.js | 10 +- packages/ERTP/src/types.js | 64 +- .../swingset-runner/demo/zoeTests/vat-bob.js | 7 +- .../swingset-runner/demo/zoeTests/vat-dave.js | 6 +- packages/zoe/docs/zoe-zcf.puml | 62 +- packages/zoe/scripts/build-zcfBundle.js | 2 +- packages/zoe/src/clientSupport/index.js | 1 - .../zoe/src/clientSupport/instanceHandle.js | 8 - packages/zoe/src/contractFacet.js | 359 --------- .../zoe/src/contractFacet/contractFacet.js | 196 +++++ .../{ => contractFacet}/evalContractCode.js | 13 +- packages/zoe/src/contractFacet/exit.js | 37 + .../src/{ => contractFacet}/offerSafety.js | 70 +- .../{ => contractFacet}/rightsConservation.js | 25 +- packages/zoe/src/contractFacet/seat.js | 87 ++ packages/zoe/src/contractSupport/auctions.js | 53 +- packages/zoe/src/contractSupport/index.js | 7 +- .../zoe/src/contractSupport/zoeHelpers.js | 743 ++++++++---------- packages/zoe/src/contracts/atomicSwap.js | 51 +- packages/zoe/src/contracts/automaticRefund.js | 30 +- packages/zoe/src/contracts/barterExchange.js | 52 +- packages/zoe/src/contracts/coveredCall.js | 98 +-- packages/zoe/src/contracts/mintPayments.js | 77 +- .../zoe/src/contracts/multipoolAutoswap.js | 2 +- packages/zoe/src/contracts/publicAuction.js | 132 ++-- packages/zoe/src/contracts/simpleExchange.js | 134 ++-- packages/zoe/src/internal-types.js | 147 +++- packages/zoe/src/issuerTable.js | 117 +++ packages/zoe/src/state.js | 285 ------- packages/zoe/src/types.js | 470 +++++------ packages/zoe/src/zoe.js | 465 ----------- .../zoe/src/{ => zoeService}/cleanProposal.js | 5 +- packages/zoe/src/zoeService/zoe.js | 314 ++++++++ .../swingsetTests/zoe-metering/vat-zoe.js | 2 +- .../zoe/test/swingsetTests/zoe/vat-bob.js | 6 +- .../zoe/test/swingsetTests/zoe/vat-dave.js | 6 +- .../zoe/test/swingsetTests/zoe/vat-zoe.js | 2 +- .../test/unitTests/contracts/escrowToVote.js | 2 +- .../test/unitTests/contracts/fakeVatAdmin.js | 11 +- .../unitTests/contracts/test-atomicSwap.js | 533 +++++++------ .../contracts/test-automaticRefund.js | 33 +- .../test/unitTests/contracts/test-autoswap.js | 13 +- .../test/unitTests/contracts/test-barter.js | 110 ++- .../contracts/test-brokenContract.js | 2 +- .../unitTests/contracts/test-coveredCall.js | 271 ++++--- .../unitTests/contracts/test-escrowToVote.js | 2 +- .../test/unitTests/contracts/test-grifter.js | 2 +- .../unitTests/contracts/test-mintPayments.js | 243 ++++-- .../contracts/test-multipoolAutoswap.js | 2 +- .../unitTests/contracts/test-publicAuction.js | 485 +++++------- .../unitTests/contracts/test-sellTickets.js | 2 +- .../contracts/test-simpleExchange.js | 377 ++++----- .../test/unitTests/contracts/test-useObj.js | 2 +- .../zoe/test/unitTests/contracts/test-zcf.js | 2 +- .../test/unitTests/contracts/useObjExample.js | 2 +- .../unitTests/contracts/zcfTesterContract.js | 2 +- .../zoe/test/unitTests/setupBasicMints.js | 7 + .../zoe/test/unitTests/setupMixedMints.js | 5 + .../test/unitTests/setupNonFungibleMints.js | 4 + .../zoe/test/unitTests/test-cleanProposal.js | 2 +- .../zoe/test/unitTests/test-offerSafety.js | 2 +- .../test/unitTests/test-rightsConservation.js | 2 +- packages/zoe/test/zoeTestHelpers.js | 35 + 66 files changed, 3021 insertions(+), 3650 deletions(-) delete mode 100644 packages/ERTP/src/amountMath.chainmail delete mode 100644 packages/ERTP/src/issuer.chainmail delete mode 100644 packages/zoe/src/clientSupport/index.js delete mode 100644 packages/zoe/src/clientSupport/instanceHandle.js delete mode 100644 packages/zoe/src/contractFacet.js create mode 100644 packages/zoe/src/contractFacet/contractFacet.js rename packages/zoe/src/{ => contractFacet}/evalContractCode.js (67%) create mode 100644 packages/zoe/src/contractFacet/exit.js rename packages/zoe/src/{ => contractFacet}/offerSafety.js (50%) rename packages/zoe/src/{ => contractFacet}/rightsConservation.js (70%) create mode 100644 packages/zoe/src/contractFacet/seat.js create mode 100644 packages/zoe/src/issuerTable.js delete mode 100644 packages/zoe/src/state.js delete mode 100644 packages/zoe/src/zoe.js rename packages/zoe/src/{ => zoeService}/cleanProposal.js (97%) create mode 100644 packages/zoe/src/zoeService/zoe.js create mode 100644 packages/zoe/test/zoeTestHelpers.js diff --git a/packages/ERTP/src/amountMath.chainmail b/packages/ERTP/src/amountMath.chainmail deleted file mode 100644 index 54976c1c8fa..00000000000 --- a/packages/ERTP/src/amountMath.chainmail +++ /dev/null @@ -1,115 +0,0 @@ -/** - * 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. - */ -struct Amount (Brand, Value) { - brand :Brand; - value :Value; -} - -/** - * Values describe the value of something that can be owned or shared. - * Fungible values are normally represented by natural numbers. Other - * values may be represented as strings naming a particular right, or - * an arbitrary object that sensibly represents the rights at issue. - * - * Value must be Comparable. (This IDL doesn't yet provide a way to specify - * subtype relationships for structs.) - */ -struct Value { -} - -/** - * Logic for manipulating amounts. - * - * Amounts are the canonical description of tradable goods. They are manipulated - * by issuers and mints, and represent the goods and currency carried by purses and - * payments. They can be used to represent things like currency, stock, and the - * abstract right to participate in a particular exchange. - * - */ -interface AmountMath (Amount (Brand, Value)) { - - /** Return the brand. */ - getBrand() -> (Brand); - - /** - * Get the name of the mathHelpers used. This can be used as an - * argument to `makeAmountMath` to create local amountMath. - */ - getMathHelpersName -> (String); - - /** Make an amount from a value by adding the brand. */ - make(allegedValue :Value) -> (Amount); - - /** - * Make sure this amount is valid and return it if so. - */ - coerce(allegedAmount :Amount) -> (Amount); - - /** Extract and return the value. */ - value(amount :Amount) -> (Value); - - /** Return the amount representing an empty amount. This is the - * identity element for MathHelpers.add and MatHelpers.subtract. - */ - getEmpty() -> (Amount); - - /** Return true if the Amount is empty. Otherwise false. */ - isEmpty(amount :Amount) -> (boolean); - - /** - * Returns true if the leftAmount is greater than or equal to the - * rightAmount. For non-scalars, "greater than or equal to" depends - * on the kind of amount, as defined by the MathHelpers. For example, - * whether rectangle A is greater than rectangle B depends on whether rectangle - * A includes rectangle B as defined by the logic in MathHelpers. - */ - isGTE(leftAmount :Amount, rightAmount :Amount) -> (boolean); - - /** - * Returns true if the leftAmount equals the rightAmount. We assume - * that if isGTE is true in both directions, isEqual is also true - */ - isEqual(leftAmount :Amount, rightAmount :Amount) -> (boolean); - - /** - * Returns a new amount that is the union of both leftAmount and rightAmount. - * - * For fungible amount this means adding the values. For other kinds of - * amount, it usually means including all of the elements from both - * left and right. - */ - add(leftAmount :Amount, rightAmount :Amount) -> (Amount); - - /** - * Returns a new amount that is the leftAmount minus the rightAmount - * (i.e. everything in the leftAmount that is not in the - * rightAmount). If leftAmount doesn't include rightAmount - * (subtraction results in a negative), throw an error. Because the - * left amount must include the right amount, this is NOT equivalent - * to set subtraction. - */ - subtract(leftAmount :Amount, rightAmount :Amount) -> (Amount); -} - -/** - * 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 AmountMath will have the same brand, but recipients - * cannot use the brand by itself to verify that a purported amount is - * authentic, since the brand can be reused by a misbehaving issuer. - */ -interface Brand ( ) { - isMyIssuer (issuer :Issuer) -> (Boolean); - getAllegedName ( ) -> (String); -} diff --git a/packages/ERTP/src/amountMath.js b/packages/ERTP/src/amountMath.js index 1805f2db0cb..8e2c288896d 100644 --- a/packages/ERTP/src/amountMath.js +++ b/packages/ERTP/src/amountMath.js @@ -54,9 +54,10 @@ import './types'; * amounts, we can get the brand and find the issuer which matches the * brand. The issuer and the brand mutually validate each other. * - * @param {Brand} brand + * @template {string} T + * @param {Brand} brand * @param {MathHelpersName} mathHelpersName - * @returns {AmountMath} + * @returns {AmountMath} */ function makeAmountMath(brand, mathHelpersName) { mustBeComparable(brand); @@ -77,8 +78,8 @@ function makeAmountMath(brand, mathHelpersName) { /** * Make an amount from a value by adding the brand. - * @param {any} allegedValue - * @returns {Amount} + * @param {Value} allegedValue + * @returns {Amount} */ make: allegedValue => { const value = helpers.doCoerce(allegedValue); @@ -89,8 +90,8 @@ function makeAmountMath(brand, mathHelpersName) { /** * Make sure this amount is valid and return it if so, throwing if invalid. - * @param {any} allegedAmount - * @returns {Amount} or throws if invalid + * @param {Amount} allegedAmount + * @returns {Amount} or throws if invalid */ coerce: allegedAmount => { // If the cache already has the allegedAmount, that diff --git a/packages/ERTP/src/issuer.chainmail b/packages/ERTP/src/issuer.chainmail deleted file mode 100644 index 122e644c636..00000000000 --- a/packages/ERTP/src/issuer.chainmail +++ /dev/null @@ -1,245 +0,0 @@ -/** - * 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. - */ -interface Issuer (Amount (Value)) { - /** - * Get the Brand for this Issuer. The Brand indicates the kind 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() -> (allegedName); - - /* Get the AmountMath for this Issuer. */ - getAmountMath() -> (AmountMath); - - /* Get the name of the MathHelpers for this Issuer. */ - getMathHelpersName() -> (String); - - /** Make an empty purse of this brand. */ - makeEmptyPurse() -> (Purse); - - /** - * Return true if the payment continues to exist. - * - * If the payment is a promise, the operation will proceed upon resolution. - */ - isLive(payment :Payment) -> (boolean); - - /** - * 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 resolution. - */ - getAmountOf(payment :Payment) -> (Amount); - - /** - * Burn all of the digital assets in the payment. `optAmount` is optional. - * If `optAmount` is present, the code will insist that the amount of - * the digital assets in the payment is equal to `optAmount`, to - * prevent sending the wrong payment and other confusion. - * - * If the payment is a promise, the operation will proceed upon resolution. - */ - burn(payment :Payment, optAmount :Amount) -> (Amount); - - /** - * Transfer all digital assets from the payment to a new payment and - * delete the original. `optAmount` is optional. - * If `optAmount` is present, the code will insist that the amount of - * digital assets in the payment is equal to `optAmount`, to prevent - * sending the wrong payment and other confusion. - * - * If the payment is a promise, the operation will proceed upon resolution. - */ - claim(payment :Payment, optAmount :Amount) - -> (Payment); - - /** - * Combine multiple payments into one payment. - * - * If any of the payments is a promise, the operation will proceed upon - * resolution. - */ - combine(paymentsArray :List(Payment)) - -> (Payment); - - /** - * Split a single payment into two payments, A and B, according to the - * paymentAmountA passed in. - * - * If the payment is a promise, the operation will proceed upon resolution. - */ - split(payment :Payment, paymentAmountA :Amount) - -> (List(Payment)); - - /** - * Split a single payment into many payments, according to the - * amounts passed in. - * - * If the payment is a promise, the operation will proceed upon resolution. - */ - splitMany(payment :Payment, amounts :List(Amount)) - -> (List(Payment)); - -} - -interface Brand { - /** - * The Brand indicates the kind of digital asset and is shared by - * the mint, the issuer, and any purses and payments of this - * particular kind. Fake digital assets and amount can use another - * issuer's brand. `brand.isMyIssuer` should be used with - * `issuer.getBrand` to ensure an issuer and brand match. - */ - isMyIssuer(allegedIssuer) -> (boolean); - getAllegedName() -> (string); -} - -/** - * Makes Issuers. - * - * The allegedName becomes part of the brand in asset descriptions. The - * allegedName doesn't have to be a string, but it will only be used for - * its value. The allegedName is useful for debugging and double-checking - * assumptions, but should not be trusted. - * - * The mathHelpersName will be used to import a specific mathHelpers - * from the mathHelpers library. For example, natMathHelpers, the - * default, is used for basic fungible tokens. - */ -interface IssuerMaker { - makeIssuerKit( - allegedName :String, - mathHelperName :String) -> (IssuerResults); -} - -/** - * The return value of makeIssuerKit - */ -struct IssuerResults ( ) { - mint :Mint; - issuer :Issuer; - amountMath :AmountMath; - brand :Brand; -} - -/** - * Holding a Mint carries the right to issue new digital assets. These - * assets all have the same kind, which is called a Brand. - */ -interface Mint (Amount (Value)) { - /** Get the Issuer for this mint. */ - getIssuer() -> (Issuer); - - /** - * Create a new Payment containing newly minted amount. - */ - mintPayment(newAmount :Amount) -> (Payment); -} - -/** - * 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(). - * - * 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. - */ -interface Purse (Amount) { - - /** Get the Issuer for this mint. */ - getIssuer() -> (Issuer); - - /** Get the amount contained in this purse, confirmed by the issuer. */ - getCurrentAmount() -> (Amount); - - /** - * 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 an unresolved promise, throw an error. - */ - deposit(payment :Payment, optAmount :Amount) -> (Amount); - - /** Withdraw amount from this purse into a new Payment. */ - withdraw(amount :Amount) -> (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)`. - */ -interface Payment (Amount) { - - /** - * Get the allegedBrand, indicating the kind 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. - */ -interface mathHelpers () { - /** - * Check the kind of this value and throw if it is not the - * expected kind. - */ - doCoerce(allegedValue :Value) -> (Value); - - /** - * Get the representation for the identity element (often 0 or an - * empty array) - */ - doGetEmpty() -> (Value); - - /** Is the value the identity element? */ - doIsEmpty(value :Value) -> (boolean); - - /** Is the left greater than or equal to the right? */ - doIsGTE(left :Value, right :Value) -> (boolean); - - /** Does left equal right? */ - doIsEqual(left :Value, right :Value) -> (boolean); - - /** Return the left combined with the right */ - doAdd(left :Value, right :Value) -> (Value); - - /** - * 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 :Value, right :Value) -> (Value); -} diff --git a/packages/ERTP/src/issuer.js b/packages/ERTP/src/issuer.js index cd293a7d6af..158d32bb279 100644 --- a/packages/ERTP/src/issuer.js +++ b/packages/ERTP/src/issuer.js @@ -11,7 +11,7 @@ import makeAmountMath from './amountMath'; import './types'; /** - * + * @template {string} T * @param {string} allegedName * @param {MathHelpersName} [mathHelpersName='nat'] * @returns {IssuerKit} @@ -55,10 +55,10 @@ function makeIssuerKit(allegedName, mathHelpersName = 'nat') { }; /** - * @returns {Purse} + * @returns {Purse} */ const makePurse = () => { - /** @type {Purse} */ + /** @type {Purse} */ const purse = harden({ deposit: (srcPayment, optAmount = undefined) => { if (isPromise(srcPayment)) { @@ -170,7 +170,7 @@ function makeIssuerKit(allegedName, mathHelpersName = 'nat') { return harden(newPayments); }; - /** @type {Issuer} */ + /** @type {Issuer} */ const issuer = harden({ getBrand: () => brand, getAllegedName: () => allegedName, @@ -274,7 +274,7 @@ function makeIssuerKit(allegedName, mathHelpersName = 'nat') { }, }); - /** @type {Mint} */ + /** @type {Mint} */ const mint = harden({ getIssuer: () => issuer, mintPayment: newAmount => { diff --git a/packages/ERTP/src/types.js b/packages/ERTP/src/types.js index 79bdb23a467..e69e274d5e5 100644 --- a/packages/ERTP/src/types.js +++ b/packages/ERTP/src/types.js @@ -7,7 +7,9 @@ */ /** - * @typedef {Object} Amount + * @template {string} BrandName - A string representing the associated + * brand + * @typedef {Object} 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 @@ -36,7 +38,9 @@ */ /** - * @typedef {Object} AmountMath + * @template {string} BrandName - A string representing the associated + * brand + * @typedef {Object} AmountMath * Logic for manipulating amounts. * * Amounts are the canonical description of tradable goods. They are manipulated @@ -93,7 +97,9 @@ */ /** - * @typedef {Object} Brand + * @template {string} BrandName - A string representing the associated + * brand + * @typedef {Object} 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 @@ -103,7 +109,8 @@ * cannot use the brand by itself to verify that a purported amount is * authentic, since the brand can be reused by a misbehaving issuer. * - * @property {(issuer: Issuer) => boolean} isMyIssuer + * @property {(allegedIssuer: any) => boolean} isMyIssuer Should be used with + * `issuer.getBrand` to ensure an issuer and brand match. * @property {() => string} getAllegedName */ @@ -112,7 +119,9 @@ */ /** - * @typedef {Object} Issuer + * @template {string} BrandName - A string representing the associated + * brand + * @typedef {Object} 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 @@ -178,42 +187,31 @@ */ /** - * @typedef {Object} Brand - * The Brand indicates the kind of digital asset and is shared by - * the mint, the issuer, and any purses and payments of this - * particular kind. Fake digital assets and amount can use another - * issuer's brand. - * - * @property {(allegedIssuer: any) => boolean} isMyIssuer Should be used with - * `issuer.getBrand` to ensure an issuer and brand match. - * @property {() => string} getAllegedName - */ - -/** - * @typedef {Object} IssuerMaker - * Makes Issuers. + * @callback MakeIssuerKit + * @param {string} allegedName + * @param {string} mathHelperName + * @returns {IssuerKit} * - * @property {(allegedName: string, mathHelperName: string) => IssuerKit} makeIssuerKit - * The allegedName becomes part of the brand in asset descriptions. The - * allegedName doesn't have to be a string, but it will only be used for - * its value. The allegedName is useful for debugging and double-checking + * The allegedName is useful for debugging and double-checking * assumptions, but should not be trusted. * * The mathHelpersName will be used to import a specific mathHelpers * from the mathHelpers library. For example, natMathHelpers, the * default, is used for basic fungible tokens. * - * @typedef {Object} IssuerKit + * @typedef {Object} IssuerKit * The return value of makeIssuerKit * - * @property {Mint} mint - * @property {Issuer} issuer - * @property {AmountMath} amountMath - * @property {Brand} brand + * @property {Mint} mint + * @property {Issuer} issuer + * @property {AmountMath} amountMath + * @property {Brand} brand */ /** - * @typedef {Object} Mint + * @template {string} BrandName - A string representing the associated + * brand + * @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. * @@ -233,7 +231,9 @@ */ /** - * @typedef {Object} Purse + * @template {string} BrandName - A string representing the associated + * brand + * @typedef {Object} 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 @@ -263,7 +263,9 @@ */ /** - * @typedef {Object} Payment + * @template {string} BrandName - A string representing the associated + * brand + * @typedef {Object} 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 diff --git a/packages/swingset-runner/demo/zoeTests/vat-bob.js b/packages/swingset-runner/demo/zoeTests/vat-bob.js index 52e79a79a42..de8f8df9094 100644 --- a/packages/swingset-runner/demo/zoeTests/vat-bob.js +++ b/packages/swingset-runner/demo/zoeTests/vat-bob.js @@ -3,7 +3,6 @@ import { E } from '@agoric/eventual-send'; import { assert, details } from '@agoric/assert'; import { sameStructure } from '@agoric/same-structure'; -import { makeGetInstanceHandle } from '@agoric/zoe/src/clientSupport'; import { showPurseBalance, setupIssuers } from './helpers'; import { makePrintLog } from './printLog'; @@ -22,12 +21,16 @@ const build = async (zoe, issuers, payments, installations, timer) => { const [moolaPayment, simoleanPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; const inviteIssuer = await E(zoe).getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); return harden({ doAutomaticRefund: async inviteP => { const invite = await inviteP; const exclInvite = await E(inviteIssuer).claim(invite); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); + const instanceHandle = await getInstanceHandle(exclInvite); const { installationHandle, issuerKeywordRecord } = await E( diff --git a/packages/swingset-runner/demo/zoeTests/vat-dave.js b/packages/swingset-runner/demo/zoeTests/vat-dave.js index 5bb79bd32c3..2930eec1ca0 100644 --- a/packages/swingset-runner/demo/zoeTests/vat-dave.js +++ b/packages/swingset-runner/demo/zoeTests/vat-dave.js @@ -3,7 +3,6 @@ import { E } from '@agoric/eventual-send'; import { assert, details } from '@agoric/assert'; import { sameStructure } from '@agoric/same-structure'; -import { makeGetInstanceHandle } from '@agoric/zoe/src/clientSupport'; import { showPurseBalance, setupIssuers } from './helpers'; import { makePrintLog } from './printLog'; @@ -22,7 +21,6 @@ const build = async (zoe, issuers, payments, installations, timer) => { const [_moolaPayment, simoleanPayment, bucksPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; const inviteIssuer = await E(zoe).getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); return harden({ doPublicAuction: async inviteP => { @@ -83,6 +81,10 @@ const build = async (zoe, issuers, payments, installations, timer) => { const { value: inviteValue } = await E(inviteIssuer).getAmountOf( exclInvite, ); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const instanceHandle = await getInstanceHandle(exclInvite); const { installationHandle, issuerKeywordRecord } = await E( zoe, diff --git a/packages/zoe/docs/zoe-zcf.puml b/packages/zoe/docs/zoe-zcf.puml index e22241ff43e..361b49c61b6 100644 --- a/packages/zoe/docs/zoe-zcf.puml +++ b/packages/zoe/docs/zoe-zcf.puml @@ -2,53 +2,51 @@ @startuml Zoe communication with Zcf scale 8 -participant SwingSet +participant CosmicSwingset actor Alice +actor Bob participant Zoe box new vat per contract collections Zcf database contract end box -SwingSet -> Zoe : create -Alice -> Zoe : install bundle -Alice //-- Zoe : installHandle -Alice -> Zoe : makeInstance(handle, ...) +CosmicSwingset -> Zoe : makeZoe(adminVat) +Alice -> Zoe : install(bundle) +Alice //-- Zoe : installation +Alice -> Zoe : makeInstance(installation...) +Zoe -> Zoe : makeInstance() +Zoe -> Zoe : makeZoeInstanceAdminForZcf() Zoe -> Zoe : createVat() -Zoe -> Zoe : makeZoeForZcf -Zoe -> Zcf : startContract(zoeForZcf) +Zoe -> Zcf : E(zcfRoot).executeContract Zcf -> Zcf : evalContractBundle() -Zcf -> Zcf : makeZcfForContract -Zcf -> contract : makeContract(zcfForContract) -contract --\\ Zcf : invite -Zcf --\\ Zoe : {invite, zcfForZoe} -Alice //-- Zoe : { invite, instanceRecord } - -==== +Zcf -> Zcf : makeZcf +Zcf -> contract : execute(zcf) contract -> Zcf : makeInvitation(...) Zcf -> Zoe : makeInvitation(...) -note right -build invokable object around hook -call Zoe.makeInvitation(callbackObj) -end note note left -create inviteHandle -save callBackObj keyed by InviteHandle -create InviteAmount -mint invitePayment +mint invitation payment end note +Zoe --\\ Zcf : invitation +Zcf --\\ Contract : invitation +contract --\\ Zcf : {creatorFacet, creatorInvitation, publicFacet } +Zcf --\\ Zoe : {creatorFacet, creatorInvitation, addSeatObj} +Alice //-- Zoe : {creatorFacet, creatorInvitation} -Zoe --\\ Zcf : invite -Zcf -> Bob : //// invite -Bob -> Zoe : offer(invite) +==== + +Alice -> Zoe : offer(invitation...) +Zoe -> Zoe : makeUserSeat +Zoe -> Alice : userSeat +Zoe -> Zcf : addSeat note left -record Deposit -create payout +deposit payments end note -Zoe -> Zcf : addOffer -Zcf -> Zcf : setup completion actions -Zcf --\\ Zoe : completeObj -Zoe -> Zcf : seatCallBack.invoke() -Zoe --\\ Bob : offerResult +Zcf -> Zcf : makeZCFSeat +Zcf -> Contract : offerHandler(ZCFSeat) +Contract -> Zcf : offerResult +Zcf --\\ Zoe : { offerResult, exitObj } + +Zoe --\\ Zoe : userSeat promises updated @enduml diff --git a/packages/zoe/scripts/build-zcfBundle.js b/packages/zoe/scripts/build-zcfBundle.js index 7096045288e..d4a4141cb51 100644 --- a/packages/zoe/scripts/build-zcfBundle.js +++ b/packages/zoe/scripts/build-zcfBundle.js @@ -12,7 +12,7 @@ async function writeSourceBundle(contractFilename, outputPath) { } async function main() { - const contractFilename = `${__dirname}/../src/contractFacet.js`; + const contractFilename = `${__dirname}/../src/contractFacet/contractFacet.js`; const outputPath = `${__dirname}/../bundles/bundle-contractFacet.js`; await writeSourceBundle(contractFilename, outputPath); } diff --git a/packages/zoe/src/clientSupport/index.js b/packages/zoe/src/clientSupport/index.js deleted file mode 100644 index 528a71635bf..00000000000 --- a/packages/zoe/src/clientSupport/index.js +++ /dev/null @@ -1 +0,0 @@ -export { makeGetInstanceHandle } from './instanceHandle'; diff --git a/packages/zoe/src/clientSupport/instanceHandle.js b/packages/zoe/src/clientSupport/instanceHandle.js deleted file mode 100644 index 8989e3a5af9..00000000000 --- a/packages/zoe/src/clientSupport/instanceHandle.js +++ /dev/null @@ -1,8 +0,0 @@ -import { E } from '@agoric/eventual-send'; - -export const makeGetInstanceHandle = inviteIssuerP => inviteP => - E(inviteIssuerP) - .getAmountOf(inviteP) - .then(amount => { - return amount.value[0].instanceHandle; - }); diff --git a/packages/zoe/src/contractFacet.js b/packages/zoe/src/contractFacet.js deleted file mode 100644 index 8399c92f837..00000000000 --- a/packages/zoe/src/contractFacet.js +++ /dev/null @@ -1,359 +0,0 @@ -// @ts-check - -// This is the Zoe contract facet. Each time we make a new instance of a -// contract we will start by creating a new vat and running this code in it. In -// order to install this code in a vat, Zoe needs to import a bundle containing -// this code. We will eventually have an automated process, but for now, every -// time this file is edited, the bundle must be manually rebuilt with -// `yarn build-zcfBundle`. - -import { assert, details, q } from '@agoric/assert'; -import { E } from '@agoric/eventual-send'; -import { isOfferSafe } from './offerSafety'; -import { areRightsConserved } from './rightsConservation'; -import { assertKeywordName, getKeywords } from './cleanProposal'; -import { makeContractTables } from './state'; -import { filterObj, filterFillAmounts, tuple } from './objArrayConversion'; -import { evalContractBundle } from './evalContractCode'; - -import '../exported'; -import './internal-types'; - -/** - * Create the contract facet. - * - * @returns {{ startContract: StartContract }} The returned instance - */ -export function buildRootObject(_vatPowers) { - // Need to make this variable a tuple type, since technically - // it could be mutated before we pass it to filterObj. - // - // If we want to avoid this type magic, just supply it as the - // verbatim argument of filterObj. - const visibleInstanceRecordFields = tuple( - 'handle', - 'installationHandle', - 'publicAPI', - 'terms', - 'issuerKeywordRecord', - 'brandKeywordRecord', - ); - /** - * @param {InstanceRecord & ZcfInstanceRecord} instanceRecord - */ - const visibleInstanceRecord = instanceRecord => - filterObj(instanceRecord, visibleInstanceRecordFields); - - const { offerTable, issuerTable } = makeContractTables(); - - const getAmountMathForBrand = brand => issuerTable.get(brand).amountMath; - - const assertOffersAreActive = candidateOfferHandles => - candidateOfferHandles.forEach(offerHandle => - assert(offerTable.has(offerHandle), details`Offer is not active`), - ); - - /** @param {OfferRecord & PrivateOfferRecord} offerRecord */ - const removeAmountsAndNotifier = offerRecord => - filterObj(offerRecord, ['handle', 'instanceHandle', 'proposal']); - /** @param {IssuerRecord & PrivateIssuerRecord} issuerRecord */ - const removePurse = issuerRecord => - filterObj(issuerRecord, ['issuer', 'brand', 'amountMath']); - - const doGetCurrentAllocation = (offerHandle, brandKeywordRecord) => { - const { currentAllocation } = offerTable.get(offerHandle); - if (brandKeywordRecord === undefined) { - return currentAllocation; - } - /** @type {AmountMathKeywordRecord} */ - const amountMathKeywordRecord = {}; - Object.getOwnPropertyNames(brandKeywordRecord).forEach(keyword => { - const brand = brandKeywordRecord[keyword]; - amountMathKeywordRecord[keyword] = issuerTable.get(brand).amountMath; - }); - return filterFillAmounts(currentAllocation, amountMathKeywordRecord); - }; - - const doGetCurrentAllocations = (offerHandles, brandKeywordRecords) => { - if (brandKeywordRecords === undefined) { - return offerHandles.map(offerHandle => - doGetCurrentAllocation(offerHandle), - ); - } - return offerHandles.map((offerHandle, i) => - doGetCurrentAllocation(offerHandle, brandKeywordRecords[i]), - ); - }; - - const reallocate = (offerHandles, newAllocations, zoeForZcf) => { - assertOffersAreActive(offerHandles); - // We may want to handle this with static checking instead. - // Discussion at: https://github.com/Agoric/agoric-sdk/issues/1017 - assert( - offerHandles.length >= 2, - details`reallocating must be done over two or more offers`, - ); - assert( - offerHandles.length === newAllocations.length, - details`There must be as many offerHandles as entries in newAllocations`, - ); - - // 1) Ensure 'offer safety' for each offer separately. - const makeOfferSafeReallocation = (offerHandle, newAllocation) => { - const { proposal, currentAllocation } = offerTable.get(offerHandle); - const reallocation = harden({ - ...currentAllocation, - ...newAllocation, - }); - - assert( - isOfferSafe(getAmountMathForBrand, proposal, reallocation), - details`The reallocation was not offer safe`, - ); - return reallocation; - }; - - // Make the reallocation and test for offer safety by comparing the - // reallocation to the original proposal. - const reallocations = offerHandles.map((offerHandle, i) => - makeOfferSafeReallocation(offerHandle, newAllocations[i]), - ); - - // 2. Ensure that rights are conserved overall. - const flattened = arr => [].concat(...arr); - const flattenAllocations = allocations => - flattened(allocations.map(allocation => Object.values(allocation))); - - const currentAllocations = offerTable - .getOffers(offerHandles) - .map(({ currentAllocation }) => currentAllocation); - const previousAmounts = flattenAllocations(currentAllocations); - const newAmounts = flattenAllocations(reallocations); - - assert( - areRightsConserved(getAmountMathForBrand, previousAmounts, newAmounts), - details`Rights are not conserved in the proposed reallocation`, - ); - - // 3. Save the reallocations. - offerTable.updateAmounts(offerHandles, reallocations); - E(zoeForZcf).updateAmounts(offerHandles, reallocations); - }; - - const addNewIssuer = (issuerP, keyword, instanceRecord, zoeForZcf) => - issuerTable.getPromiseForIssuerRecord(issuerP).then(issuerRecord => { - E(zoeForZcf).addNewIssuer(issuerP, keyword); - assertKeywordName(keyword); - assert( - !getKeywords(instanceRecord.issuerKeywordRecord).includes(keyword), - details`keyword ${keyword} must be unique`, - ); - const newIssuerKeywordRecord = { - ...instanceRecord.issuerKeywordRecord, - [keyword]: issuerRecord.issuer, - }; - const newBrandKeywordRecord = { - ...instanceRecord.brandKeywordRecord, - [keyword]: issuerRecord.brand, - }; - instanceRecord.brandKeywordRecord = newBrandKeywordRecord; - instanceRecord.issuerKeywordRecord = newIssuerKeywordRecord; - - return removePurse(issuerRecord); - }); - - function completeOffers(offerHandlesToDrop, zoeForZcf) { - assertOffersAreActive(offerHandlesToDrop); - offerTable.deleteOffers(offerHandlesToDrop); - return E(zoeForZcf).completeOffers(offerHandlesToDrop); - } - - /** - * Create the contract-facing Zoe facet. - * - * @param {ZoeService} zoeService - * @param {InstanceRecord & ZcfInstanceRecord} instanceRecord - * @param {Issuer} inviteIssuer - * @param {ZoeForZcf} zoeForZcf - * @returns {ContractFacet} - */ - const makeContractFacet = ( - zoeService, - instanceRecord, - inviteIssuer, - zoeForZcf, - ) => { - let publicApiInitialized = false; - - /** @type {ContractFacet} */ - const contractFacet = { - reallocate: (offerHandles, newAllocations) => - reallocate(offerHandles, newAllocations, zoeForZcf), - addNewIssuer: (issuerP, keyword) => - addNewIssuer(issuerP, keyword, instanceRecord, zoeForZcf), - complete: offerHandlesToDrop => - completeOffers(offerHandlesToDrop, zoeForZcf), - makeInvitation: (offerHandler, inviteDesc, options = harden({})) => { - const inviteHandler = harden({ invoke: offerHandler }); - return E(zoeForZcf).makeInvitation(inviteHandler, inviteDesc, options); - }, - initPublicAPI: newPublicAPI => { - assert( - !publicApiInitialized, - details`the publicAPI has already been initialized`, - ); - publicApiInitialized = true; - E(zoeForZcf).updatePublicAPI(newPublicAPI); - }, - - // The methods below are pure and have no side-effects // - getZoeService: () => zoeService, - - getInviteIssuer: () => inviteIssuer, - - getOfferNotifier: offerHandle => - E(zoeService).getOfferNotifier(offerHandle), - - getOfferStatuses: offerHandles => { - const { active, inactive } = offerTable.getOfferStatuses(offerHandles); - return harden({ active, inactive }); - }, - isOfferActive: offerHandle => { - const isActive = offerTable.isOfferActive(offerHandle); - // if offer isn't present, we do not want to throw. - return isActive; - }, - getOffers: offerHandles => { - assertOffersAreActive(offerHandles); - return offerTable.getOffers(offerHandles).map(removeAmountsAndNotifier); - }, - getOffer: offerHandle => { - assertOffersAreActive(harden([offerHandle])); - return removeAmountsAndNotifier(offerTable.get(offerHandle)); - }, - getCurrentAllocation: (offerHandle, brandKeywordRecord) => { - assertOffersAreActive(harden([offerHandle])); - return doGetCurrentAllocation(offerHandle, brandKeywordRecord); - }, - getCurrentAllocations: (offerHandles, brandKeywordRecords) => { - assertOffersAreActive(offerHandles); - return doGetCurrentAllocations(offerHandles, brandKeywordRecords); - }, - getInstanceRecord: () => visibleInstanceRecord(instanceRecord), - getIssuerForBrand: brand => issuerTable.get(brand).issuer, - getBrandForIssuer: issuer => issuerTable.brandFromIssuer(issuer), - getAmountMath: getAmountMathForBrand, - getVatAdmin: () => instanceRecord.adminNode, - }; - return harden(contractFacet); - }; - - /** - * @returns {ZcfForZoe} - */ - const makeZcfForZoe = (instanceHandle, zoeForZcf) => { - /** @type {ZcfForZoe} */ - const zcfForZoe = { - addOffer: (offerHandle, proposal, allocation) => { - /** @type {Updater} */ - const ignoringUpdater = harden({ - updateState: _ => {}, - finish: _ => {}, - fail: _ => {}, - }); - - /** @type {Omit} */ - const offerRecord = { - instanceHandle, - proposal, - currentAllocation: allocation, - notifier: undefined, - updater: ignoringUpdater, - }; - - const { exit } = proposal; - const [exitKind] = Object.getOwnPropertyNames(exit); - - /** @type {CompleteObj | undefined} */ - let completeObj; - const completeOffer = () => { - return completeOffers(harden([offerHandle]), zoeForZcf); - }; - - if (exitKind === 'afterDeadline') { - // Automatically complete offer after deadline. - assert(exit.afterDeadline); - E(exit.afterDeadline.timer).setWakeup( - exit.afterDeadline.deadline, - harden({ - wake: () => completeOffer(), - }), - ); - } else if (exitKind === 'onDemand') { - // Add to offerResult an object with a complete() method to support - // complete offer on demand. Note: we cannot add the `completeOffer` - // function directly to the offerResult because our marshalling layer - // only allows two kinds of objects: records (no methods and only - // data) and presences (local proxies for objects that may have - // methods). - completeObj = { - complete: () => completeOffer(), - }; - } else { - // if exitRule.kind is 'waived' the user has no ability to complete - // on demand - assert( - exitKind === 'waived', - details`exit kind was not recognized: ${q(exitKind)}`, - ); - } - offerTable.create(offerRecord, offerHandle); - return completeObj; - }, - }; - return harden(zcfForZoe); - }; - - /** - * Makes a contract instance from an installation and returns a - * unique handle for the instance that can be shared, as well as - * other information, such as the terms used in the instance. - * - * @type {StartContract} - */ - const startContract = params => { - const contractCode = evalContractBundle(params.bundle); - - const { issuerKeywordRecord } = params.instanceData; - const issuersP = Object.getOwnPropertyNames(issuerKeywordRecord).map( - keyword => issuerKeywordRecord[keyword], - ); - - // invoke contract and return inner facet to Zoe. - const invokeContract = () => { - const contractFacet = makeContractFacet( - params.zoeService, - // copy so we can update brands and keywords - { ...params.instanceData }, - params.inviteIssuer, - params.zoeForZcf, - ); - /** @type {Promise>} */ - const inviteP = E(contractCode).makeContract(contractFacet); - return { - inviteP, - zcfForZoe: makeZcfForZoe(params.instanceData.handle, params.zoeForZcf), - }; - }; - - // The issuers may not have been seen before, so we must wait for - // the issuer records to be available synchronously - return issuerTable - .getPromiseForIssuerRecords(issuersP) - .then(invokeContract); - }; - - return harden({ startContract }); -} - -harden(buildRootObject); diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js new file mode 100644 index 00000000000..d447fb224d7 --- /dev/null +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -0,0 +1,196 @@ +// @ts-check + +// This is the Zoe contract facet. Each time we make a new instance of a +// contract we will start by creating a new vat and running this code in it. In +// order to install this code in a vat, Zoe needs to import a bundle containing +// this code. We will eventually have an automated process, but for now, every +// time this file is edited, the bundle must be manually rebuilt with +// `yarn build-zcfBundle`. + +import { assert, details } from '@agoric/assert'; +import { E } from '@agoric/eventual-send'; +import makeWeakStore from '@agoric/weak-store'; + +import { areRightsConserved } from './rightsConservation'; +import { makeIssuerTable } from '../issuerTable'; +import { assertKeywordName, getKeywords } from '../zoeService/cleanProposal'; +import { evalContractBundle } from './evalContractCode'; +import { makeSeatAdmin } from './seat'; +import { makeExitObj } from './exit'; + +import '../../exported'; +import '../internal-types'; + +export function buildRootObject() { + /** @type ExecuteContract */ + const executeContract = async ( + bundle, + zoeService, + invitationIssuer, + zoeInstanceAdmin, + hardenedInstanceRecord, + ) => { + const instanceRecord = { ...hardenedInstanceRecord }; + const issuerTable = makeIssuerTable(); + const getAmountMath = brand => issuerTable.get(brand).amountMath; + + const invitationHandleToHandler = makeWeakStore('invitationHandle'); + const seatToSeatAdmin = makeWeakStore('seat'); + + const issuers = Object.values(instanceRecord.issuerKeywordRecord); + + const getPromiseForIssuerRecords = issuersP => + Promise.all(issuersP.map(issuerTable.getPromiseForIssuerRecord)); + + await getPromiseForIssuerRecords(issuers); + + const allSeatStagings = new Set(); + + /** @type ContractFacet */ + const zcf = { + reallocate: (/** @type SeatStaging[] */ ...seatStagings) => { + // We may want to handle this with static checking instead. + // Discussion at: https://github.com/Agoric/agoric-sdk/issues/1017 + assert( + seatStagings.length >= 2, + details`reallocating must be done over two or more seats`, + ); + + seatStagings.forEach(seatStaging => + assert( + allSeatStagings.has(seatStaging), + details`The seatStaging ${seatStaging} was not recognized`, + ), + ); + + // Ensure that rights are conserved overall. Offer safety was + // already checked when an allocation was staged for an individual seat. + const flattened = arr => [].concat(...arr); + const flattenAllocations = allocations => + flattened(allocations.map(allocation => Object.values(allocation))); + + const previousAllocations = seatStagings.map(seatStaging => + seatStaging.getSeat().getCurrentAllocation(), + ); + const previousAmounts = flattenAllocations(previousAllocations); + + const newAllocations = seatStagings.map(seatStaging => + seatStaging.getStagedAllocation(), + ); + const newAmounts = flattenAllocations(newAllocations); + + assert( + areRightsConserved(getAmountMath, previousAmounts, newAmounts), + details`Rights are not conserved in the proposed reallocation`, + ); + + // Commit the staged allocations and inform Zoe of the + // newAllocation. + seatStagings.forEach(seatStaging => + seatToSeatAdmin.get(seatStaging.getSeat()).commit(seatStaging), + ); + }, + saveIssuer: (issuerP, keyword) => { + return E(zoeInstanceAdmin) + .saveIssuer(issuerP, keyword) + .then(() => { + return issuerTable + .getPromiseForIssuerRecord(issuerP) + .then(issuerRecord => { + assertKeywordName(keyword); + assert( + !getKeywords(instanceRecord.issuerKeywordRecord).includes( + keyword, + ), + details`keyword ${keyword} must be unique`, + ); + instanceRecord.issuerKeywordRecord = { + ...instanceRecord.issuerKeywordRecord, + [keyword]: issuerRecord.issuer, + }; + instanceRecord.brandKeywordRecord = { + ...instanceRecord.brandKeywordRecord, + [keyword]: issuerRecord.brand, + }; + + return issuerRecord; + }); + }); + }, + makeInvitation: (offerHandler, description, customProperties = {}) => { + assert.typeof( + description, + 'string', + details`invitations must have a description string: ${description}`, + ); + /** @type {InvitationHandle} */ + const invitationHandle = {}; + harden(invitationHandle); + invitationHandleToHandler.init(invitationHandle, offerHandler); + /** @type {Promise>} */ + const invitationP = E(zoeInstanceAdmin).makeInvitation( + invitationHandle, + description, + customProperties, + ); + return invitationP; + }, + // Shutdown the entire vat and give payouts + shutdown: () => E(zoeInstanceAdmin).shutdown(), + + // The methods below are pure and have no side-effects // + getZoeService: () => zoeService, + getInvitationIssuer: () => invitationIssuer, + getInstanceRecord: () => harden({ ...instanceRecord }), + getBrandForIssuer: issuer => + issuerTable.getIssuerRecordByIssuer(issuer).brand, + getAmountMath, + }; + harden(zcf); + + // To Zoe, we will return the invite and an object such that Zoe + // can tell us about new seats. + /** @type AddSeatObj */ + const addSeatObj = { + addSeat: (invitationHandle, zoeSeat, seatData) => { + const { seatAdmin, seat } = makeSeatAdmin( + allSeatStagings, + zoeSeat, + seatData, + getAmountMath, + ); + seatToSeatAdmin.init(seat, seatAdmin); + const offerHandler = invitationHandleToHandler.get(invitationHandle); + const offerResultP = E(offerHandler)(seat).catch(err => { + seat.exit(); + throw err; + }); + const exitObj = makeExitObj(seatData.proposal, zoeSeat); + /** @type AddSeatResult */ + const addSeatResult = { offerResultP, exitObj }; + return harden(addSeatResult); + }, + }; + harden(addSeatObj); + + // First, evaluate the contract code bundle. + const contractCode = evalContractBundle(bundle); + + // Next, execute the contract code, passing in zcf and the terms + /** @type {Promise} */ + return E(contractCode) + .start(zcf, instanceRecord.terms) + .then(({ creatorFacet, publicFacet, creatorInvitation }) => { + return harden({ + creatorFacet, + publicFacet, + creatorInvitation, + addSeatObj, + }); + }); + }; + + return harden({ executeContract }); +} + +harden(buildRootObject); diff --git a/packages/zoe/src/evalContractCode.js b/packages/zoe/src/contractFacet/evalContractCode.js similarity index 67% rename from packages/zoe/src/evalContractCode.js rename to packages/zoe/src/contractFacet/evalContractCode.js index 2bdc322f051..2276c0a8c94 100644 --- a/packages/zoe/src/evalContractCode.js +++ b/packages/zoe/src/contractFacet/evalContractCode.js @@ -1,9 +1,5 @@ -import Nat from '@agoric/nat'; +// @ts-check -import makeIssuerKit from '@agoric/ertp'; -import { assert } from '@agoric/assert'; -import { makePromiseKit } from '@agoric/promise-kit'; -import { sameStructure } from '@agoric/same-structure'; import { importBundle } from '@agoric/import-bundle'; import { HandledPromise } from '@agoric/eventual-send'; @@ -14,15 +10,8 @@ const evalContractBundle = (bundle, additionalEndowments = {}) => { log: console.info, }; - // TODO: this should really only be console and HandledPromise const defaultEndowments = { - assert, console: louderConsole, - harden, - Nat, - makeIssuerKit, - makePromiseKit, - sameStructure, HandledPromise, }; diff --git a/packages/zoe/src/contractFacet/exit.js b/packages/zoe/src/contractFacet/exit.js new file mode 100644 index 00000000000..1c57b677946 --- /dev/null +++ b/packages/zoe/src/contractFacet/exit.js @@ -0,0 +1,37 @@ +import { assert, details, q } from '@agoric/assert'; +import { E } from '@agoric/eventual-send'; + +/** @type MakeExitObj */ +export const makeExitObj = (proposal, zoeSeat) => { + const [exitKind] = Object.getOwnPropertyNames(proposal.exit); + + /** @type {ExitObj | undefined} */ + let exitObj; + + if (exitKind === 'afterDeadline') { + // Automatically exit the seat after deadline. + E(proposal.exit.afterDeadline.timer).setWakeup( + proposal.exit.afterDeadline.deadline, + harden({ + wake: () => E(zoeSeat).exit(), + }), + ); + } else if (exitKind === 'onDemand') { + // Allow the user to exit their seat on demand. Note: we must wrap + // it in an object to send it back to Zoe because our marshalling layer + // only allows two kinds of objects: records (no methods and only + // data) and presences (local proxies for objects that may have + // methods). + exitObj = { + exit: () => E(zoeSeat).exit(), + }; + } else { + // if exitKind is 'waived' the user has no ability to exit their seat + // on demand + assert( + exitKind === 'waived', + details`exit kind was not recognized: ${q(exitKind)}`, + ); + } + return exitObj; +}; diff --git a/packages/zoe/src/offerSafety.js b/packages/zoe/src/contractFacet/offerSafety.js similarity index 50% rename from packages/zoe/src/offerSafety.js rename to packages/zoe/src/contractFacet/offerSafety.js index 05ee41cab8d..6cae5332d82 100644 --- a/packages/zoe/src/offerSafety.js +++ b/packages/zoe/src/contractFacet/offerSafety.js @@ -1,12 +1,15 @@ +// @ts-check + /** + * @template {string} T * Helper to perform satisfiesWant and satisfiesGive. Is * allocationAmount greater than or equal to requiredAmount for every * keyword of giveOrWant? - * @param {(Brand) => AmountMath} getAmountMath - * @param {Proposal["give"] | Proposal["want"]} giveOrWant + * @param {(brand: Brand) => AmountMath} getAmountMath + * @param {ProposalRecord["give"] | ProposalRecord["want"]} giveOrWant * @param {AmountKeywordRecord} allocation */ -const satisfiesInternal = (getAmountMath, giveOrWant, allocation) => { +const satisfiesInternal = (getAmountMath, giveOrWant = {}, allocation) => { const isGTEByKeyword = ([keyword, requiredAmount]) => { // If there is no allocation for a keyword, we know the giveOrWant // is not satisfied without checking further. @@ -23,15 +26,17 @@ const satisfiesInternal = (getAmountMath, giveOrWant, allocation) => { /** * For this allocation to satisfy what the user wanted, their * allocated amounts must be greater than or equal to proposal.want. - * @param {(Brand) => AmountMath} getAmountMath - a function that - * takes a brand and returns the appropriate amountMath. The function - * must have an amountMath for every brand in proposal.want. - * @param {Proposal} proposal - the rules that accompanied the escrow - * of payments that dictate what the user expected to get back from - * Zoe. A proposal is a record with keys `give`, `want`, and `exit`. - * `give` and `want` are records with keywords as keys and amounts as - * values. The proposal is a user's understanding of the contract that - * they are entering when they make an offer. + * @template {string} T + * @param {(brand: Brand) => AmountMath} getAmountMath - a + * function that takes a brand and returns the appropriate amountMath. + * The function must have an amountMath for every brand in + * proposal.want. + * @param {ProposalRecord} proposal - the rules that accompanied the + * escrow of payments that dictate what the user expected to get back + * from Zoe. A proposal is a record with keys `give`, `want`, and + * `exit`. `give` and `want` are records with keywords as keys and + * amounts as values. The proposal is a user's understanding of the + * contract that they are entering when they make an offer. * @param {AmountKeywordRecord} allocation - a record with keywords * as keys and amounts as values. These amounts are the reallocation * to be given to a user. @@ -43,15 +48,17 @@ const satisfiesWant = (getAmountMath, proposal, allocation) => * For this allocation to count as a full refund, the allocated * amounts must be greater than or equal to what was originally * offered (proposal.give). - * @param {(Brand) => AmountMath} getAmountMath - a function that - * takes a brand and returns the appropriate amountMath. The function - * must have an amountMath for every brand in proposal.give. - * @param {Proposal} proposal - the rules that accompanied the escrow - * of payments that dictate what the user expected to get back from - * Zoe. A proposal is a record with keys `give`, `want`, and `exit`. - * `give` and `want` are records with keywords as keys and amounts as - * values. The proposal is a user's understanding of the contract that - * they are entering when they make an offer. + * @template {string} T + * @param {(brand: Brand) => AmountMath} getAmountMath - a + * function that takes a brand and returns the appropriate amountMath. + * The function must have an amountMath for every brand in + * proposal.give. + * @param {ProposalRecord} proposal - the rules that accompanied the + * escrow of payments that dictate what the user expected to get back + * from Zoe. A proposal is a record with keys `give`, `want`, and + * `exit`. `give` and `want` are records with keywords as keys and + * amounts as values. The proposal is a user's understanding of the + * contract that they are entering when they make an offer. * @param {AmountKeywordRecord} allocation - a record with keywords * as keys and amounts as values. These amounts are the reallocation * to be given to a user. @@ -66,16 +73,17 @@ const satisfiesGive = (getAmountMath, proposal, allocation) => * `proposal.give` (giving a refund) or whether we fully satisfy * `proposal.want`. Both can be fully satisfied. * - * @param {(Brand) => AmountMath} getAmountMath - a function that - * takes a brand and returns the appropriate amountMath. The function - * must have an amountMath for every brand in proposal.want and - * proposal.give. - * @param {Proposal} proposal - the rules that accompanied the escrow - * of payments that dictate what the user expected to get back from - * Zoe. A proposal is a record with keys `give`, `want`, and `exit`. - * `give` and `want` are records with keywords as keys and amounts as - * values. The proposal is a user's understanding of the contract that - * they are entering when they make an offer. + * @template {string} T + * @param {(brand: Brand) => AmountMath} getAmountMath - a + * function that takes a brand and returns the appropriate amountMath. + * The function must have an amountMath for every brand in + * proposal.want and proposal.give. + * @param {ProposalRecord} proposal - the rules that accompanied the + * escrow of payments that dictate what the user expected to get back + * from Zoe. A proposal is a record with keys `give`, `want`, and + * `exit`. `give` and `want` are records with keywords as keys and + * amounts as values. The proposal is a user's understanding of the + * contract that they are entering when they make an offer. * @param {AmountKeywordRecord} allocation - a record with keywords * as keys and amounts as values. These amounts are the reallocation * to be given to a user. diff --git a/packages/zoe/src/rightsConservation.js b/packages/zoe/src/contractFacet/rightsConservation.js similarity index 70% rename from packages/zoe/src/rightsConservation.js rename to packages/zoe/src/contractFacet/rightsConservation.js index 7bed17c9896..e118ab5f8a8 100644 --- a/packages/zoe/src/rightsConservation.js +++ b/packages/zoe/src/contractFacet/rightsConservation.js @@ -3,16 +3,17 @@ import makeStore from '@agoric/store'; import { assert, details } from '@agoric/assert'; -import '../exported'; -import './internal-types'; +import '../../exported'; +import '../internal-types'; /** + * @template {string} T * Iterate over the amounts and sum, storing the sums in a * map by brand. - * @param {(brand: Brand) => AmountMath} getAmountMath - a function + * @param {(brand: Brand) => AmountMath} getAmountMath - a function * to get amountMath given a brand. - * @param {Amount[]} amounts - an array of amounts - * @returns {Store} sumsByBrand - a map of Brand keys and + * @param {Amount[]} amounts - an array of amounts + * @returns {Store, Amount>} sumsByBrand - a map of Brand keys and * Amount values. The amounts are the sums. */ const sumByBrand = (getAmountMath, amounts) => { @@ -30,11 +31,12 @@ const sumByBrand = (getAmountMath, amounts) => { }; /** + * @template {string} T * Do the left sums by brand equal the right sums by brand? - * @param {(brand: Brand) => AmountMath} getAmountMath - a function + * @param {(brand: Brand) => AmountMath} getAmountMath - a function * to get amountMath given a brand. - * @param {Store} leftSumsByBrand - a map of brands to sums - * @param {Store} rightSumsByBrand - a map of brands to sums + * @param {Store, Amount>} leftSumsByBrand - a map of brands to sums + * @param {Store, Amount>} rightSumsByBrand - a map of brands to sums * indexed by issuer */ const isEqualPerBrand = (getAmountMath, leftSumsByBrand, rightSumsByBrand) => { @@ -56,13 +58,14 @@ const isEqualPerBrand = (getAmountMath, leftSumsByBrand, rightSumsByBrand) => { }; /** + * @template {string} T * `areRightsConserved` checks that the total amount per brand is * equal to the total amount per brand in the proposed reallocation - * @param {(brand: Brand) => AmountMath} getAmountMath - a function + * @param {(brand: Brand) => AmountMath} getAmountMath - a function * to get amountMath given a brand. - * @param {Amount[]} previousAmounts - an array of the amounts before the + * @param {Amount[]} previousAmounts - an array of the amounts before the * proposed reallocation - * @param {Amount[]} newAmounts - an array of the amounts in the + * @param {Amount[]} newAmounts - an array of the amounts in the * proposed reallocation * * @returns {boolean} isEqualPerBrand diff --git a/packages/zoe/src/contractFacet/seat.js b/packages/zoe/src/contractFacet/seat.js new file mode 100644 index 00000000000..22aec4016d4 --- /dev/null +++ b/packages/zoe/src/contractFacet/seat.js @@ -0,0 +1,87 @@ +// @ts-check + +import { E } from '@agoric/eventual-send'; +import { assert, details } from '@agoric/assert'; + +import { isOfferSafe } from './offerSafety'; + +import '../../exported'; +import '../internal-types'; + +/** @type MakeSeatAdmin */ +export const makeSeatAdmin = ( + allSeatStagings, + zoeSeat, + seatData, + getAmountMath, +) => { + // The proposal and notifier are not reassigned. + const { proposal, notifier } = seatData; + + // The currentAllocation, exited, and stagedAllocation may be reassigned. + let currentAllocation = harden(seatData.initialAllocation); + let exited = false; // seat is "active" + + /** @type ZCFSeatAdmin */ + const seatAdmin = harden({ + commit: seatStaging => { + assert( + allSeatStagings.has(seatStaging), + details`The seatStaging ${seatStaging} was not recognized`, + ); + currentAllocation = seatStaging.getStagedAllocation(); + E(zoeSeat).replaceAllocation(currentAllocation); + }, + }); + + /** @type {ZCFSeat} */ + const seat = harden({ + exit: () => { + exited = true; + E(zoeSeat).exit(); + }, + kickOut: (msg = 'Kicked out of seat') => { + seat.exit(); + assert.fail(msg); + }, + getNotifier: () => notifier, + hasExited: () => exited, + getProposal: () => proposal, + getAmountAllocated: (keyword, brand) => { + if (currentAllocation[keyword] !== undefined) { + return currentAllocation[keyword]; + } + return getAmountMath(brand).getEmpty(); + }, + getCurrentAllocation: () => currentAllocation, + isOfferSafe: newAllocation => { + const reallocation = harden({ + ...currentAllocation, + ...newAllocation, + }); + + return isOfferSafe(getAmountMath, proposal, reallocation); + }, + stage: newAllocation => { + // Check offer safety. + const allocation = harden({ + ...currentAllocation, + ...newAllocation, + }); + + assert( + isOfferSafe(getAmountMath, proposal, allocation), + details`The reallocation was not offer safe`, + ); + + const seatStaging = { + getSeat: () => seat, + getStagedAllocation: () => allocation, + }; + allSeatStagings.add(seatStaging); + return seatStaging; + }, + }); + + return harden({ seat, seatAdmin }); +}; diff --git a/packages/zoe/src/contractSupport/auctions.js b/packages/zoe/src/contractSupport/auctions.js index f2afee549a4..9a133fd824c 100644 --- a/packages/zoe/src/contractSupport/auctions.js +++ b/packages/zoe/src/contractSupport/auctions.js @@ -1,15 +1,15 @@ -export const secondPriceLogic = (bidAmountMath, bidOfferHandles, bids) => { +export const secondPriceLogic = (bidAmountMath, bidSeats) => { let highestBid = bidAmountMath.getEmpty(); let secondHighestBid = bidAmountMath.getEmpty(); - let highestBidOfferHandle; - // eslint-disable-next-line array-callback-return - bidOfferHandles.map((offerHandle, i) => { - const bid = bids[i]; + let highestBidSeat; + + bidSeats.forEach(bidSeat => { + const bid = bidSeat.getAmountAllocated('Bid', highestBid.brand); // If the bid is greater than the highestBid, it's the new highestBid if (bidAmountMath.isGTE(bid, highestBid)) { secondHighestBid = highestBid; highestBid = bid; - highestBidOfferHandle = offerHandle; + highestBidSeat = bidSeat; } else if (bidAmountMath.isGTE(bid, secondHighestBid)) { // If the bid is not greater than the highest bid, but is greater // than the second highest bid, it is the new second highest bid. @@ -17,48 +17,35 @@ export const secondPriceLogic = (bidAmountMath, bidOfferHandles, bids) => { } }); return harden({ - winnerOfferHandle: highestBidOfferHandle, + winnerSeat: highestBidSeat, winnerBid: highestBid, price: secondHighestBid, }); }; -export const closeAuction = ( - zcf, - { auctionLogicFn, sellerOfferHandle, allBidHandles }, -) => { - const { brandKeywordRecord } = zcf.getInstanceRecord(); - const bidAmountMath = zcf.getAmountMath(brandKeywordRecord.Ask); - const assetAmountMath = zcf.getAmountMath(brandKeywordRecord.Asset); - - // Filter out any inactive bids - const { active: activeBidHandles } = zcf.getOfferStatuses( - harden(allBidHandles), - ); +export const closeAuction = (zcf, auctionLogicFn, sellSeat, bidSeats) => { + const { + want: { Ask: minBid }, + give: { Asset: assetAmount }, + } = sellSeat.getProposal(); + const bidAmountMath = zcf.getAmountMath(minBid.brand); + const assetAmountMath = zcf.getAmountMath(assetAmount.brand); - const getBids = amountsKeywordRecord => amountsKeywordRecord.Bid; - const bids = zcf.getCurrentAllocations(activeBidHandles).map(getBids); - const assetAmount = zcf.getOffer(sellerOfferHandle).proposal.give.Asset; - - const { winnerOfferHandle, winnerBid, price } = auctionLogicFn( + const { winnerSeat, winnerBid, price } = auctionLogicFn( bidAmountMath, - activeBidHandles, - bids, + bidSeats, ); // The winner gets to keep the difference between their bid and the // price paid. const winnerRefund = bidAmountMath.subtract(winnerBid, price); - const newSellerAmounts = { Asset: assetAmountMath.getEmpty(), Ask: price }; - const newWinnerAmounts = { Asset: assetAmount, Bid: winnerRefund }; - // Everyone else gets a refund so their values remain the // same. zcf.reallocate( - harden([sellerOfferHandle, winnerOfferHandle]), - harden([newSellerAmounts, newWinnerAmounts]), + sellSeat.stage({ Asset: assetAmountMath.getEmpty(), Ask: price }), + winnerSeat.stage({ Asset: assetAmount, Bid: winnerRefund }), ); - const allOfferHandles = harden([sellerOfferHandle, ...activeBidHandles]); - zcf.complete(allOfferHandles); + sellSeat.exit(); + bidSeats.forEach(bidSeat => bidSeat.exit()); }; diff --git a/packages/zoe/src/contractSupport/index.js b/packages/zoe/src/contractSupport/index.js index 812de835862..b75aeacef58 100644 --- a/packages/zoe/src/contractSupport/index.js +++ b/packages/zoe/src/contractSupport/index.js @@ -13,5 +13,10 @@ export { makeStateMachine } from './stateMachine'; export { defaultAcceptanceMsg, defaultRejectMsg, - makeZoeHelpers, + trade, + swap, + assertProposalKeywords, + assertIssuerKeywords, + satisfies, + escrowAndAllocateTo, } from './zoeHelpers'; diff --git a/packages/zoe/src/contractSupport/zoeHelpers.js b/packages/zoe/src/contractSupport/zoeHelpers.js index f254bd37c8e..31186e70812 100644 --- a/packages/zoe/src/contractSupport/zoeHelpers.js +++ b/packages/zoe/src/contractSupport/zoeHelpers.js @@ -2,439 +2,390 @@ import { assert, details } from '@agoric/assert'; import { sameStructure } from '@agoric/same-structure'; -import { E, HandledPromise } from '@agoric/eventual-send'; -import { satisfiesWant, isOfferSafe } from '../offerSafety'; +import { E } from '@agoric/eventual-send'; +import { makePromiseKit } from '@agoric/promise-kit'; + +import { satisfiesWant } from '../contractFacet/offerSafety'; import '../../exported'; export const defaultRejectMsg = `The offer was invalid. Please check your refund.`; export const defaultAcceptanceMsg = `The offer has been accepted. Once the contract has been completed, please check your payout`; -const getKeys = obj => harden(Object.getOwnPropertyNames(obj || {})); const getKeysSorted = obj => harden(Object.getOwnPropertyNames(obj || {}).sort()); + +// Compare actual keys to expected keys. If expectedKeys is +// undefined, return true trivially. +const checkKeys = (actual, expected) => { + if (expected === undefined) { + return true; + } + return sameStructure(getKeysSorted(actual), getKeysSorted(expected)); +}; + /** - * Makes an object with helper functions useful to zoe contracts. - * + * Given toGains (an AmountKeywordRecord), and allocations (a pair, + * 'to' and 'from', of AmountKeywordRecords), all the entries in + * toGains will be added to 'to'. If fromLosses is defined, all the + * entries in fromLosses are subtracted from 'from'. (If fromLosses + * is not defined, toGains is subtracted from 'from'.) * @param {ContractFacet} zcf + * @param {FromToAllocations} allocations - the 'to' and 'from' + * allocations + * @param {AmountKeywordRecord} toGains - what should be gained in + * the 'to' allocation + * @param {AmountKeywordRecord} [fromLosses=toGains] - what should be lost in + * the 'from' allocation. If not defined, fromLosses is equal to + * toGains. Note that the total amounts should always be equal; it + * is the keywords that might be different. + * @returns {FromToAllocations} allocations - new allocations + * + * @typedef FromToAllocations + * @property {AmountKeywordRecord} from + * @property {AmountKeywordRecord} to */ -export const makeZoeHelpers = zcf => { - const zoeService = zcf.getZoeService(); - - const rejectOffer = (offerHandle, msg = defaultRejectMsg) => { - assert.fail(msg); - }; - - // Compare the keys of actual with expected keys and reject offer if - // not sameStructure. If expectedKeys is undefined, no comparison occurs. - const rejectKeysIf = ( - offerHandle, - actual, - expected, - msg = defaultRejectMsg, - ) => { - if (expected !== undefined) { - if (!sameStructure(getKeysSorted(actual), getKeysSorted(expected))) { - return rejectOffer(offerHandle, msg); - } - } - return undefined; - }; - // Compare actual keys to expected keys. If expectedKeys is - // undefined, return true trivially. - const checkKeys = (actual, expected) => { - if (expected === undefined) { - return true; +const calcNewAllocations = ( + zcf, + allocations, + toGains, + fromLosses = toGains, +) => { + const subtract = (amount, amountToSubtract) => { + const { brand } = amount; + const amountMath = zcf.getAmountMath(brand); + if (amountToSubtract !== undefined) { + return amountMath.subtract(amount, amountToSubtract); } - return sameStructure(getKeysSorted(actual), getKeysSorted(expected)); + return amount; }; - /** - * Given toGains (an AmountKeywordRecord), and allocations (a pair, - * 'to' and 'from', of AmountKeywordRecords), all the entries in - * toGains will be added to 'to'. If fromLosses is defined, all the - * entries in fromLosses are subtracted from 'from'. (If fromLosses - * is not defined, toGains is subtracted from 'from'.) - * - * @param {FromToAllocations} allocations - the 'to' and 'from' - * allocations - * @param {AmountKeywordRecord} toGains - what should be gained in - * the 'to' allocation - * @param {AmountKeywordRecord} [fromLosses=toGains] - what should be lost in - * the 'from' allocation. If not defined, fromLosses is equal to - * toGains. Note that the total amounts should always be equal; it - * is the keywords that might be different. - * @returns {FromToAllocations} allocations - new allocations - * - * @typedef FromToAllocations - * @property {AmountKeywordRecord} from - * @property {AmountKeywordRecord} to - */ - const calcNewAllocations = (allocations, toGains, fromLosses = toGains) => { - const subtract = (amount, amountToSubtract) => { + const add = (amount, amountToAdd) => { + if (amount && amountToAdd) { const { brand } = amount; const amountMath = zcf.getAmountMath(brand); - if (amountToSubtract !== undefined) { - return amountMath.subtract(amount, amountToSubtract); - } - return amount; - }; + return amountMath.add(amount, amountToAdd); + } + return amount || amountToAdd; + }; - const add = (amount, amountToAdd) => { - if (amount && amountToAdd) { - const { brand } = amount; - const amountMath = zcf.getAmountMath(brand); - return amountMath.add(amount, amountToAdd); - } - return amount || amountToAdd; - }; + const newFromAllocation = Object.fromEntries( + Object.entries(allocations.from).map(([keyword, allocAmount]) => { + return [keyword, subtract(allocAmount, fromLosses[keyword])]; + }), + ); + + const allToKeywords = [ + ...Object.keys(toGains), + ...Object.keys(allocations.to), + ]; + + const newToAllocation = Object.fromEntries( + allToKeywords.map(keyword => [ + keyword, + add(allocations.to[keyword], toGains[keyword]), + ]), + ); + + return harden({ + from: newFromAllocation, + to: newToAllocation, + }); +}; - const newFromAllocation = Object.fromEntries( - Object.entries(allocations.from).map(([keyword, allocAmount]) => { - return [keyword, subtract(allocAmount, fromLosses[keyword])]; - }), - ); +const mergeAllocations = (currentAllocation, allocation) => { + const newAllocation = { + ...currentAllocation, + ...allocation, + }; + return newAllocation; +}; - const allToKeywords = [ - ...Object.keys(toGains), - ...Object.keys(allocations.to), - ]; +export const assertIssuerKeywords = (zcf, expected) => { + const { issuerKeywordRecord } = zcf.getInstanceRecord(); + const actual = getKeysSorted(issuerKeywordRecord); + expected = [...expected]; // in case hardened + expected.sort(); + assert( + sameStructure(actual, harden(expected)), + details`keywords: ${actual} were not as expected: ${expected}`, + ); +}; - const newToAllocation = Object.fromEntries( - allToKeywords.map(keyword => [ - keyword, - add(allocations.to[keyword], toGains[keyword]), - ]), - ); +/** + * Check if the keywords match expected. Returns a boolean and does + * not throw. + * @param {ZCFSeat} seat + * @param {ExpectedRecord} expected + * @returns {boolean} + */ +export const checkIfProposal = (seat, expected) => { + const actual = seat.getProposal(); + return ( + // Check that the "give" keys match expected keys. + checkKeys(actual.give, expected.give) && + // Check that the "want" keys match expected keys. + checkKeys(actual.want, expected.want) && + // Check that the "exit" key (i.e. "onDemand") matches the expected key. + checkKeys(actual.exit, expected.exit) + ); +}; - return harden({ - from: newFromAllocation, - to: newToAllocation, - }); - }; +/** + * Check whether an update to currentAllocation satisfies + * proposal.want. Note that this is half of the offer safety + * check; whether the allocation constitutes a refund is not + * checked. Allocation is merged with currentAllocation + * (allocations' values prevailing if the keywords are the same) + * to produce the newAllocation. + * @param {ContractFacet} zcf + * @param {ZCFSeat} seat + * @param {Allocation} allocation + * @returns {boolean} + */ +export const satisfies = (zcf, seat, allocation) => { + const currentAllocation = seat.getCurrentAllocation(); + const newAllocation = mergeAllocations(currentAllocation, allocation); + const proposal = seat.getProposal(); + return satisfiesWant(zcf.getAmountMath, proposal, newAllocation); +}; - const mergeAllocations = (currentAllocation, allocation) => { - const newAllocation = { - ...currentAllocation, - ...allocation, - }; - return newAllocation; - }; +/** @type {Trade} */ +export const trade = (zcf, keepLeft, tryRight) => { + assert( + keepLeft.seat !== tryRight.seat, + details`an offer cannot trade with itself`, + ); + let leftAllocation = keepLeft.seat.getCurrentAllocation(); + let rightAllocation = tryRight.seat.getCurrentAllocation(); + try { + // for all the keywords and amounts in leftGains, transfer from + // right to left + ({ from: rightAllocation, to: leftAllocation } = calcNewAllocations( + zcf, + { from: rightAllocation, to: leftAllocation }, + keepLeft.gains, + tryRight.losses, + )); + // For all the keywords and amounts in rightGains, transfer from + // left to right + ({ from: leftAllocation, to: rightAllocation } = calcNewAllocations( + zcf, + { from: leftAllocation, to: rightAllocation }, + tryRight.gains, + keepLeft.losses, + )); + } catch (err) { + console.log(err); + throw tryRight.seat.kickOut(); + } + + // Check whether reallocate would error before calling. If + // it would error, reject the right offer and return. + const offerSafeForLeft = keepLeft.seat.isOfferSafe(leftAllocation); + const offerSafeForRight = tryRight.seat.isOfferSafe(rightAllocation); + if (!(offerSafeForLeft && offerSafeForRight)) { + console.log(`currentLeftAllocation`, keepLeft.seat.getCurrentAllocation()); + console.log(`currentRightAllocation`, tryRight.seat.getCurrentAllocation()); + console.log(`proposed left reallocation`, leftAllocation); + console.log(`proposed right reallocation`, rightAllocation); + // show the constraints + console.log(`left want`, keepLeft.seat.getProposal().want); + console.log(`right want`, tryRight.seat.getProposal().want); + + if (!offerSafeForLeft) { + console.log(`offer not safe for left`); + } + if (!offerSafeForRight) { + console.log(`offer not safe for right`); + } + return tryRight.seat.kickOut(); + } - const helpers = harden({ - getKeys, - assertKeywords: expected => { - const { issuerKeywordRecord } = zcf.getInstanceRecord(); - const actual = getKeysSorted(issuerKeywordRecord); - expected = [...expected]; // in case hardened - expected.sort(); - assert( - sameStructure(actual, harden(expected)), - details`keywords: ${actual} were not as expected: ${expected}`, - ); - }, - rejectIfNotProposal: (offerHandle, expected) => { - const { proposal: actual } = zcf.getOffer(offerHandle); - rejectKeysIf(offerHandle, actual.give, expected.give); - rejectKeysIf(offerHandle, actual.want, expected.want); - rejectKeysIf(offerHandle, actual.exit, expected.exit); - }, - checkIfProposal: (offerHandle, expected) => { - const { proposal: actual } = zcf.getOffer(offerHandle); - return ( - // Check that the "give" keys match expected keys. - checkKeys(actual.give, expected.give) && - // Check that the "want" keys match expected keys. - checkKeys(actual.want, expected.want) && - // Check that the "exit" key (i.e. "onDemand") matches the expected key. - checkKeys(actual.exit, expected.exit) - ); - }, - getActiveOffers: handles => - zcf.getOffers(zcf.getOfferStatuses(handles).active), - rejectOffer, + return zcf.reallocate( + keepLeft.seat.stage(leftAllocation), + tryRight.seat.stage(rightAllocation), + ); +}; - /** - * Check whether an update to currentAllocation satisfies - * proposal.want. Note that this is half of the offer safety - * check; whether the allocation constitutes a refund is not - * checked. Allocation is merged with currentAllocation - * (allocations' values prevailing if the keywords are the same) - * to produce the newAllocation. - * @param {OfferHandle} offerHandle - * @param {allocation} amountKeywordRecord - * @returns {boolean} - */ - satisfies: (offerHandle, allocation) => { - const currentAllocation = zcf.getCurrentAllocation(offerHandle); - const newAllocation = mergeAllocations(currentAllocation, allocation); - const { proposal } = zcf.getOffer(offerHandle); - return satisfiesWant(zcf.getAmountMath, proposal, newAllocation); +/** + * If the two handles can trade, then swap their compatible assets, + * marking both offers as complete. + * + * The surplus remains with the original offer. For example if + * offer A gives 5 moola and offer B only wants 3 moola, offer A + * retains 2 moola. + * + * If the keep offer is no longer active (it was already completed), the try + * offer will be rejected with a message (provided by 'keepHandleInactiveMsg'). + * + * TODO: If the try offer is no longer active, swap() should terminate with + * a useful error message, like defaultRejectMsg. + * + * If the swap fails, no assets are transferred, and the 'try' offer is rejected. + * + * @param {ZCFSeat} keepSeat + * @param {ZCFSeat} trySeat + * @param {String} [keepHandleInactiveMsg] + */ +export const swap = ( + zcf, + keepSeat, + trySeat, + keepHandleInactiveMsg = 'prior offer is unavailable', +) => { + if (keepSeat.hasExited()) { + throw trySeat.kickOut(keepHandleInactiveMsg); + } + + trade( + zcf, + { + seat: keepSeat, + gains: keepSeat.getProposal().want, }, - - /** - * Check whether an update to currentAllocation satisfies offer - * safety. Note that this is the equivalent of `satisfiesWant` || - * `satisfiesGive`. Allocation is merged with currentAllocation - * (allocations' values prevailing if the keywords are the same) - * to produce the newAllocation. - * @param {OfferHandle} offerHandle - * @param {AmountKeywordRecord} allocation - * @returns {boolean} - */ - isOfferSafe: (offerHandle, allocation) => { - const currentAllocation = zcf.getCurrentAllocation(offerHandle); - const newAllocation = mergeAllocations(currentAllocation, allocation); - const { proposal } = zcf.getOffer(offerHandle); - return isOfferSafe(zcf.getAmountMath, proposal, newAllocation); + { + seat: trySeat, + gains: trySeat.getProposal().want, }, + ); - /** - * Trade between left and right so that left and right end up with - * the declared gains. - * @param {offerHandleGainsLossesRecord} keepLeft - * @param {offerHandleGainsLossesRecord} tryRight - * @returns {undefined | Error} - * - * @typedef {Object} offerHandleGainsLossesRecord - * @property {OfferHandle} offerHandle - * @property {AmountKeywordRecord} gains - what the offer will - * gain as a result of this trade - * @property {AmountKeywordRecord=} losses - what the offer will - * give up as a result of this trade. Losses is optional, but can - * only be omitted if the keywords for both offers are the same. - * If losses is not defined, the gains of the other offer is - * subtracted. - */ - trade: (keepLeft, tryRight) => { - assert( - keepLeft.offerHandle !== tryRight.offerHandle, - details`an offer cannot trade with itself`, - ); - let leftAllocation = zcf.getCurrentAllocation(keepLeft.offerHandle); - let rightAllocation = zcf.getCurrentAllocation(tryRight.offerHandle); + keepSeat.exit(); + trySeat.exit(); + return defaultAcceptanceMsg; +}; - try { - // for all the keywords and amounts in leftGains, transfer from - // right to left - ({ from: rightAllocation, to: leftAllocation } = calcNewAllocations( - { from: rightAllocation, to: leftAllocation }, - keepLeft.gains, - tryRight.losses, - )); - // For all the keywords and amounts in rightGains, transfer from - // left to right - ({ from: leftAllocation, to: rightAllocation } = calcNewAllocations( - { from: leftAllocation, to: rightAllocation }, - tryRight.gains, - keepLeft.losses, - )); - } catch (err) { - return rejectOffer(tryRight.offerHandle); - } +/** + * @typedef ExpectedRecord + * @property {Record} [want] + * @property {Record} [give] + * @property {Partial>} [exit] + */ - // Check whether reallocate would error before calling. If - // it would error, reject the right offer and return. - const offerSafeForLeft = helpers.isOfferSafe( - keepLeft.offerHandle, - leftAllocation, - ); - const offerSafeForRight = helpers.isOfferSafe( - tryRight.offerHandle, - rightAllocation, - ); - if (!(offerSafeForLeft && offerSafeForRight)) { - console.log( - `currentLeftAllocation`, - zcf.getCurrentAllocation(keepLeft.offerHandle), - ); - console.log( - `currentRightAllocation`, - zcf.getCurrentAllocation(tryRight.offerHandle), - ); - console.log(`proposed left reallocation`, leftAllocation); - console.log(`proposed right reallocation`, rightAllocation); - // show the contraints - console.log( - `left want`, - zcf.getOffer(keepLeft.offerHandle).proposal.want, - ); - console.log( - `right want`, - zcf.getOffer(tryRight.offerHandle).proposal.want, +/** + * Make an offerHook that wraps the provided `offerHook`, to first + * check the submitted offer against an `expected` record that says + * what shape of proposal is acceptable. + * + * This ExpectedRecord is like a Proposal, but the amounts in 'want' + * and 'give' should be null; the exit clause should specify a rule with + * null contents. If the client submits an Offer which does not match + * these expectations, that offer will be rejected (and refunded). + * + * @param {OfferHandler} offerHandler + * @param {ExpectedRecord} expected + */ +export const assertProposalKeywords = (offerHandler, expected) => + /** @param {ZCFSeat} seat */ + seat => { + const actual = seat.getProposal(); + // Does not check values + const assertKeys = (a, e) => { + if (e !== undefined) { + assert( + sameStructure(getKeysSorted(a), getKeysSorted(e)), + details`actual ${a} did not match expected ${e}`, ); - - if (!offerSafeForLeft) { - console.log(`offer not safe for left`); - } - if (!offerSafeForRight) { - console.log(`offer not safe for right`); - } - return rejectOffer(tryRight.offerHandle); - } - zcf.reallocate( - [keepLeft.offerHandle, tryRight.offerHandle], - [leftAllocation, rightAllocation], - ); - return undefined; - }, - - /** - * If the two handles can trade, then swap their compatible assets, - * marking both offers as complete. - * - * The surplus remains with the original offer. For example if - * offer A gives 5 moola and offer B only wants 3 moola, offer A - * retains 2 moola. - * - * If the keep offer is no longer active (it was already completed), the try - * offer will be rejected with a message (provided by 'keepHandleInactiveMsg'). - * - * TODO: If the try offer is no longer active, swap() should terminate with - * a useful error message, like defaultRejectMsg. - * - * If the swap fails, no assets are transferred, and the 'try' offer is rejected. - * - * @param {OfferHandle} keepHandle - * @param {OfferHandle} tryHandle - * @param {String} [keepHandleInactiveMsg] - */ - swap: ( - keepHandle, - tryHandle, - keepHandleInactiveMsg = 'prior offer is unavailable', - ) => { - if (!zcf.isOfferActive(keepHandle)) { - throw helpers.rejectOffer(tryHandle, keepHandleInactiveMsg); } + }; + assertKeys(actual.give, expected.give); + assertKeys(actual.want, expected.want); + assertKeys(actual.exit, expected.exit); + return offerHandler(seat); + }; +/** + * Return a record with a promise for the userSeat and a promise for + * the zcfSeat + * + * This offer will have an empty 'give' and 'want', making it useful + * for contracts to use for unrestricted internal asset reallocation. + * One example is the Autoswap contract, which uses an empty offer + * to manage internal escrowed assets. + * @param {ContractFacet} zcf + * @returns {{userSeat: Promise, zcfSeat: Promise}} + */ +export const makeEmptyOffer = zcf => { + const ZCFSeatPromiseKit = makePromiseKit(); + const invite = zcf.makeInvitation( + seat => ZCFSeatPromiseKit.resolve(seat), + 'empty offer', + ); + const zoeService = zcf.getZoeService(); + return { + userSeat: E(zoeService).offer(invite), + zcfSeat: ZCFSeatPromiseKit.promise, + }; +}; - helpers.trade( +/** + * Escrow payments with Zoe and reallocate the amount of each + * payment to a recipient. + * + * @template {string} T + * @param {ContractFacet} zcf + * @param {ZCFSeat} recipientSeat + * @param {AmountKeywordRecord} giveAmountKeywordRecord - the keywords + * and amounts to give to recipient. Keywords must match the keywords + * for the payments. + * @param {PaymentKeywordRecord} paymentKeywordRecord + * @returns {Promise} + */ +export const escrowAndAllocateTo = ( + zcf, + recipientSeat, + giveAmountKeywordRecord, + paymentKeywordRecord, +) => { + // We will create a temporary seat to be able to escrow our payment + // with Zoe. + let tempSeat; + + // We need to make an invitation and store the seat associated with + // that invitation for future use + const contractSelfInvite = zcf.makeInvitation( + seat => (tempSeat = seat), + 'self invite', + ); + // To escrow the payments, we must get the Zoe Service facet and + // make an offer + const proposal = harden({ give: giveAmountKeywordRecord }); + + return E(zcf.getZoeService()) + .offer(contractSelfInvite, proposal, paymentKeywordRecord) + .then(() => { + // At this point, the temporary offer has the amount from the + // payment but nothing else. The recipient offer may have any + // allocation, so we can't assume the allocation is currently empty for this + // keyword. + + trade( + zcf, { - offerHandle: keepHandle, - gains: zcf.getOffer(keepHandle).proposal.want, + seat: tempSeat, + gains: {}, + losses: giveAmountKeywordRecord, }, { - offerHandle: tryHandle, - gains: zcf.getOffer(tryHandle).proposal.want, + seat: recipientSeat, + gains: giveAmountKeywordRecord, }, ); - zcf.complete([keepHandle, tryHandle]); - return defaultAcceptanceMsg; - }, + // Exit the temporary seat + tempSeat.exit(); - /** - * @typedef ExpectedRecord - * @property {Record} [want] - * @property {Record} [give] - * @property {Partial>} [exit] - */ - - /** - * Make an offerHook that wraps the provided `offerHook`, to first - * check the submitted offer against an `expected` record that says - * what shape of proposal is acceptable. - * - * This ExpectedRecord is like a Proposal, but the amounts in 'want' - * and 'give' should be null; the exit clause should specify a rule with - * null contents. If the client submits an Offer which does not match - * these expectations, that offer will be rejected (and refunded). - * - * @template OC - * @param {OfferHook} offerHook - * @param {ExpectedRecord} expected - */ - checkHook: (offerHook, expected) => - /** @param {OfferHandle} offerHandle */ - offerHandle => { - helpers.rejectIfNotProposal(offerHandle, expected); - return offerHook(offerHandle); - }, - - /** - * Return a Promise for an OfferHandle. - * - * This offer will have an empty 'give' and 'want', making it useful - * for contracts to use for unrestricted internal asset reallocation. - * One example is the Autoswap contract, which uses an empty offer - * to manage internal escrowed assets. - * - * @returns {Promise} - */ - makeEmptyOffer: () => - new HandledPromise(resolve => { - const invite = zcf.makeInvitation( - offerHandle => resolve(offerHandle), - 'empty offer', - ); - E(zoeService).offer(invite); - }), - - /** - * Escrow a payment with Zoe and reallocate the amount of the - * payment to a recipient. - * - * @param {Object} obj - * @param {Amount} obj.amount - * @param {Payment} obj.payment - * @param {String} obj.keyword - * @param {OfferHandle} obj.recipientHandle - * @returns {Promise} - */ - escrowAndAllocateTo: ({ amount, payment, keyword, recipientHandle }) => { - // We will create a temporary offer to be able to escrow our payment - // with Zoe. - let tempHandle; - - // We need to make an invite and store the offerHandle of that - // invite for future use. - const contractSelfInvite = zcf.makeInvitation( - offerHandle => (tempHandle = offerHandle), - 'self invite', - ); - // To escrow the payment, we must get the Zoe Service facet and - // make an offer - const proposal = harden({ give: { Temp: amount } }); - const payments = harden({ Temp: payment }); - - return E(zcf.getZoeService()) - .offer(contractSelfInvite, proposal, payments) - .then(() => { - // At this point, the temporary offer has the amount from the - // payment but nothing else. The recipient offer may have any - // allocation, so we can't assume the allocation is currently empty for this - // keyword. - - helpers.trade( - { - offerHandle: tempHandle, - gains: {}, - losses: { Temp: amount }, - }, - { - offerHandle: recipientHandle, - gains: { [keyword]: amount }, - }, - ); - - // Complete the temporary offerHandle - zcf.complete([tempHandle]); - - // Now, the temporary offer no longer exists, but the recipient - // offer is allocated the value of the payment. - }); - }, - /* - * Given a brand, assert that the mathHelpers for that issuer - * are 'nat' mathHelpers - */ - assertNatMathHelpers: brand => { - const amountMath = zcf.getAmountMath(brand); - assert( - amountMath.getMathHelpersName() === 'nat', - details`issuer must have natMathHelpers`, - ); - }, - }); - return helpers; + // Now, the temporary seat no longer exists, but the recipient + // seat is allocated the value of the payment. + }); +}; +/* + * Given a brand, assert that the mathHelpers for that issuer are + * 'nat' mathHelpers + */ +export const assertNatMathHelpers = (zcf, brand) => { + const amountMath = zcf.getAmountMath(brand); + assert( + amountMath.getMathHelpersName() === 'nat', + details`issuer must have natMathHelpers`, + ); }; diff --git a/packages/zoe/src/contracts/atomicSwap.js b/packages/zoe/src/contracts/atomicSwap.js index c3309c02c38..20e4de7e197 100644 --- a/packages/zoe/src/contracts/atomicSwap.js +++ b/packages/zoe/src/contracts/atomicSwap.js @@ -1,7 +1,11 @@ // @ts-check // Eventually will be importable from '@agoric/zoe-contract-support' -import { makeZoeHelpers } from '../contractSupport'; +import { + assertIssuerKeywords, + swap, + assertProposalKeywords, +} from '../contractSupport'; import '../../exported'; @@ -14,39 +18,42 @@ import '../../exported'; * amount no greater than the original's give, and a give amount at least as * large as the original's want. * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { - const { swap, assertKeywords, checkHook } = makeZoeHelpers(zcf); - assertKeywords(harden(['Asset', 'Price'])); +const start = (zcf, _terms) => { + assertIssuerKeywords(zcf, harden(['Asset', 'Price'])); - const makeMatchingInvite = firstOfferHandle => { - const { - proposal: { want, give }, - } = zcf.getOffer(firstOfferHandle); + /** @type {OfferHandler} */ + const makeMatchingInvitation = firstSeat => { + const { want, give } = firstSeat.getProposal(); - return zcf.makeInvitation( - offerHandle => swap(firstOfferHandle, offerHandle), + /** @type {OfferHandler} */ + const secondSeatOfferHandler = secondSeat => + swap(zcf, firstSeat, secondSeat); + + const secondSeatInvitation = zcf.makeInvitation( + secondSeatOfferHandler, 'matchOffer', - harden({ - customProperties: { - asset: give.Asset, - price: want.Price, - }, - }), + { + asset: give.Asset, + price: want.Price, + }, ); + return secondSeatInvitation; }; - const firstOfferExpected = harden({ + const firstProposalExpected = harden({ give: { Asset: null }, want: { Price: null }, }); - return zcf.makeInvitation( - checkHook(makeMatchingInvite, firstOfferExpected), + const creatorInvitation = zcf.makeInvitation( + assertProposalKeywords(makeMatchingInvitation, firstProposalExpected), 'firstOffer', ); + + return { creatorInvitation }; }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/automaticRefund.js b/packages/zoe/src/contracts/automaticRefund.js index 1368888fbe0..6c49704ce79 100644 --- a/packages/zoe/src/contracts/automaticRefund.js +++ b/packages/zoe/src/contracts/automaticRefund.js @@ -13,29 +13,29 @@ import '../../exported'; * Since the contract doesn't attempt any reallocation, the offer can contain * anything in `give` and `want`. The amount in `give` will be returned, and * `want` will be ignored. - * + * @type {ContractStartFn} * @param {ContractFacet} zcf */ -const makeContract = zcf => { +const start = (zcf, _terms) => { let offersCount = 0; - const refundOfferHook = offerHandle => { + /** @type {OfferHandler} */ + const refund = seat => { offersCount += 1; - zcf.complete(harden([offerHandle])); + seat.exit(); return `The offer was accepted`; }; - const makeRefundInvite = () => - zcf.makeInvitation(refundOfferHook, 'getRefund'); + const makeRefundInvitation = () => zcf.makeInvitation(refund, 'getRefund'); + + const publicFacet = harden({ + getOffersCount: () => offersCount, + makeInvitation: makeRefundInvitation, + }); - zcf.initPublicAPI( - harden({ - getOffersCount: () => offersCount, - makeInvite: makeRefundInvite, - }), - ); + const creatorInvitation = makeRefundInvitation(); - return makeRefundInvite(); + return harden({ creatorInvitation, publicFacet }); }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/barterExchange.js b/packages/zoe/src/contracts/barterExchange.js index 2738cb9a647..70a88f0a661 100644 --- a/packages/zoe/src/contracts/barterExchange.js +++ b/packages/zoe/src/contracts/barterExchange.js @@ -1,10 +1,12 @@ // @ts-check import makeStore from '@agoric/store'; -import { makeZoeHelpers, defaultAcceptanceMsg } from '../contractSupport'; - import '../../exported'; +// Eventually will be importable from '@agoric/zoe-contract-support' +import { trade } from '../contractSupport'; +import { satisfies } from '../contractSupport/zoeHelpers'; + /** * This Barter Exchange accepts offers to trade arbitrary goods for other * things. It doesn't require registration of Issuers. If two offers satisfy @@ -16,16 +18,14 @@ import '../../exported'; * successful trader gets their `want` and may trade with counter-parties who * specify any amount up to their specified `give`. * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { +const start = (zcf, _terms) => { // bookOrders is a Map of Maps. The first key is the brand of the offer's // GIVE, and the second key is the brand of its WANT. For each offer, we // store its handle and the amounts for `give` and `want`. const bookOrders = makeStore('bookOrders'); - const { satisfies, trade } = makeZoeHelpers(zcf); - function lookupBookOrders(brandIn, brandOut) { if (!bookOrders.has(brandIn)) { bookOrders.init(brandIn, new Map()); @@ -42,8 +42,8 @@ const makeContract = zcf => { function findMatchingTrade(newDetails, orders) { return orders.find(order => { return ( - satisfies(newDetails.offerHandle, { Out: order.amountIn }) && - satisfies(order.offerHandle, { Out: newDetails.amountIn }) + satisfies(zcf, newDetails.offerSeat, { Out: order.amountIn }) && + satisfies(zcf, order.offerSeat, { Out: newDetails.amountIn }) ); }); } @@ -65,8 +65,9 @@ const makeContract = zcf => { if (matchingTrade) { // reallocate by giving each side what it wants trade( + zcf, { - offerHandle: matchingTrade.offerHandle, + seat: matchingTrade.offerSeat, gains: { Out: matchingTrade.amountOut, }, @@ -75,7 +76,7 @@ const makeContract = zcf => { }, }, { - offerHandle: offerDetails.offerHandle, + seat: offerDetails.offerSeat, gains: { Out: offerDetails.amountOut, }, @@ -85,7 +86,8 @@ const makeContract = zcf => { }, ); removeFromOrders(matchingTrade); - zcf.complete([offerDetails.offerHandle, matchingTrade.offerHandle]); + offerDetails.offerSeat.exit(); + matchingTrade.offerSeat.exit(); return true; } @@ -100,35 +102,41 @@ const makeContract = zcf => { orders.push(offerDetails); } - function extractOfferDetails(offerHandle) { + function extractOfferDetails(offerSeat) { const { give: { In: amountIn }, want: { Out: amountOut }, - } = zcf.getOffer(offerHandle).proposal; + } = offerSeat.getProposal(); return { - offerHandle, + offerSeat, amountIn, amountOut, }; } - const exchangeOfferHook = offerHandle => { - const offerDetails = extractOfferDetails(offerHandle); + + /** @type {OfferHandler} */ + const exchangeOfferHandler = offerSeat => { + const offerDetails = extractOfferDetails(offerSeat); if (!tradeWithMatchingOffer(offerDetails)) { addToBook(offerDetails); } - return defaultAcceptanceMsg; + return 'Trade completed.'; }; const makeExchangeInvite = () => - zcf.makeInvitation(exchangeOfferHook, 'exchange'); + zcf.makeInvitation(exchangeOfferHandler, 'exchange'); - zcf.initPublicAPI(harden({ makeInvite: makeExchangeInvite })); + const inviteFacet = harden({ makeInvite: makeExchangeInvite }); + const creatorFacet = harden({ + makeInvite: makeExchangeInvite, + getPublicFacet: () => inviteFacet, + }); - return makeExchangeInvite(); + return { publicFacet: inviteFacet, creatorFacet }; }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/coveredCall.js b/packages/zoe/src/contracts/coveredCall.js index 1f72f93073c..09ab6c556a0 100644 --- a/packages/zoe/src/contracts/coveredCall.js +++ b/packages/zoe/src/contracts/coveredCall.js @@ -3,87 +3,87 @@ import { assert, details } from '@agoric/assert'; // Eventually will be importable from '@agoric/zoe-contract-support' -import { makeZoeHelpers } from '../contractSupport'; +import { + swap, + assertIssuerKeywords, + assertProposalKeywords, +} from '../contractSupport'; import '../../exported'; -const rejectMsg = `The covered call option is expired.`; - /** - * In a covered call, a digital asset's owner sells a call - * option. A call option is the right to buy the digital asset at a - * pre-determined price, called the strike price. The call option has an expiry - * date, when the contract will be cancelled. + * In a covered call, a digital asset's owner sells a call option. A + * call option is the right to buy the digital asset at a + * pre-determined price, called the strike price. The call option has + * an expiry date, when the contract will be cancelled. * - * In this contract, the expiry date is the deadline when - * the offer escrowing the underlying assets is cancelled. - * Therefore, the proposal for the underlying assets must have an - * exit record with the key "afterDeadline". + * In this contract, the expiry date is the deadline when the offer + * escrowing the underlying assets is cancelled. Therefore, the + * proposal for the underlying assets must have an exit record with + * the key "afterDeadline". * - * The invite received by the covered call creator is the call option. It has - * this additional information in the invite's value: - * { expirationDate, timerAuthority, underlyingAsset, strikePrice } + * The invitation received by the covered call creator is the call + * option. It has this additional information in the invitation's + * value: { expirationDate, timerAuthority, underlyingAsset, + * strikePrice } * * The initial proposal should be: * { - * give: { UnderlyingAsset: assetAmount }, - * want: { StrikePrice: priceAmount }, - * exit: { afterDeadline: { deadline: time, timer: timer } }, + * give: { UnderlyingAsset: assetAmount }, want: { StrikePrice: + * priceAmount }, exit: { afterDeadline: { deadline: time, timer: + * timer } }, * } - * The result of the initial offer is { payout, outcome }, where payout will - * eventually resolve to the strikePrice, and outcome is an assayable invitation - * to buy the underlying asset. Since the contract provides assurance that the - * underlying asset is available on the specified terms, the invite itself can - * be traded as a valuable good. + * The result of the initial offer is a seat where the payout will + * eventually resolve to the strikePrice, and the offerResult is an + * assayable invitation to buy the underlying asset. Since the + * contract provides assurance that the underlying asset is available + * on the specified terms, the invitation itself can be traded as a + * valuable good. * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { - const { swap, assertKeywords, checkHook } = makeZoeHelpers(zcf); - assertKeywords(harden(['UnderlyingAsset', 'StrikePrice'])); +const start = (zcf, _terms) => { + assertIssuerKeywords(zcf, harden(['UnderlyingAsset', 'StrikePrice'])); + + const makeCallOption = sellerSeat => { + const { want, give, exit } = sellerSeat.getProposal(); + const rejectMsg = `The covered call option is expired.`; - const makeCallOptionInvite = sellerHandle => { - const { - proposal: { want, give, exit }, - } = zcf.getOffer(sellerHandle); + const exerciseOption = exerciserSeat => { + debugger; + return swap(zcf, sellerSeat, exerciserSeat, rejectMsg); + }; - const exerciseOptionHook = offerHandle => - swap(sellerHandle, offerHandle, rejectMsg); const exerciseOptionExpected = harden({ give: { StrikePrice: null }, want: { UnderlyingAsset: null }, }); - assert( - exit && exit.afterDeadline, - details`exit must be afterDeadline, not ${exit}`, - ); - return zcf.makeInvitation( - checkHook(exerciseOptionHook, exerciseOptionExpected), + assertProposalKeywords(exerciseOption, exerciseOptionExpected), 'exerciseOption', harden({ - customProperties: { - expirationDate: exit.afterDeadline.deadline, - timerAuthority: exit.afterDeadline.timer, - underlyingAsset: give.UnderlyingAsset, - strikePrice: want.StrikePrice, - }, + expirationDate: exit.afterDeadline.deadline, + timerAuthority: exit.afterDeadline.timer, + underlyingAsset: give.UnderlyingAsset, + strikePrice: want.StrikePrice, }), ); }; - const writeOptionExpected = harden({ + const makeCallOptionExpected = harden({ give: { UnderlyingAsset: null }, want: { StrikePrice: null }, exit: { afterDeadline: null }, }); - return zcf.makeInvitation( - checkHook(makeCallOptionInvite, writeOptionExpected), + const creatorInvitation = zcf.makeInvitation( + assertProposalKeywords(makeCallOption, makeCallOptionExpected), 'makeCallOption', ); + + return harden({ creatorInvitation }); }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/mintPayments.js b/packages/zoe/src/contracts/mintPayments.js index 102713556f5..6fa675a4fd2 100644 --- a/packages/zoe/src/contracts/mintPayments.js +++ b/packages/zoe/src/contracts/mintPayments.js @@ -2,7 +2,7 @@ // @ts-check import makeIssuerKit from '@agoric/ertp'; -import { makeZoeHelpers } from '../contractSupport'; +import { escrowAndAllocateTo } from '../contractSupport'; import '../../exported'; @@ -21,61 +21,56 @@ import '../../exported'; * * @param {ContractFacet} zcf */ -const makeContract = zcf => { +const start = (zcf, _terms) => { // Create the internal token mint for a fungible digital asset const { issuer, mint, amountMath } = makeIssuerKit('tokens'); - const zoeHelpers = makeZoeHelpers(zcf); - // We need to tell Zoe about this issuer and add a keyword for the // issuer. Let's call this the 'Token' issuer. - return zcf.addNewIssuer(issuer, 'Token').then(() => { + return zcf.saveIssuer(issuer, 'Token').then(() => { // We need to wait for the promise to resolve (meaning that Zoe // has done the work of adding a new issuer). - const offerHook = offerHandle => { - // We will send everyone who makes an offer 1000 tokens - - const tokens1000 = amountMath.make(1000); - const payment = mint.mintPayment(tokens1000); + const mintPayment = (extent = 1000) => seat => { + const amount = amountMath.make(extent); + const payment = mint.mintPayment(amount); // Let's use a helper function which escrows the payment with // Zoe, and reallocates to the recipientHandle. - return zoeHelpers - .escrowAndAllocateTo({ - amount: tokens1000, - payment, - keyword: 'Token', - recipientHandle: offerHandle, - }) - .then(() => { - // Complete the user's offer so that the user gets a payout - zcf.complete(harden([offerHandle])); + return escrowAndAllocateTo( + zcf, + seat, + { Token: amount }, + { Token: payment }, + ).then(() => { + // Exit the seat so the user gets a payout + seat.exit(); - // Since the user is getting the payout through Zoe, we can - // return anything here. Let's return some helpful instructions. - return 'Offer completed. You should receive a payment from Zoe'; - }); + // Since the user is getting the payout through Zoe, we can + // return anything here. Let's return some helpful instructions. + return 'Offer completed. You should receive a payment from Zoe'; + }); }; - // A function for making invites to this contract - const makeInvite = () => zcf.makeInvitation(offerHook, 'mint a payment'); + const creatorFacet = { + // The creator of the instance can send invitations to anyone + // they wish to. + makeInvitation: extent => + zcf.makeInvitation(mintPayment(extent), 'mint a payment'), + getTokenIssuer: () => issuer, + }; - zcf.initPublicAPI( - harden({ - // provide a way for anyone who knows the instanceHandle of - // the contract to make their own invite. - makeInvite, - // make the token issuer public. Note that only the mint can - // make new digital assets. The issuer is ok to make public. - getTokenIssuer: () => issuer, - }), - ); + const publicFacet = { + // Make the token issuer public. Note that only the mint can + // make new digital assets. The issuer is ok to make public. + getTokenIssuer: () => issuer, + }; - // return an invite to the creator of the contract instance - // through Zoe - return makeInvite(); + // Return the creatorFacet to the creator, so they can make + // invitations for others to get payments of tokens. Publish the + // publicFacet. + return harden({ creatorFacet, publicFacet }); }); }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/multipoolAutoswap.js b/packages/zoe/src/contracts/multipoolAutoswap.js index b55aa57ce29..6d91d9d7327 100644 --- a/packages/zoe/src/contracts/multipoolAutoswap.js +++ b/packages/zoe/src/contracts/multipoolAutoswap.js @@ -6,7 +6,7 @@ import { assert, details } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; import { makeTable, makeValidateProperties } from '../table'; -import { assertKeywordName } from '../cleanProposal'; +import { assertKeywordName } from '../zoeService/cleanProposal'; import { makeZoeHelpers, getInputPrice, diff --git a/packages/zoe/src/contracts/publicAuction.js b/packages/zoe/src/contracts/publicAuction.js index d90fe16bf70..475977b3d7e 100644 --- a/packages/zoe/src/contracts/publicAuction.js +++ b/packages/zoe/src/contracts/publicAuction.js @@ -5,9 +5,11 @@ import Nat from '@agoric/nat'; // Eventually will be importable from '@agoric/zoe-contract-support' import { defaultAcceptanceMsg, - makeZoeHelpers, + assertIssuerKeywords, + satisfies, secondPriceLogic, closeAuction, + assertProposalKeywords, } from '../contractSupport'; import '../../exported'; @@ -20,130 +22,102 @@ import '../../exported'; * second highest bidder. * * makeInstance() specifies the issuers and terms ({ numBidsAllowed }) specify - * the number of bids required. An invitation for the seller is returned. The + * the number of bids required. An invitation for the seller is + * returned as the creatorInvitation. The * seller's offer should look like * { give: { Asset: asset }, want: { Ask: minimumBidAmount } } * The asset can be non-fungible, but the Ask amount should be of a fungible * brand. - * The bidder invitations are available from await E(publicAPI).makeInvites(n). Each + * The bidder invitations can be made by calling makeBidInvitation on the creatorFacet. Each * bidder can submit an offer: { give: { Bid: null } want: { Asset: null } }. * - * publicAPI also has methods to find out what's being auctioned - * (getAuctionedAssetsAmounts()), or the minimum bid (getMinimumBid()). - * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { - const { rejectOffer, satisfies, assertKeywords, checkHook } = makeZoeHelpers( - zcf, - ); - - let { - terms: { numBidsAllowed }, - } = zcf.getInstanceRecord(); - numBidsAllowed = Nat(numBidsAllowed !== undefined ? numBidsAllowed : 3); +const start = (zcf, { numBidsAllowed = 3 }) => { + numBidsAllowed = Nat(numBidsAllowed); - let sellerOfferHandle; + let sellSeat; let minimumBid; let auctionedAssets; - const allBidHandles = []; + const bidSeats = []; // seller will use 'Asset' and 'Ask'. buyer will use 'Asset' and 'Bid' - assertKeywords(harden(['Asset', 'Ask'])); + assertIssuerKeywords(zcf, harden(['Asset', 'Ask'])); - const bidderOfferHook = offerHandle => { + /** @type {OfferHandler} */ + const bid = bidSeat => { // Check that the item is still up for auction - if (!zcf.isOfferActive(sellerOfferHandle)) { + if (sellSeat.hasExited()) { const rejectMsg = `The item up for auction is not available or the auction has completed`; - throw rejectOffer(offerHandle, rejectMsg); + throw bidSeat.kickOut(rejectMsg); } - if (allBidHandles.length >= numBidsAllowed) { - throw rejectOffer(offerHandle, `No further bids allowed.`); + if (bidSeats.length >= numBidsAllowed) { + throw bidSeat.kickOut(`No further bids allowed.`); } - const sellerSatisfied = satisfies(sellerOfferHandle, { - Ask: zcf.getCurrentAllocation(offerHandle).Bid, + const sellerSatisfied = satisfies(zcf, sellSeat, { + Ask: bidSeat.getAmountAllocated('Bid', minimumBid.brand), Asset: zcf.getAmountMath(auctionedAssets.brand).getEmpty(), }); - const bidderSatisfied = satisfies(offerHandle, { - Asset: zcf.getCurrentAllocation(sellerOfferHandle).Asset, + const bidderSatisfied = satisfies(zcf, bidSeat, { + Asset: sellSeat.getAmountAllocated('Asset', auctionedAssets.brand), Bid: zcf.getAmountMath(minimumBid.brand).getEmpty(), }); if (!(sellerSatisfied && bidderSatisfied)) { const rejectMsg = `Bid was under minimum bid or for the wrong assets`; - throw rejectOffer(offerHandle, rejectMsg); + throw bidSeat.kickOut(rejectMsg); } // Save valid bid and try to close. - allBidHandles.push(offerHandle); - if (allBidHandles.length >= numBidsAllowed) { - closeAuction(zcf, { - auctionLogicFn: secondPriceLogic, - sellerOfferHandle, - allBidHandles, - }); + bidSeats.push(bidSeat); + if (bidSeats.length >= numBidsAllowed) { + closeAuction(zcf, secondPriceLogic, sellSeat, bidSeats); } return defaultAcceptanceMsg; }; - const bidderOfferExpected = harden({ + const bidExpected = harden({ give: { Bid: null }, want: { Asset: null }, }); - const makeBidderInvite = () => + const makeBidInvitation = () => zcf.makeInvitation( - checkHook(bidderOfferHook, bidderOfferExpected), + assertProposalKeywords(bid, bidExpected), 'bid', harden({ - customProperties: { - auctionedAssets, - minimumBid, - }, + auctionedAssets, + minimumBid, + numBidsAllowed, }), ); - const sellerOfferHook = offerHandle => { - if (auctionedAssets) { - throw rejectOffer(offerHandle, `assets already present`); - } - // Save the valid offer - sellerOfferHandle = offerHandle; - const { proposal } = zcf.getOffer(offerHandle); - auctionedAssets = proposal.give.Asset; - minimumBid = proposal.want.Ask; - return defaultAcceptanceMsg; - }; - - const sellerOfferExpected = harden({ + const sellExpected = harden({ give: { Asset: null }, want: { Ask: null }, }); - const makeSellerInvite = () => - zcf.makeInvitation( - checkHook(sellerOfferHook, sellerOfferExpected), - 'sellAssets', - ); + const sell = seat => { + // Save the valid seat for when the auction closes. + sellSeat = seat; + ({ + give: { Asset: auctionedAssets }, + want: { Ask: minimumBid }, + } = seat.getProposal()); + + // The bid invitations can only be sent out after the assets to be + // auctioned are escrowed. + return harden({ makeBidInvitation }); + }; - zcf.initPublicAPI( - harden({ - makeInvites: numInvites => { - if (auctionedAssets === undefined) { - throw new Error(`No assets are up for auction.`); - } - const invites = []; - for (let i = 0; i < numInvites; i += 1) { - invites.push(makeBidderInvite()); - } - return invites; - }, - getAuctionedAssetsAmounts: () => auctionedAssets, - getMinimumBid: () => minimumBid, - }), + const creatorInvitation = zcf.makeInvitation( + assertProposalKeywords(sell, sellExpected), + 'sellAssets', ); - return makeSellerInvite(); + return harden({ + creatorInvitation, + }); }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/simpleExchange.js b/packages/zoe/src/contracts/simpleExchange.js index 6e3b9fd4300..3c0058c6b68 100644 --- a/packages/zoe/src/contracts/simpleExchange.js +++ b/packages/zoe/src/contracts/simpleExchange.js @@ -1,9 +1,14 @@ // @ts-check import { makeNotifierKit } from '@agoric/notifier'; -import { makeZoeHelpers, defaultAcceptanceMsg } from '../contractSupport'; import '../../exported'; +import { + swap, + satisfies, + checkIfProposal, + assertIssuerKeywords, +} from '../contractSupport/zoeHelpers'; /** * SimpleExchange is an exchange with a simple matching algorithm, which allows @@ -24,55 +29,37 @@ import '../../exported'; * The invitation returned on installation of the contract is the same as what * is returned by calling `await E(publicAPI).makeInvite(). * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { - let sellOfferHandles = []; - let buyOfferHandles = []; +const start = (zcf, _terms) => { + let sellOfferSeats = []; + let buyOfferSeats = []; // eslint-disable-next-line no-use-before-define const { notifier, updater } = makeNotifierKit(getBookOrders()); - const { - rejectOffer, - checkIfProposal, - swap, - satisfies, - getActiveOffers, - assertKeywords, - } = makeZoeHelpers(zcf); + assertIssuerKeywords(zcf, harden(['Asset', 'Price'])); - assertKeywords(harden(['Asset', 'Price'])); - - function flattenOffer(o) { + function dropOnExit(p) { return { - want: o.proposal.want, - give: o.proposal.give, + want: p.want, + give: p.give, }; } - function flattenOrders(offerHandles) { - const result = zcf - .getOffers(zcf.getOfferStatuses(offerHandles).active) - .map(offerRecord => flattenOffer(offerRecord)); - return result; + function flattenOrders(seats) { + const activeSeats = seats.filter(s => !s.hasExited()); + return activeSeats.map(offerRecord => + dropOnExit(offerRecord.getProposal()), + ); } function getBookOrders() { return { - buys: flattenOrders(buyOfferHandles), - sells: flattenOrders(sellOfferHandles), + buys: flattenOrders(buyOfferSeats), + sells: flattenOrders(sellOfferSeats), }; } - function getOffer(offerHandle) { - for (const handle of [...sellOfferHandles, ...buyOfferHandles]) { - if (offerHandle === handle) { - return flattenOffer(getActiveOffers([offerHandle])[0]); - } - } - return 'not an active offer'; - } - // Tell the notifier that there has been a change to the book orders function bookOrdersChanged() { updater.updateState(getBookOrders()); @@ -81,17 +68,14 @@ const makeContract = zcf => { // If there's an existing offer that this offer is a match for, make the trade // and return the handle for the matched offer. If not, return undefined, so // the caller can know to add the new offer to the book. - function swapIfCanTrade(offerHandles, offerHandle) { - for (const iHandle of offerHandles) { - const satisfiedBy = (xHandle, yHandle) => - satisfies(xHandle, zcf.getCurrentAllocation(yHandle)); - if ( - satisfiedBy(iHandle, offerHandle) && - satisfiedBy(offerHandle, iHandle) - ) { - swap(offerHandle, iHandle); + function swapIfCanTrade(offers, offerSeat) { + for (const offer of offers) { + const satisfiedBy = (xSeat, ySeat) => + satisfies(zcf, xSeat, ySeat.getCurrentAllocation()); + if (satisfiedBy(offer, offerSeat) && satisfiedBy(offerSeat, offer)) { + swap(zcf, offerSeat, offer); // return handle to remove - return iHandle; + return offer; } } return undefined; @@ -101,20 +85,21 @@ const makeContract = zcf => { // the matching offer and return the remaining counterOffers. If there's no // matching offer, add the offerHandle to the coOffers, and return the // unmodified counterOfffers - function swapIfCanTradeAndUpdateBook(counterOffers, coOffers, offerHandle) { - const handle = swapIfCanTrade(counterOffers, offerHandle); - if (handle) { + function swapIfCanTradeAndUpdateBook(counterOffers, coOffers, offerSeat) { + const offer = swapIfCanTrade(counterOffers, offerSeat); + if (offer) { // remove the matched offer. - counterOffers = counterOffers.filter(value => value !== handle); + counterOffers = counterOffers.filter(value => value !== offer); } else { // Save the order in the book - coOffers.push(offerHandle); + coOffers.push(offerSeat); } return counterOffers; } - const exchangeOfferHook = offerHandle => { + /** @type {OfferHandler} */ + const exchangeOfferHandler = offerSeat => { const buyAssetForPrice = harden({ give: { Price: null }, want: { Asset: null }, @@ -123,40 +108,43 @@ const makeContract = zcf => { give: { Asset: null }, want: { Price: null }, }); - if (checkIfProposal(offerHandle, sellAssetForPrice)) { - buyOfferHandles = swapIfCanTradeAndUpdateBook( - buyOfferHandles, - sellOfferHandles, - offerHandle, + if (checkIfProposal(offerSeat, sellAssetForPrice)) { + buyOfferSeats = swapIfCanTradeAndUpdateBook( + buyOfferSeats, + sellOfferSeats, + offerSeat, ); /* eslint-disable no-else-return */ - } else if (checkIfProposal(offerHandle, buyAssetForPrice)) { - sellOfferHandles = swapIfCanTradeAndUpdateBook( - sellOfferHandles, - buyOfferHandles, - offerHandle, + } else if (checkIfProposal(offerSeat, buyAssetForPrice)) { + sellOfferSeats = swapIfCanTradeAndUpdateBook( + sellOfferSeats, + buyOfferSeats, + offerSeat, ); } else { // Eject because the offer must be invalid - return rejectOffer(offerHandle); + return offerSeat.reject(); } bookOrdersChanged(); - return defaultAcceptanceMsg; + return 'Trade Successful'; }; const makeExchangeInvite = () => - zcf.makeInvitation(exchangeOfferHook, 'exchange'); + zcf.makeInvitation(exchangeOfferHandler, 'exchange'); + + const publicFacet = harden({ + makeInvite: makeExchangeInvite, + getNotifier: () => notifier, + }); - zcf.initPublicAPI( - harden({ - makeInvite: makeExchangeInvite, - getOffer, - getNotifier: () => notifier, - }), - ); + const creatorFacet = harden({ + getPublicFacet: () => publicFacet, + }); - return makeExchangeInvite(); + // set the initial state of the notifier + bookOrdersChanged(); + return { creatorInvitation: makeExchangeInvite(), creatorFacet, publicFacet }; }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index f9986faedf8..71b901fa524 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -19,11 +19,8 @@ */ /** - * @typedef {Object} ZcfForZoe - * The facet ZCF presents to Zoe. - * - * @property {(offerHandle: OfferHandle, proposal: ProposalRecord, allocation: Allocation) => (CompleteObj | undefined)} addOffer - * Add a single offer to this contract instance. + * @template T + * @typedef {(record: any) => record is T} Validator */ /** @@ -36,56 +33,122 @@ */ /** - * @template OC - the offer outcome - * @typedef {Object} InviteHandler - * @property {OfferHook} invoke + * @template T + * @typedef {Object} Table + * @property {(record: any) => record is T} validate + * @property {(record: Omit, handle: H = harden({})) => H} create + * @property {(handle: any) => T & {handle: {}}} get + * @property {(handle: any) => boolean} has + * @property {(handle: any) => void} delete + * @property {(handle: H, partialRecord: Partial) => H} update */ /** - * @typedef StartContractParams - * @property {ZoeService} zoeService - The canonical Zoe service in case the contract wants it - * @property {SourceBundle} bundle an object containing source code and moduleFormat - * @property {InstanceRecord&InternalInstanceRecord} instanceData, fields for the instanceRecord - * @property {ZoeForZcf} zoeForZcf - An inner facet of Zoe for the contractFacet's use - * @property {Issuer} inviteIssuer, Zoe's inviteIssuer, for the contract to use - * - * @callback StartContract - * Makes a contract instance from an installation and returns a - * unique handle for the instance that can be shared, as well as - * other information, such as the terms used in the instance. - * @param {StartContractParams} params - * @returns {Promise<{ inviteP: Promise, zcfForZoe: ZcfForZoe }>} + * @typedef {Object} ZoeSeat + * @property {() => void} exit - exit seat + * @property {(replacementAllocation: Allocation) => void} replaceAllocation - replace the + * currentAllocation with this allocation */ /** - * @template T - * @typedef {(record: any) => record is T} Validator + * @typedef {Object} ZCFSeatAdmin + * @property {(seatStaging: SeatStaging) => void} commit */ /** - * @template T - * @typedef {Object} Table - * @property {(record: any) => record is T} validate - * @property {(record: Omit, handle: H = harden({})) => H} create - * @property {(handle: any) => T & {handle: {}}} get - * @property {(handle: any) => boolean} has - * @property {(handle: any) => void} delete - * @property {(handle: H, partialRecord: Partial) => H} update + * @typedef {Object} SeatData + * @property {ProposalRecord} proposal + * @property {Notifier} notifier + * @property {Allocation} initialAllocation + */ + +/** + * Make the ZCF seat and seat admin + * @callback MakeSeatAdmin + * @param {Set} allSeatStagings - a set of valid + * seatStagings where allocations have been checked for offerSafety + * @param {ZoeSeat} zoeSeat - a presence from Zoe such that ZCF can tell Zoe + * about seat events + * @param {SeatData} seatData - pass-by-copy data to use to make the seat + * @param {(brand: Brand) => AmountMath} getAmountMath + * @returns {{seatAdmin: ZCFSeatAdmin, seat: ZCFSeat}} */ /** - * @typedef {Object} PrivateInstanceRecord - * @property {Promise} zcfForZoe - the inner facet for Zoe to use - * @property {Set} offerHandles - the offer handles for this instance + * @typedef {Object} AddSeatResult + * @property {Promise} offerResultP + * @property {Object} exitObj + */ + +/** + * @typedef {Object} InstanceAdmin + * @property {(invitationHandle: InvitationHandle, seatAdmin: + * ZoeSeat, seatData: SeatData) => Promise} addSeatAdmin + * @property {(seatAdmin: ZoeSeat) => void} removeSeatAdmin + * @property {() => Instance} getInstance + * @property {() => PublicFacet} getPublicFacet + * @property {() => IssuerKeywordRecord} getIssuers + * @property {() => BrandKeywordRecord} getBrands + * @property {() => Object} getTerms + */ + +/** + * @typedef {Object} AddSeatObj + * @property {(invitationHandle, zoeSeat, seatData) => AddSeatResult} addSeat + */ + +/** + * @typedef {Object} ZoeInstanceAdmin + * @property {(invitationHandle: InvitationHandle, description: + * string, customProperties?: {}) => Payment<'ZoeInvitation'>} + * makeInvitation + * @property {() => void} shutdown + * @property {(issuerP: Issuer|Promise, keyword: Keyword) => void} saveIssuer + */ + +/** + * @typedef {Object} InstanceData + */ + +/** + * @typedef {Object} ZCFRoot + * @property {ExecuteContract} executeContract + * + * @typedef {Object} ExecuteContractResult + * @property {Object} creatorFacet + * @property {Promise} creatorInvitation + * @property {Object} publicFacet + * @property {AddSeatObj} addSeatObj * - * @typedef {Object} ZcfInstanceRecord - * @property {VatAdmin} adminNode * - * @typedef {Object} PrivateIssuerRecord - * @property {Purse} purse + * @callback ExecuteContract + * @param {SourceBundle} bundle + * @param {ZoeService} zoeService + * @param {Issuer<'ZoeInvitation'>} invitationIssuer + * @param {ZoeInstanceAdmin} zoeInstanceAdmin + * @param {InstanceRecord} instanceRecord + * @returns {ExecuteContractResult} * - * @typedef {Object} PrivateOfferRecord - * @property {Allocation} currentAllocation - the allocation corresponding to this offer - * @property {Notifier|undefined} notifier - the notifier for allocation changes - * @property {Updater} updater - the notifier for allocation changes + */ + +/** + * @callback MakeMakeInstanceFn + * @param {VatAdminSvc} vatAdminSvc, + * @param {GetPromiseForIssuerRecord} getPromiseForIssuerRecord, + * @param {IssuerKit<'ZoeInvitation'>} invitationKit, + * @param {HasInstallation} hasInstallation, + * @param {ZoeService} zoeService, + * @param {AddInstance} addInstance, + * @returns MakeInstance + */ + +/** + * @callback MakeExitObj + * @param {ProposalRecord} proposal + * @param {ZoeSeat} zoeSeat + */ + +/** + * @typedef {Object} ExitObj + * @property {() => void} exit */ diff --git a/packages/zoe/src/issuerTable.js b/packages/zoe/src/issuerTable.js new file mode 100644 index 00000000000..09854739ab9 --- /dev/null +++ b/packages/zoe/src/issuerTable.js @@ -0,0 +1,117 @@ +// @ts-check + +import { E } from '@agoric/eventual-send'; + +import makeWeakStore from '@agoric/weak-store'; +import makeAmountMath from '@agoric/ertp/src/amountMath'; +import { makeTable, makeValidateProperties } from './table'; + +import '../exported'; +import './internal-types'; +import { assert } from '@agoric/assert'; + +/** + * @template K,V + * @typedef {import('@agoric/weak-store').WeakStore} WeakStore + */ + +// Issuer Table key: brand +// Columns: issuer | amountMath | brand +// +// The IssuerTable is keyed by brand, but the Issuer is required in order for +// getPromiseForIssuerRecord() to initialize the records. When +// getPromiseForIssuerRecord is called and the record doesn't exist, it stores a +// promise for the record in issuersInProgress, and then builds the record. It +// updates the tables when done. +// +const makeIssuerTable = () => { + /** + * TODO: make sure this validate function protects against malicious + * misshapen objects rather than just a general check. + * @type {Validator} + */ + const validateSomewhat = makeValidateProperties( + harden(['brand', 'issuer', 'amountMath']), + ); + + const makeCustomMethods = table => { + /** @type {WeakStore,any>} */ + const issuersInProgress = makeWeakStore('issuer'); + + /** @type {WeakStore,Brand>} */ + const issuerToBrand = makeWeakStore('issuer'); + + // We can't be sure we can build the table entry soon enough that the first + // caller will get the actual data, so we start by saving a promise in the + // inProgress table, and once we have the Issuer, build the record, fill in + // the table, and resolve the promise. + /** + * @param {Issuer} issuer + */ + function buildTableEntryAndPlaceHolder(issuer) { + // remote calls which immediately return a promise + const mathHelpersNameP = E(issuer).getMathHelpersName(); + const brandP = E(issuer).getBrand(); + const brandIssuerMatchP = E(brandP).isMyIssuer(issuer); + + /** + * @type {[ + * PromiseLike>, + * PromiseLike<'nat' | 'set' | 'strSet'>, + * PromiseLike + * ]} + * */ + const promiseArray = [brandP, mathHelpersNameP, brandIssuerMatchP]; + // a promise for a synchronously accessible record + const synchronousRecordP = Promise.all(promiseArray).then( + ([brand, mathHelpersName, brandIssuerMatch]) => { + assert( + brandIssuerMatch, + `issuer was using a brand which was not its own`, + ); + const amountMath = makeAmountMath(brand, mathHelpersName); + const issuerRecord = { + brand, + issuer, + amountMath, + }; + table.create(issuerRecord, brand); + issuerToBrand.init(issuer, brand); + issuersInProgress.delete(issuer); + return table.get(brand); + }, + ); + issuersInProgress.init(issuer, synchronousRecordP); + return synchronousRecordP; + } + + const customMethods = harden({ + // `issuerP` may be a promise, presence, or local object. If there's + // already a record, or already a promise for a record, return it. + // Otherwise wrap a promise around building the record so we can return + // the promise until we build the record. + getPromiseForIssuerRecord: issuerP => { + return Promise.resolve(issuerP).then(issuer => { + if (issuerToBrand.has(issuer)) { + // we always initialize table and issuerToBrand together + return table.get(issuerToBrand.get(issuer)); + // eslint-disable-next-line no-else-return + } else if (issuersInProgress.has(issuer)) { + // a promise which resolves to the issuer record + return issuersInProgress.get(issuer); + // eslint-disable-next-line no-else-return + } else { + return buildTableEntryAndPlaceHolder(issuer); + } + }); + }, + // Synchronous, but throws if not present. + getIssuerRecordByIssuer: issuer => table.get(issuerToBrand.get(issuer)), + }); + return customMethods; + }; + + return makeTable(validateSomewhat, 'brand', makeCustomMethods); +}; + +export { makeIssuerTable }; diff --git a/packages/zoe/src/state.js b/packages/zoe/src/state.js deleted file mode 100644 index 7df8689b48c..00000000000 --- a/packages/zoe/src/state.js +++ /dev/null @@ -1,285 +0,0 @@ -// @ts-check - -import { E } from '@agoric/eventual-send'; - -import makeWeakStore from '@agoric/weak-store'; -import makeAmountMath from '@agoric/ertp/src/amountMath'; -import { makeTable, makeValidateProperties } from './table'; - -import '../exported'; -import './internal-types'; - -export { makeHandle } from './table'; - -// Installation Table key: installationHandle -// Columns: bundle -/** - * - */ -const makeInstallationTable = () => { - /** - * @type {Validator} - */ - const validateSomewhat = makeValidateProperties(['bundle']); - return makeTable(validateSomewhat, 'installationHandle'); -}; - -// Instance Table key: instanceHandle -// Columns: installationHandle | publicAPI | terms | issuerKeywordRecord -// | brandKeywordRecord | zcfForZoe | offerHandles -// Zoe uses this table to track contract instances. The issuerKeywordRecord and -// brandKeyword are slightly redundant, but are each convenient at this point. -// zcfForZoe provides a direct channel to the ZCF running in a vat. zcfForZoe -// is initially set to a promise, since it can't be created until a reply is -// received from the newly created vat. Zoe needs to know the offerHandles in -// order to fulfill its responsibility for exit safety. -const makeInstanceTable = () => { - /** - * TODO: make sure this validate function protects against malicious - * misshapen objects rather than just a general check. - * - * @type {Validator} - */ - const validateSomewhat = makeValidateProperties([ - 'installationHandle', - 'publicAPI', - 'terms', - 'issuerKeywordRecord', - 'brandKeywordRecord', - 'zcfForZoe', - 'offerHandles', - ]); - - const makeCustomMethods = table => { - const customMethods = harden({ - addOffer: (instanceHandle, newOfferHandle) => { - const { offerHandles } = table.get(instanceHandle); - offerHandles.add(newOfferHandle); - return instanceHandle; - }, - removeCompletedOffers: (instanceHandle, handlesToDrop) => { - const { offerHandles } = table.get(instanceHandle); - for (const h of handlesToDrop) { - offerHandles.delete(h); - } - return instanceHandle; - }, - }); - return customMethods; - }; - - return makeTable(validateSomewhat, 'instanceHandle', makeCustomMethods); -}; - -// Offer Table key: offerHandle -// Columns: instanceHandle | proposal | currentAllocation | notifier | updater -// The two versions of this table are slightly different. Zoe's -// currentAllocation is kept up-to-date with ZCF and will be used for closing. -// Zcf effectively has a local cache of the allocations. Zoe's allocations are -// definitive (in comparison to the ones in zcf), so Zoe's notifier is -// authoritative. Zcf doesn't store a notifier, but does store a no-action -// updater, so updateAmounts can work polymorphically. -const makeOfferTable = () => { - /** - * TODO: make sure this validate function protects against malicious - * misshapen objects rather than just a general check. - */ - const validateProperties = makeValidateProperties([ - 'instanceHandle', - 'proposal', - 'currentAllocation', - 'notifier', - 'updater', - ]); - - /** - * @type {Validator} - */ - const validateSomewhat = obj => { - validateProperties(obj); - // TODO: Should check the rest of the representation of the proposal - // TODO: Should check that the deadline representation is itself valid. - return true; - }; - - /** - * @param {Table} table - */ - const makeCustomMethods = table => { - const customMethods = harden({ - /** @param {OfferHandle[]} offerHandles */ - getOffers: offerHandles => offerHandles.map(table.get), - /** @param {OfferHandle[]} offerHandles */ - getOfferStatuses: offerHandles => { - const active = []; - const inactive = []; - for (const offerHandle of offerHandles) { - if (table.has(offerHandle)) { - active.push(offerHandle); - } else { - inactive.push(offerHandle); - } - } - return harden({ - active, - inactive, - }); - }, - /** @param {OfferHandle} offerHandle */ - isOfferActive: offerHandle => table.has(offerHandle), - /** @param {OfferHandle[]} offerHandles */ - deleteOffers: offerHandles => { - return offerHandles.map(offerHandle => { - const { updater } = table.get(offerHandle); - updater.finish(undefined); - return table.delete(offerHandle); - }); - }, - /** - * @param {OfferHandle[]} offerHandles - * @param {Allocation[]} newAllocations - * @returns {OfferHandle[]} - */ - updateAmounts: (offerHandles, newAllocations) => - offerHandles.map((offerHandle, i) => { - // newAllocation can replace the old allocation entirely - const newAllocation = newAllocations[i]; - const { updater } = table.get(offerHandle); - updater.updateState(newAllocation); - return table.update(offerHandle, { - currentAllocation: newAllocation, - }); - }), - }); - return customMethods; - }; - - return makeTable(validateSomewhat, 'offerHandle', makeCustomMethods); -}; - -// Payout Map key: offerHandle -// Columns: payout -/** - * Create payoutMap - * @returns {WeakStore>} Store - */ -const makePayoutMap = () => makeWeakStore('offerHandle'); - -// Issuer Table key: brand -// Columns: issuer | purse | amountMath -// -// The IssuerTable is keyed by brand, but the Issuer is required in order for -// getPromiseForIssuerRecord() to initialize the records. When -// getPromiseForIssuerRecord is called and the record doesn't exist, it stores a -// promise for the record in issuersInProgress, and then builds the record. It -// updates the tables when done. -// -// Zoe main has an issuerTable that stores the purses with deposited assets. -// Zoe contract facet has everything but the purses. -const makeIssuerTable = (withPurses = true) => { - /** - * TODO: make sure this validate function protects against malicious - * misshapen objects rather than just a general check. - * @type {Validator} - */ - const validateSomewhat = makeValidateProperties( - withPurses - ? ['brand', 'issuer', 'purse', 'amountMath'] - : ['brand', 'issuer', 'amountMath'], - ); - - const makeCustomMethods = table => { - /** @type {WeakStore} */ - const issuersInProgress = makeWeakStore('issuer'); - - /** @type {WeakStore} */ - const issuerToBrand = makeWeakStore('issuer'); - - // We can't be sure we can build the table entry soon enough that the first - // caller will get the actual data, so we start by saving a promise in the - // inProgress table, and once we have the Issuer, build the record, fill in - // the table, and resolve the promise. - /** - * @param {Issuer} issuer - */ - function buildTableEntryAndPlaceHolder(issuer) { - // remote calls which immediately return a promise - const mathHelpersNameP = E(issuer).getMathHelpersName(); - const brandP = E(issuer).getBrand(); - - /** - * a promise for a synchronously accessible record - * @type {[PromiseLike, PromiseLike, PromiseLike | undefined]} - */ - const promiseRecord = [brandP, mathHelpersNameP, undefined]; - if (withPurses) { - promiseRecord[2] = E(issuer).makeEmptyPurse(); - } - const synchronousRecordP = Promise.all(promiseRecord).then( - ([brand, mathHelpersName, purse]) => { - const amountMath = makeAmountMath(brand, mathHelpersName); - const issuerRecord = { - brand, - issuer, - amountMath, - }; - if (withPurses) { - issuerRecord.purse = purse; - } - table.create(issuerRecord, brand); - issuerToBrand.init(issuer, brand); - issuersInProgress.delete(issuer); - return table.get(brand); - }, - ); - issuersInProgress.init(issuer, synchronousRecordP); - return synchronousRecordP; - } - - const customMethods = harden({ - // `issuerP` may be a promise, presence, or local object. If there's - // already a record, or already a promise for a record, return it. - // Otherwise wrap a promise around building the record so we can return - // the promise until we build the record. - getPromiseForIssuerRecord: issuerP => { - return Promise.resolve(issuerP).then(issuer => { - if (issuerToBrand.has(issuer)) { - // we always initialize table and issuerToBrand together - return table.get(issuerToBrand.get(issuer)); - // eslint-disable-next-line no-else-return - } else if (issuersInProgress.has(issuer)) { - // a promise which resolves to the issuer record - return issuersInProgress.get(issuer); - // eslint-disable-next-line no-else-return - } else { - return buildTableEntryAndPlaceHolder(issuer); - } - }); - }, - /** @type {(issuerPs: (ERef)[]) => Promise} */ - getPromiseForIssuerRecords: issuerPs => - Promise.all(issuerPs.map(customMethods.getPromiseForIssuerRecord)), - brandFromIssuer: issuer => issuerToBrand.get(issuer), - }); - return customMethods; - }; - - return makeTable(validateSomewhat, 'brand', makeCustomMethods); -}; - -const makeZoeTables = () => - harden({ - installationTable: makeInstallationTable(), - instanceTable: makeInstanceTable(), - offerTable: makeOfferTable(), - payoutMap: makePayoutMap(), - issuerTable: makeIssuerTable(), - }); - -const makeContractTables = () => - harden({ - offerTable: makeOfferTable(), - issuerTable: makeIssuerTable(false), - }); - -export { makeZoeTables, makeContractTables }; diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 3eb265a86d9..18ea2d5773a 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -15,10 +15,7 @@ /** * @typedef {string} Keyword - * @typedef {Handle<'InstallationHandle'>} InstallationHandle - an opaque handle for an bundle installation - * @typedef {Handle<'InstanceHandle'>} InstanceHandle - an opaque handle for a contract instance - * @typedef {Handle<'OfferHandle'>} OfferHandle - an opaque handle for an offer - * @typedef {Handle<'InviteHandle'>} InviteHandle - an opaque handle for an invite + * @typedef {Handle<'InvitationHandle'>} InvitationHandle - an opaque handle for an invite * @typedef {Record} IssuerKeywordRecord * @typedef {Record} BrandKeywordRecord * @typedef {Record} PaymentKeywordRecord @@ -28,103 +25,114 @@ /** * @typedef {Object} ZoeService - * Zoe provides a framework for deploying and working with smart contracts. It - * is accessed as a long-lived and well-trusted service that enforces offer - * safety for the contracts that use it. Zoe has a single `inviteIssuer` for - * the entirety of its lifetime. By having a reference to Zoe, a user can get - * the `inviteIssuer` and thus validate any `invite` they receive from someone - * else. * - * Zoe has two different facets: the public Zoe service and the contract facet - * (ZCF). Each contract instance has a copy of ZCF within its vat. The contract - * and ZCF never have direct access to the users' payments or the Zoe purses. - * The contract can only do a few things through ZCF. It can propose a - * reallocation of amount or complete an offer. It can also speak directly to Zoe - * outside of its vat, and create a new offer for record-keeping and other - * purposes. - * - * @property {() => Issuer} getInviteIssuer - * Zoe has a single `inviteIssuer` for the entirety of its lifetime. - * By having a reference to Zoe, a user can get the `inviteIssuer` - * and thus validate any `invite` they receive from someone else. The - * mint associated with the inviteIssuer creates the ERTP payments - * that represent the right to interact with a smart contract in - * particular ways. + * Zoe provides a framework for deploying and working with smart + * contracts. It is accessed as a long-lived and well-trusted service + * that enforces offer safety for the contracts that use it. Zoe has a + * single `invitationIssuer` for the entirety of its lifetime. By + * having a reference to Zoe, a user can get the `invitationIssuer` + * and thus validate any `invitation` they receive from someone else. + * + * Zoe has two different facets: the public Zoe service and the + * contract facet (ZCF). Each contract instance has a copy of ZCF + * within its vat. The contract and ZCF never have direct access to + * the users' payments or the Zoe purses. + * + * @property {() => Issuer<'ZoeInvitation'>} getInvitationIssuer + * + * Zoe has a single `invitationIssuer` for the entirety of its + * lifetime. By having a reference to Zoe, a user can get the + * `invitationIssuer` and thus validate any `invitation` they receive + * from someone else. The mint associated with the invitationIssuer + * creates the ERTP payments that represent the right to interact with + * a smart contract in particular ways. + * + * @property {Install} install + * @property {MakeInstance} makeInstance + * @property {Offer} offer + * @property {(Instance) => Object} getPublicFacet + * @property {(Instance) => IssuerKeywordRecord} getIssuers + * @property {(Instance) => BrandKeywordRecord} getBrands + * @property {(Instance) => Object} getTerms + */ + +/** + * @typedef {Object} Installation + * @property {() => SourceBundle} getBundle + */ + +/** + * @callback Install + * @param {SourceBundle} bundle + * @returns {Promise} * - * @property {(bundle: SourceBundle) => Promise} install * Create an installation by safely evaluating the code and - * registering it with Zoe. Returns an installationHandle. - * - * @property {(installationHandle: InstallationHandle, - * issuerKeywordRecord: IssuerKeywordRecord, - * terms?: object) - * => Promise>} makeInstance - * Zoe is long-lived. We can use Zoe to create smart contract - * instances by specifying a particular contract installation to - * use, as well as the `issuerKeywordRecord` and `terms` of the contract. The - * `issuerKeywordRecord` is a record mapping string names (keywords) to issuers, - * such as `{ Asset: simoleanIssuer}`. (Note that the keywords must - * begin with a capital letter and must be ASCII identifiers.) Parties to the - * contract will use the keywords to index their proposal and - * their payments. - * - * Terms are the arguments to the contract, - * such as the number of bids an auction will wait for before closing. - * Terms are up to the discretion of the smart contract. We get back - * an invite (an ERTP payment) to participate in the contract. - * - * @property {(instanceHandle: InstanceHandle) => InstanceRecord} getInstanceRecord - * Credibly get information about the instance (such as the installation - * and terms used). - * - * @property {(invite: Invite|PromiseLike>, - * proposal?: Proposal, - * paymentKeywordRecord?: PaymentKeywordRecord) - * => Promise>} offer - * To redeem an invite, the user normally provides a proposal (their rules for the - * offer) as well as payments to be escrowed by Zoe. If either the proposal or payments - * would be empty, indicate this by omitting that argument or passing undefined, rather - * than passing an empty record. - * - * The proposal has three parts: `want` and `give` are used - * by Zoe to enforce offer safety, and `exit` is used to specify - * the particular payout-liveness policy that Zoe can guarantee. - * `want` and `give` are objects with keywords as keys and amounts - * as values. `paymentKeywordRecord` is a record with keywords as keys, - * and the values are the actual payments to be escrowed. A payment - * is expected for every rule under `give`. - * - * @property {(offerHandle: OfferHandle) => boolean} isOfferActive - * @property {(offerHandles: OfferHandle[]) => OfferRecord[]} getOffers - * @property {(offerHandle: OfferHandle) => OfferRecord} getOffer - * @property {(offerHandle: OfferHandle) => Notifier} getOfferNotifier - * Get a notifier (see `@agoric/notify`) for the offer's reallocations. - * @property {(offerHandle: OfferHandle, brandKeywordRecord?: BrandKeywordRecords) => Allocation} getCurrentAllocation - * @property {(offerHandles: OfferHandle[], brandKeywordRecord[]?: BrandKeywordRecords) => Allocation[]} getCurrentAllocations - * @property {(installationHandle: InstallationHandle) => SourceBundle} getInstallation - * Get the source code for the installed contract. Throws an error if the - * installationHandle is not found. - * - * @typedef {Object} CompleteObj - * @property {() => void} complete attempt to exit the contract and return a refund + * registering it with Zoe. Returns an installation. */ /** - * @template OC - the offer outcome - * @typedef {Object} OfferResultRecord This is returned by a call to `offer` on Zoe. - * @property {Promise} offerHandle - * @property {Promise} payout A promise that resolves - * to a record which has keywords as keys and promises for payments - * as values. Note that while the payout promise resolves when an offer - * is completed, the promise for each payment resolves after the remote - * issuer successfully withdraws the payment. - * - * @property {Promise} outcome Note that if the offerHook throws, - * this outcome Promise will reject, but the rest of the OfferResultRecord is - * still meaningful. - * - * @property {CompleteObj} [completeObj] - * completeObj will only be present if exitKind was 'onDemand' + * @typedef {Object} UserSeat + * @property {() => Promise} getCurrentAllocation + * @property {() => Promise} getProposal + * @property {() => Promise} getPayouts + * @property {(keyword: Keyword) => Promise} getPayout + * @property {() => Promise} getOfferResult + * @property {() => void=} exit + */ + +/** + * @callback Offer + * @param {Invitation|Promise} invitation + * @param {Proposal=} proposal + * @param {PaymentKeywordRecord=} paymentKeywordRecord + * @returns {Promise} seat + * + * To redeem an invite, the user normally provides a proposal (their + * rules for the offer) as well as payments to be escrowed by Zoe. If + * either the proposal or payments would be empty, indicate this by + * omitting that argument or passing undefined, rather than passing an + * empty record. + * + * The proposal has three parts: `want` and `give` are used by Zoe to + * enforce offer safety, and `exit` is used to specify the particular + * payout-liveness policy that Zoe can guarantee. `want` and `give` + * are objects with keywords as keys and amounts as values. + * `paymentKeywordRecord` is a record with keywords as keys, and the + * values are the actual payments to be escrowed. A payment is + * expected for every rule under `give`. + */ + +/** + * @typedef {Object} CreatorFacetWInstance + * @property {() => Instance} getInstance + */ + +/** + * @typedef {Object} MakeInstanceResult + * @property {CreatorFacetWInstance & Record} creatorFacet + * @property {Payment<'ZoeInvitation'>} creatorInvitation + */ + +/** + * @callback MakeInstance + * @param {Installation} installation + * @param {IssuerKeywordRecord=} issuerKeywordRecord + * @param {Object=} terms + * @returns {MakeInstanceResult} + + * Zoe is long-lived. We can use Zoe to create smart contract + * instances by specifying a particular contract installation to use, + * as well as the `issuerKeywordRecord` and `terms` of the contract. + * The `issuerKeywordRecord` is a record mapping string names + * (keywords) to issuers, such as `{ Asset: simoleanIssuer}`. (Note + * that the keywords must begin with a capital letter and must be + * ASCII identifiers.) Parties to the contract will use the keywords + * to index their proposal and their payments. + * + * Terms are the arguments to the contract, such as the number of bids + * an auction will wait for before closing. Terms are up to the + * discretion of the smart contract. We get back the admin facet as + * defined by the contract. */ /** @@ -135,15 +143,6 @@ * * The keys are keywords, and the values are amounts. For example: * { Asset: amountMath.make(5), Price: amountMath.make(9) } - * - * @typedef {AmountKeywordRecord[]} AmountKeywordRecords - */ - -/** - * @template OC - the offer outcome - * @typedef {Object} MakeInstanceResult - * @property {Invite} invite - * @property {InstanceRecord} instanceRecord */ /** @@ -169,21 +168,6 @@ * `{ afterDeadline: { timer :Timer, deadline :Deadline } } */ -/** - * @template OC - the offer outcome - * @typedef {Payment & { _inviteOutcome: OC }} Invite - * An invitation to participate in a Zoe contract. - * Invites are Payments, so they can be transferred, stored in Purses, and - * verified. Only Zoe can create new Invites. - */ - -/** - * @template OC - the offer outcome - * @callback MakeContract The type exported from a Zoe contract - * @param {ContractFacet} zcf The Zoe Contract Facet - * @returns {Invite} invite The closely-held administrative invite - */ - /** * @typedef {Object} ContractFacet * The Zoe interface specific to a contract instance. @@ -191,104 +175,71 @@ * access the Zoe state for that instance. The Zoe Contract Facet is accessed * synchronously from within the contract, and usually is referred to in code as * zcf. - * @property {Reallocate} reallocate Propose a reallocation of extents per offer - * @property {Complete} complete Complete an offer + * @property {Reallocate} reallocate - reallocate amounts among seats + * @property {SaveIssuer} saveIssuer - save an issuer to ZCF and Zoe + * and get the amountMath and brand synchronously accessible after saving * @property {MakeInvitation} makeInvitation - * @property {AddNewIssuer} addNewIssuer - * @property {InitPublicAPI} initPublicAPI + * @property {Shutdown} shutdown * @property {() => ZoeService} getZoeService - * @property {() => Issuer} getInviteIssuer - * @property {(offerHandles: OfferHandle[]) => OfferStatus} getOfferStatuses - * @property {(offerHandle: OfferHandle) => boolean} isOfferActive - * @property {(offerHandles: OfferHandle[]) => OfferRecord[]} getOffers - * @property {(offerHandle: OfferHandle) => OfferRecord} getOffer - * @property {(offerHandle: OfferHandle, brandKeywordRecord?: BrandKeywordRecord) => Allocation} getCurrentAllocation - * @property {(offerHandles: OfferHandle[], brandKeywordRecords?: BrandKeywordRecord[]) => Allocation[]} getCurrentAllocations - * @property {(offerHandle: OfferHandle) => Promise>} getOfferNotifier - * @property {() => InstanceRecord} getInstanceRecord + * @property {() => Issuer<'ZoeInvitation'>} getInvitationIssuer + * @property {() => InstanceRecord } getInstanceRecord * @property {(issuer: Issuer) => Brand} getBrandForIssuer - * @property {(brand: Brand) => Issuer} getIssuerForBrand - * @property {(brand: Brand) => AmountMath} getAmountMath - * @property {() => VatAdmin} getVatAdmin - * + * @property {GetAmountMath} getAmountMath + */ + +/** * @callback Reallocate - * The contract can propose a reallocation of extents across offers - * by providing two parallel arrays: offerHandles and newAllocations. - * Each element of newAllocations is an AmountKeywordRecord whose - * amount should replace the old amount for that keyword for the - * corresponding offer. + * + * The contract can reallocate over seatStagings, which are + * associations of seats with reallocations. * * The reallocation will only succeed if the reallocation 1) conserves * rights (the amounts specified have the same total value as the - * current total amount), and 2) is 'offer-safe' for all parties involved. + * current total amount), and 2) is 'offer-safe' for all parties + * involved. Offer safety is checked at the staging step. * * The reallocation is partial, meaning that it applies only to the - * amount associated with the offerHandles that are passed in. By - * induction, if rights conservation and offer safety hold before, - * they will hold after a safe reallocation, even though we only - * re-validate for the offers whose allocations will change. Since - * rights are conserved for the change, overall rights will be unchanged, - * and a reallocation can only effect offer safety for offers whose - * allocations change. - * - * zcf.reallocate will throw an error if any of the - * newAllocations do not have a value for all the - * keywords in sparseKeywords. An error will also be thrown if - * any newAllocations have keywords that are not in - * sparseKeywords. - * - * @param {OfferHandle[]} offerHandles An array of offerHandles - * @param {AmountKeywordRecord[]} newAllocations An - * array of amountKeywordRecords - objects with keyword keys - * and amount values, with one keywordRecord per offerHandle. - * @returns {void} - * - * @callback Complete - * The contract can "complete" an offer to remove it from the - * ongoing contract and resolve the player's payouts (either - * winnings or refunds). Because Zoe only allows for - * reallocations that conserve rights and are 'offer-safe', we - * don't need to do those checks at this step and can assume - * that the invariants hold. - * @param {OfferHandle[]} offerHandles - an array of offerHandles + * seats associated with the seatStagings. By induction, if rights + * conservation and offer safety hold before, they will hold after a + * safe reallocation, even though we only re-validate for the seats + * whose allocations will change. Since rights are conserved for the + * change, overall rights will be unchanged, and a reallocation can + * only effect offer safety for seats whose allocations change. + * + * @param {SeatStaging} seatStaging + * @param {SeatStaging} seatStaging + * @param {SeatStaging=} seatStaging + * @param {SeatStaging=} seatStaging + * @param {SeatStaging=} seatStaging * @returns {void} */ /** - * @template OC - the offer outcome * @callback MakeInvitation - * Make a credible Zoe invite for a particular smart contract - * indicated by the unique `instanceHandle`. The other - * information in the extent of this invite is decided by the - * governing contract and should include whatever information is - * necessary for a potential buyer of the invite to know what - * they are getting. Note: if information can be derived in - * queries based on other information, we choose to omit it. For - * instance, `installationHandle` can be derived from - * `instanceHandle` and is omitted even though it is useful. - * @param {OfferHook} offerHook - a function that will be handed the - * offerHandle at the right time, and returns a contract-specific - * OfferOutcome which will be put in the OfferResultRecord. - * @param {string} inviteDesc - * @param {MakeInvitationOptions} [options] - * @returns {Promise>} - */ - -/** - * @typedef MakeInvitationOptions - * @property {CustomProperties} [customProperties] - an object of - * information to include in the extent, as defined by the smart - * contract + * + * Make a credible Zoe invitation for a particular smart contract + * indicated by the `instance` in the extent of the invitation. Zoe + * also puts the `installation` and a unique `handle` in the extent of + * the invitation. The contract must provide a `description` for the + * invitation and should include whatever information is + * necessary for a potential buyer of the invitation to know what they are + * getting in the `customProperties`. `customProperties` will be + * placed in the extent of the invitation. + * + * @param {OfferHandler} offerHandler - a contract specific function + * that handles the offer, such as saving it or performing a trade + * @param {string} description + * @param {Object=} customProperties + * @returns {Promise} */ /** - * @template OC - the outcome type - * @callback OfferHook - * This function will be called with the OfferHandle when the offer - * is prepared. It should return a contract-specific "OfferOutcome" - * value that will be put in the OfferResultRecord. - * @param {OfferHandle} offerHandle - * @returns {OC} + * @callback SaveIssuer + * Informs Zoe about an issuer and returns a promise for acknowledging + * when the issuer is added and ready. + * @param {Promise|Issuer} issuerP Promise for issuer + * @param {Keyword} keyword Keyword for added issuer + * @returns {Promise} Issuer is added and ready */ /** @@ -310,35 +261,37 @@ */ /** - * @typedef {Object} VatAdmin + * @typedef RootAndAdminNode + * @property {Object} root + * @property {AdminNode} adminNode + */ + +/** + * @typedef {Object} VatAdminSvc + * @property {(bundle: SourceBundle) => RootAndAdminNode} createVat + */ + +/** + * @typedef {Object} AdminNode * A powerful object that can be used to terminate the vat in which a contract * is running, to get statistics, or to be notified when it terminates. The * VatAdmin object is only available to the contract from within the contract so * that clients of the contract can tell (by getting the source code from Zoe - * using the instanceHandle) what use the contract makes of it. If they want to + * using the installation) what use the contract makes of it. If they want to * be assured of discretion, or want to know that the contract doesn't have the * ability to call terminate(), Zoe makes this visible. * - * @property {() => Object} done - * provides a promise that will be fullfilled when the contract is terminated. - * @property {() => undefined} terminate + * @property {() => Promise} done + * provides a promise that will be fulfilled when the contract is terminated. + * @property {() => void} terminate * kills the vat in which the contract is running * @property {() => Object} adminData * provides some statistics about the vat in which the contract is running. */ /** - * @typedef {Object} CustomProperties - * - * @typedef {object} OfferRecord - * @property {OfferHandle} handle - opaque identifier for the offer - * @property {InstanceHandle} instanceHandle - opaque identifier for the instance - * @property {ProposalRecord} proposal - the offer proposal (including want, give, exit) * * @typedef {object} InstanceRecord - * @property {InstanceHandle} handle - opaque identifier for the instance - * @property {InstallationHandle} installationHandle - opaque identifier for the installation - * @property {Promise} publicAPI - the invite-free publicly accessible API for the contract * @property {Object} terms - contract parameters * @property {IssuerKeywordRecord} issuerKeywordRecord - record with keywords keys, issuer values * @property {BrandKeywordRecord} brandKeywordRecord - record with @@ -349,17 +302,82 @@ * @property {Issuer} issuer * @property {AmountMath} amountMath * - * @typedef {Object} InstallationRecord - * @property {InstallationHandle} handle - opaque identifier for the installation - * @property {SourceBundle} bundle - contract code - * - * @typedef {Object} OfferStatus - * @property {OfferHandle[]} active - * @property {OfferHandle[]} inactive - * - * @typedef {(offerHandle: OfferHandle) => void} OfferHook + * @typedef {Record} Allocation + * @typedef {Record} AmountMathKeywordRecord + */ + +/** + * @typedef {Object} ZCFSeat + * @property {() => void} exit + * @property {(msg: string=) => never} kickOut + * @property {() => Notifier} getNotifier + * @property {() => boolean} hasExited + * @property {() => ProposalRecord} getProposal + * @property {(keyword: Keyword, brand: Brand) => Amount} getAmountAllocated + * The brand is used for filling in an empty amount if the `keyword` + * is not present in the allocation + * @property {() => Allocation} getCurrentAllocation + * @property {(newAllocation: Allocation) => Boolean} isOfferSafe + * @property {(newAllocation: Allocation) => SeatStaging} stage + */ + +/** + * @typedef {Object} SeatStaging + * @property {() => ZCFSeat} getSeat + * @property {() => Allocation} getStagedAllocation + */ + +/** + * @callback OfferHandler + * @param {ZCFSeat} seat + * @returns any + */ + +/** + * @typedef {Object} ContractStartFnResult + * @property {Object=} creatorFacet + * @property {Promise=} creatorInvitation + * @property {Object=} publicFacet + */ + +/** + * @callback ContractStartFn + * @param {ContractFacet} zcf + * @param {Object} terms + * @returns {ContractStartFnResult} + */ + +/** + * @typedef {Payment<'ZoeInvitation'>} Invitation + */ + +/** + * @typedef {{}} Instance + */ + +/** + * @template T + * @callback GetAmountMath + * @param {Brand} brand + * @returns {AmountMath} + */ + +/** + * @callback Trade + * Trade between left and right so that left and right end up with + * the declared gains. + * @param {ContractFacet} zcf + * @param {SeatGainsLossesRecord} keepLeft + * @param {SeatGainsLossesRecord} tryRight + * @returns {void} * - * @typedef {Keyword[]} SparseKeywords - * @typedef {{[Keyword:string]:Amount}} Allocation - * @typedef {{[Keyword:string]:AmountMath}} AmountMathKeywordRecord + * @typedef {Object} SeatGainsLossesRecord + * @property {ZCFSeat} seat + * @property {AmountKeywordRecord} gains - what the offer will + * gain as a result of this trade + * @property {AmountKeywordRecord=} losses - what the offer will + * give up as a result of this trade. Losses is optional, but can + * only be omitted if the keywords for both offers are the same. + * If losses is not defined, the gains of the other offer is + * subtracted. */ diff --git a/packages/zoe/src/zoe.js b/packages/zoe/src/zoe.js deleted file mode 100644 index 5c5b23d8631..00000000000 --- a/packages/zoe/src/zoe.js +++ /dev/null @@ -1,465 +0,0 @@ -// @ts-check - -import { E } from '@agoric/eventual-send'; -import makeWeakStore from '@agoric/weak-store'; -import makeIssuerKit from '@agoric/ertp'; -import { assert, details } from '@agoric/assert'; -import { makeNotifierKit } from '@agoric/notifier'; -import { makePromiseKit } from '@agoric/promise-kit'; - -/** - * Zoe uses ERTP, the Electronic Rights Transfer Protocol - */ -import '../exported'; -import './internal-types'; - -import { cleanProposal, cleanKeywords } from './cleanProposal'; -import { arrayToObj, filterFillAmounts, filterObj } from './objArrayConversion'; -import { makeZoeTables, makeHandle } from './state'; - -// This is the Zoe contract facet from contractFacet.js, packaged as a bundle -// that can be used to create a new vat. Every time it is edited, it must be -// manually rebuilt with `yarn build-zcfBundle`. This happens automatically -// on `yarn build` or `yarn test`. - -// Do the dance necessary to allow a non-existing bundle to pass both lint and ts. -/* eslint-disable import/no-unresolved, import/extensions */ -// @ts-ignore -import zcfContractBundle from '../bundles/bundle-contractFacet'; -/* eslint-enable import/no-unresolved, import/extensions */ - -/** - * Create an instance of Zoe. - * - * @param {Object} vatAdminSvc - The vatAdmin Service, which carries the power - * to create a new vat. - * @returns {ZoeService} The created Zoe service. - */ -function makeZoe(vatAdminSvc) { - /** - * A weakMap from the inviteHandles to contract offerHook upcalls - * @type {WeakStore>} - */ - const inviteHandleToHandler = makeWeakStore('inviteHandle'); - - const { - mint: inviteMint, - issuer: inviteIssuer, - amountMath: inviteAmountMath, - } = makeIssuerKit('zoeInvite', 'set'); - - // All of the Zoe state is stored in these tables built on WeakMaps - const { - installationTable, - instanceTable, - offerTable, - payoutMap, - issuerTable, - } = makeZoeTables(); - - const getAmountMathForBrand = brand => issuerTable.get(brand).amountMath; - - /** - * @param {InstanceHandle} instanceHandle - * @param {OfferHandle[]} offerHandles - */ - const completeOffers = (instanceHandle, offerHandles) => { - const { inactive } = offerTable.getOfferStatuses(offerHandles); - if (inactive.length > 0) { - throw new Error(`offer has already completed`); - } - const offerRecords = offerTable.getOffers(offerHandles); - - // Remove the offers from the offerTable so that they are no longer active. - offerTable.deleteOffers(offerHandles); - - // Resolve the payout promises with promises for the payouts - for (const offerRecord of offerRecords) { - /** @type {PaymentPKeywordRecord} */ - const payout = {}; - Object.keys(offerRecord.currentAllocation).forEach(keyword => { - const payoutAmount = offerRecord.currentAllocation[keyword]; - const { purse } = issuerTable.get(payoutAmount.brand); - payout[keyword] = E(purse).withdraw(payoutAmount); - }); - harden(payout); - payoutMap.get(offerRecord.handle).resolve(payout); - } - - // Remove the offers from the instanceTable now that they've been completed. - instanceTable.removeCompletedOffers(instanceHandle, offerHandles); - }; - - const filterOfferRecord = offerRecord => - filterObj(offerRecord, ['handle', 'instanceHandle', 'proposal']); - - const doGetCurrentAllocation = (offerHandle, brandKeywordRecord) => { - const { currentAllocation } = offerTable.get(offerHandle); - if (brandKeywordRecord === undefined) { - return currentAllocation; - } - /** @type {AmountMathKeywordRecord} */ - const amountMathKeywordRecord = {}; - Object.getOwnPropertyNames(brandKeywordRecord).forEach(keyword => { - const brand = brandKeywordRecord[keyword]; - amountMathKeywordRecord[keyword] = issuerTable.get(brand).amountMath; - }); - return filterFillAmounts(currentAllocation, amountMathKeywordRecord); - }; - - const doGetCurrentAllocations = (offerHandles, brandKeywordRecords) => { - if (brandKeywordRecords === undefined) { - return offerHandles.map(offerHandle => - doGetCurrentAllocation(offerHandle), - ); - } - return offerHandles.map((offerHandle, i) => - doGetCurrentAllocation(offerHandle, brandKeywordRecords[i]), - ); - }; - - /** - * Make a Zoe invite payment with an value that is a mix of credible - * information from Zoe (the `handle` and `instanceHandle`) and - * other information defined by the smart contract (the mandatory - * `inviteDesc` and the optional `options.customProperties`). - * Note that the smart contract cannot override or change the values - * of `handle` and `instanceHandle`. - * - * @template OC - the offer outcome - * @param {InstanceHandle} instanceHandle - * @param {InviteHandler} inviteHandler - * @param {string} inviteDesc - * @param {Object} options - * @returns {Invite} - */ - const makeInvitation = ( - instanceHandle, - inviteHandler, - inviteDesc, - options = harden({}), - ) => { - assert.typeof( - inviteDesc, - 'string', - details`expected an inviteDesc string: ${inviteDesc}`, - ); - - const { customProperties = harden({}) } = options; - const inviteHandle = makeHandle('InviteHandle'); - const { installationHandle } = instanceTable.get(instanceHandle); - const inviteAmount = inviteAmountMath.make( - harden([ - { - ...customProperties, - inviteDesc, - handle: inviteHandle, - instanceHandle, - installationHandle, - }, - ]), - ); - const handler = offerHandle => E(inviteHandler).invoke(offerHandle); - inviteHandleToHandler.init(inviteHandle, handler); - const invite = inviteMint.mintPayment(inviteAmount); - return /** @type {Invite} */ (invite); - }; - - /** - * drop zcfForZoe, offerHandles, adminNode. - * @type {(record: any) => InstanceRecord} - */ - const filterInstanceRecord = record => - filterObj(record, [ - 'handle', - 'installationHandle', - 'publicAPI', - 'terms', - 'issuerKeywordRecord', - 'brandKeywordRecord', - ]); - - /** - * @param {InstanceHandle} instanceHandle - * @param {PromiseRecord} publicApiP - * @returns {ZoeForZcf} - */ - const makeZoeForZcf = (instanceHandle, publicApiP) => { - return harden({ - makeInvitation: (inviteHandler, inviteDesc, options = undefined) => - makeInvitation(instanceHandle, inviteHandler, inviteDesc, options), - updateAmounts: (offerHandles, reallocations) => - offerTable.updateAmounts(offerHandles, reallocations), - updatePublicAPI: publicAPI => publicApiP.resolve(publicAPI), - addNewIssuer: (issuerP, keyword) => - issuerTable.getPromiseForIssuerRecord(issuerP).then(issuerRecord => { - const { issuerKeywordRecord, brandKeywordRecord } = instanceTable.get( - instanceHandle, - ); - const newIssuerKeywordRecord = { - ...issuerKeywordRecord, - [keyword]: issuerRecord.issuer, - }; - const newBrandKeywordRecord = { - ...brandKeywordRecord, - [keyword]: issuerRecord.brand, - }; - instanceTable.update(instanceHandle, { - issuerKeywordRecord: newIssuerKeywordRecord, - brandKeywordRecord: newBrandKeywordRecord, - }); - }), - completeOffers: offerHandles => - completeOffers(instanceHandle, offerHandles), - }); - }; - - // The public Zoe service has four main methods: `install` takes - // contract code and registers it with Zoe associated with an - // `installationHandle` for identification, `makeInstance` creates - // an instance from an installation, `getInstanceRecord` credibly - // retrieves an instance from Zoe, and `offer` allows users to - // securely escrow and get in return a record containing a promise for - // payouts, a promise for the outcome of joining the contract, - // and, depending on the exit conditions, perhaps a completeObj, - // an object with a complete method for leaving the contract on demand. - - /** @type {ZoeService} */ - const zoeService = { - getInviteIssuer: () => inviteIssuer, - - install: bundle => installationTable.create(harden({ bundle })), - - makeInstance: ( - installationHandle, - issuerKeywordRecord = harden({}), - terms = harden({}), - ) => { - assert( - installationTable.has(installationHandle), - details`${installationHandle} was not a valid installationHandle`, - ); - const publicApiP = makePromiseKit(); - return E(vatAdminSvc) - .createVat(zcfContractBundle) - .then(({ root, adminNode }) => { - /** @type {{ startContract: StartContract }} */ - const zcfRoot = root; - const instanceHandle = makeHandle('InstanceHandle'); - const zoeForZcf = makeZoeForZcf(instanceHandle, publicApiP); - - const cleanedKeywords = cleanKeywords(issuerKeywordRecord); - const issuersP = cleanedKeywords.map( - keyword => issuerKeywordRecord[keyword], - ); - const makeCleanup = _marker => { - return () => { - // console.log(`ZOE makeInstance enter CLEANUP: ${marker} `); - const { offerHandles } = instanceTable.get(instanceHandle); - // This cleanup can't rely on ZCF to complete the offers since - // it's invoked when ZCF is no longer accessible. - completeOffers(instanceHandle, Array.from(offerHandles)); - }; - }; - - // Build an entry for the instanceTable. It will contain zcfForZoe - // which isn't available until ZCF starts. When ZCF starts up, it - // will invoke the contract, which might make calls back to the Zoe - // facet we provide, so InstanceRecord needs to be present by then. - // We'll store an initial version of InstanceRecord before invoking - // ZCF and fill in the zcfForZoe when we get it. - const zcfForZoePromise = makePromiseKit(); - /** @type {Omit} */ - const instanceRecord = { - installationHandle, - publicAPI: publicApiP.promise, - terms, - issuerKeywordRecord: {}, - brandKeywordRecord: {}, - zcfForZoe: zcfForZoePromise.promise, - offerHandles: new Set(), - }; - const addIssuersToInstanceRecord = issuerRecords => { - const issuers = issuerRecords.map(record => record.issuer); - const cleanedIssuerKeywordRecord = arrayToObj( - issuers, - cleanedKeywords, - ); - instanceRecord.issuerKeywordRecord = cleanedIssuerKeywordRecord; - const brands = issuerRecords.map(record => record.brand); - const brandKeywordRecord = arrayToObj(brands, cleanedKeywords); - instanceRecord.brandKeywordRecord = brandKeywordRecord; - instanceTable.create(instanceRecord, instanceHandle); - E(adminNode) - .done() - .then(makeCleanup('done success'), makeCleanup('done reject')); - }; - - const callStartContract = () => { - /** @type {InstanceRecord} */ - const instanceData = harden({ - handle: instanceHandle, - installationHandle, - publicAPI: instanceRecord.publicAPI, - terms, - adminNode, - issuerKeywordRecord: instanceRecord.issuerKeywordRecord, - brandKeywordRecord: instanceRecord.brandKeywordRecord, - }); - const contractParams = harden({ - zoeService, - bundle: installationTable.get(installationHandle).bundle, - instanceData, - zoeForZcf, - inviteIssuer, - }); - return E(zcfRoot).startContract(contractParams); - }; - - const finishContractInstall = ({ inviteP, zcfForZoe }) => { - zcfForZoePromise.resolve(zcfForZoe); - return inviteIssuer.isLive(inviteP).then(success => { - assert( - success, - details`invites must be issued by the inviteIssuer.`, - ); - - function buildRecord(invite) { - return { - invite, - instanceRecord: filterInstanceRecord( - instanceTable.get(instanceHandle), - ), - }; - } - - return inviteP.then(buildRecord, makeCleanup('invite failure')); - }); - }; - - // The issuers may not have been seen before, so we must wait for the - // issuer records to be available synchronously - return issuerTable - .getPromiseForIssuerRecords(issuersP) - .then(addIssuersToInstanceRecord) - .then(callStartContract) - .then(finishContractInstall); - }); - }, - - getInstanceRecord: instanceHandle => - filterInstanceRecord(instanceTable.get(instanceHandle)), - - getOfferNotifier: offerHandle => { - const { notifier } = offerTable.get(offerHandle); - assert(notifier, `notifier is not set within Zoe`); - return notifier; - }, - - offer: ( - invite, - proposal = harden({}), - paymentKeywordRecord = harden({}), - ) => { - return inviteIssuer.burn(invite).then(inviteAmount => { - assert( - inviteAmount.value.length === 1, - 'only one invite should be redeemed', - ); - const giveKeywords = proposal.give - ? Object.getOwnPropertyNames(proposal.give) - : []; - const wantKeywords = proposal.want - ? Object.getOwnPropertyNames(proposal.want) - : []; - const userKeywords = harden([...giveKeywords, ...wantKeywords]); - - const cleanedProposal = cleanProposal(getAmountMathForBrand, proposal); - - const paymentDepositedPs = userKeywords.map(keyword => { - if (giveKeywords.includes(keyword)) { - // We cannot trust the amount in the proposal, so we use our - // cleaned proposal's amount that should be the same. - const giveAmount = cleanedProposal.give[keyword]; - const { purse } = issuerTable.get(giveAmount.brand); - return E(purse).deposit(paymentKeywordRecord[keyword], giveAmount); - // eslint-disable-next-line no-else-return - } else { - // payments outside the give: clause are ignored. - return getAmountMathForBrand( - cleanedProposal.want[keyword].brand, - ).getEmpty(); - } - }); - - const { - value: [{ instanceHandle, handle: inviteHandle }], - } = inviteAmount; - const offerHandle = makeHandle('OfferHandle'); - - // recordOffer() creates and stores a record in the offerTable. The - // allocations are according to the keywords in the offer's proposal, - // which are not required to match anything in the issuerKeywordRecord - // that was used to instantiate the contract. recordOffer() is called - // on amountsArray, which includes amounts for all the keywords in the - // proposal. Keywords in the give clause are mapped to the amount - // deposited. Keywords in the want clause are mapped to the empty - // amount for that keyword's Issuer. - const recordOffer = amountsArray => { - /** @type {NotifierRecord} */ - const notifierKit = makeNotifierKit(undefined); - /** @type {Omit} */ - const offerRecord = { - instanceHandle, - proposal: cleanedProposal, - currentAllocation: arrayToObj(amountsArray, userKeywords), - notifier: notifierKit.notifier, - updater: notifierKit.updater, - }; - const { zcfForZoe } = instanceTable.get(instanceHandle); - payoutMap.init(offerHandle, makePromiseKit()); - offerTable.create(offerRecord, offerHandle); - instanceTable.addOffer(instanceHandle, offerHandle); - return E(zcfForZoe).addOffer( - offerHandle, - cleanedProposal, - offerRecord.currentAllocation, - ); - }; - - const makeOfferResult = completeObj => { - const offerHandler = inviteHandleToHandler.get(inviteHandle); - const offerOutcome = offerHandler(offerHandle).catch(err => { - completeOffers(instanceHandle, [offerHandle]); - throw err; - }); - const offerResult = { - offerHandle: E.when(offerHandle), - payout: payoutMap.get(offerHandle).promise, - outcome: offerOutcome, - completeObj, - }; - return harden(offerResult); - }; - return Promise.all(paymentDepositedPs) - .then(recordOffer) - .then(makeOfferResult); - }); - }, - - isOfferActive: offerTable.isOfferActive, - getOffers: offerHandles => - offerTable.getOffers(offerHandles).map(filterOfferRecord), - getOffer: offerHandle => filterOfferRecord(offerTable.get(offerHandle)), - getCurrentAllocation: (offerHandle, brandKeywordRecord) => - doGetCurrentAllocation(offerHandle, brandKeywordRecord), - getCurrentAllocations: (offerHandles, brandKeywordRecords) => - doGetCurrentAllocations(offerHandles, brandKeywordRecords), - getInstallation: installationHandle => - installationTable.get(installationHandle).bundle, - }; - - return harden(zoeService); -} - -export { makeZoe }; diff --git a/packages/zoe/src/cleanProposal.js b/packages/zoe/src/zoeService/cleanProposal.js similarity index 97% rename from packages/zoe/src/cleanProposal.js rename to packages/zoe/src/zoeService/cleanProposal.js index 56312bea72f..0fd05c96731 100644 --- a/packages/zoe/src/cleanProposal.js +++ b/packages/zoe/src/zoeService/cleanProposal.js @@ -1,7 +1,10 @@ import { assert, details, q } from '@agoric/assert'; import { mustBeComparable } from '@agoric/same-structure'; -import { arrayToObj, assertSubset } from './objArrayConversion'; +import '../../exported'; +import '../internal-types'; + +import { arrayToObj, assertSubset } from '../objArrayConversion'; // We adopt simple requirements on keywords so that they do not accidentally // conflict with existing property names. diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js new file mode 100644 index 00000000000..e805969fa9b --- /dev/null +++ b/packages/zoe/src/zoeService/zoe.js @@ -0,0 +1,314 @@ +// @ts-check +import makeIssuerKit from '@agoric/ertp'; +import makeWeakStore from '@agoric/weak-store'; +import { assert, details } from '@agoric/assert'; +import { E } from '@agoric/eventual-send'; +import { makeNotifierKit } from '@agoric/notifier'; +import { makePromiseKit } from '@agoric/promise-kit'; + +/** + * Zoe uses ERTP, the Electronic Rights Transfer Protocol + */ +import '@agoric/ertp/exported'; + +import '../../exported'; +import '../internal-types'; + +import { makeIssuerTable } from '../issuerTable'; +import zcfContractBundle from '../../bundles/bundle-contractFacet'; +import { arrayToObj } from '../objArrayConversion'; +import { cleanKeywords, cleanProposal } from './cleanProposal'; + +/** + * Create an instance of Zoe. + * + * @param {VatAdminSvc} vatAdminSvc - The vatAdmin Service, which carries the power + * to create a new vat. + * @returns {ZoeService} The created Zoe service. + */ +function makeZoe(vatAdminSvc) { + const invitationKit = makeIssuerKit('Zoe Invitation', 'set'); + + // Zoe state shared among functions + const issuerTable = makeIssuerTable(); + /** @type {Set} */ + const installations = new Set(); + + /** @type {WeakStore} */ + const instanceToInstanceAdmin = makeWeakStore('instance'); + + /** @type {GetAmountMath} */ + const getAmountMath = brand => issuerTable.get(brand).amountMath; + + const brandToPurse = makeWeakStore('brand'); + + /** + * Create an installation by permanently storing the bundle. It will be + * evaluated each time it is used to make a new instance of a contract. + */ + /** @type {Install} */ + const install = async bundle => { + /** @type {Installation} */ + const installation = { getBundle: () => bundle }; + harden(installation); + installations.add(installation); + return installation; + }; + + /** @type {ZoeService} */ + const zoeService = { + getInvitationIssuer: () => invitationKit.issuer, + install, + getPublicFacet: instance => + instanceToInstanceAdmin.get(instance).getPublicFacet(), + getBrands: instance => instanceToInstanceAdmin.get(instance).getBrands(), + getIssuers: instance => instanceToInstanceAdmin.get(instance).getIssuers(), + getTerms: instance => instanceToInstanceAdmin.get(instance).getTerms(), + makeInstance: async ( + installation, + uncleanIssuerKeywordRecord = harden({}), + terms = harden({}), + ) => { + // Unpack the invitationKit. + + const { + issuer: invitationIssuer, + mint: invitationMint, + amountMath: invitationAmountMath, + } = invitationKit; + + const getPromiseForIssuerRecords = issuers => + Promise.all(issuers.map(issuerTable.getPromiseForIssuerRecord)); + assert( + installations.has(installation), + details`${installation} was not a valid installation`, + ); + + const seatAdmins = new Set(); + const instance = harden({}); + + const keywords = cleanKeywords(uncleanIssuerKeywordRecord); + + const issuerPs = keywords.map( + keyword => uncleanIssuerKeywordRecord[keyword], + ); + + // The issuers may not have been seen before, so we must wait for the + // issuer records to be available synchronously + const issuerRecords = await getPromiseForIssuerRecords(issuerPs); + issuerRecords.forEach(record => { + if (!brandToPurse.has(record.brand)) { + brandToPurse.init(record.brand, E(record.issuer).makeEmptyPurse()); + } + }); + + const instanceRecord = { + issuerKeywordRecord: arrayToObj( + issuerRecords.map(record => record.issuer), + keywords, + ), + brandKeywordRecord: arrayToObj( + issuerRecords.map(record => record.brand), + keywords, + ), + installation, + terms, + }; + + const createVatResult = await E(vatAdminSvc).createVat(zcfContractBundle); + const { adminNode, root } = createVatResult; + /** @type {ZCFRoot} */ + const zcfRoot = root; + + const exitAllSeats = () => + seatAdmins.forEach(seatAdmin => seatAdmin.exit()); + + E(adminNode) + .done() + .then( + () => exitAllSeats(), + () => exitAllSeats(), + ); + + /** @type {ZoeInstanceAdmin} */ + const zoeInstanceAdminForZcf = { + makeInvitation: (invitationHandle, description, customProperties) => { + const invitationAmount = invitationAmountMath.make( + harden([ + { + ...customProperties, + description, + handle: invitationHandle, + instance, + installation, + }, + ]), + ); + return invitationMint.mintPayment(invitationAmount); + }, + // checks of keyword done on zcf side + saveIssuer: (issuerP, keyword) => + issuerTable + .getPromiseForIssuerRecord(issuerP) + .then(({ issuer, brand }) => { + instanceRecord.issuerKeywordRecord = { + ...instanceRecord.issuerKeywordRecord, + [keyword]: issuer, + }; + instanceRecord.brandKeywordRecord = { + ...instanceRecord.brandKeywordRecord, + [keyword]: brand, + }; + if (!brandToPurse.has(brand)) { + brandToPurse.init(brand, E(issuer).makeEmptyPurse()); + } + }), + shutdown: () => { + exitAllSeats(); + adminNode.terminate(); + }, + }; + + const bundle = installation.getBundle(); + + const { + creatorFacet = {}, + publicFacet = {}, + creatorInvitation, + addSeatObj, + } = await E(zcfRoot).executeContract( + bundle, + zoeService, + invitationIssuer, + zoeInstanceAdminForZcf, + harden({ ...instanceRecord }), + ); + + const creatorFacetWInstance = { + ...creatorFacet, + getInstance: () => instance, + }; + + /** @type {InstanceAdmin} */ + const instanceAdmin = { + addSeatAdmin: async (invitationHandle, seatAdmin, seatData) => { + seatAdmins.add(seatAdmin); + return E(addSeatObj).addSeat(invitationHandle, seatAdmin, seatData); + }, + removeSeatAdmin: seatAdmin => seatAdmins.delete(seatAdmin), + getPublicFacet: () => publicFacet, + getTerms: () => instanceRecord.terms, + getIssuers: () => instanceRecord.issuerKeywordRecord, + getBrands: () => instanceRecord.brandKeywordRecord, + getInstance: () => instance, + }; + + instanceToInstanceAdmin.init(instance, instanceAdmin); + + // Actually returned to the user. + return { creatorFacet: creatorFacetWInstance, creatorInvitation }; + }, + offer: async ( + invitation, + uncleanProposal = harden({}), + paymentKeywordRecord = harden({}), + ) => { + return invitationKit.issuer.burn(invitation).then(invitationAmount => { + assert( + invitationAmount.value.length === 1, + 'Only one invitation can be redeemed at a time', + ); + + const proposal = cleanProposal(getAmountMath, uncleanProposal); + const { give, want } = proposal; + const giveKeywords = Object.keys(give); + const wantKeywords = Object.keys(want); + const proposalKeywords = harden([...giveKeywords, ...wantKeywords]); + + const paymentDepositedPs = proposalKeywords.map(keyword => { + if (giveKeywords.includes(keyword)) { + // We cannot trust the amount in the proposal, so we use our + // cleaned proposal's amount that should be the same. + const giveAmount = proposal.give[keyword]; + const purse = brandToPurse.get(giveAmount.brand); + return E(purse).deposit(paymentKeywordRecord[keyword], giveAmount); + // eslint-disable-next-line no-else-return + } else { + // payments outside the give: clause are ignored. + return getAmountMath(proposal.want[keyword].brand).getEmpty(); + } + }); + const { + value: [{ instance, handle: invitationHandle }], + } = invitationAmount; + + return Promise.all(paymentDepositedPs).then(amountsArray => { + const initialAllocation = arrayToObj(amountsArray, proposalKeywords); + + const payoutPromiseKit = makePromiseKit(); + const offerResultPromiseKit = makePromiseKit(); + const exitObjPromiseKit = makePromiseKit(); + const { notifier, updater } = makeNotifierKit(); + let currentAllocation = initialAllocation; + + const instanceAdmin = instanceToInstanceAdmin.get(instance); + + /** @type {ZoeSeat} */ + const seatAdmin = { + replaceAllocation: replacementAllocation => { + harden(replacementAllocation); + // Merging happens in ZCF, so replacementAllocation can + // replace the old allocation entirely. + updater.updateState(replacementAllocation); + currentAllocation = replacementAllocation; + }, + exit: () => { + updater.finish(undefined); + instanceAdmin.removeSeatAdmin(seatAdmin); + + /** @type {PaymentPKeywordRecord} */ + const payout = {}; + Object.entries(currentAllocation).forEach( + ([keyword, payoutAmount]) => { + const purse = brandToPurse.get(payoutAmount.brand); + payout[keyword] = E(purse).withdraw(payoutAmount); + }, + ); + harden(payout); + payoutPromiseKit.resolve(payout); + }, + }; + harden(seatAdmin); + + /** @type {UserSeat} */ + const userSeat = { + getCurrentAllocation: async () => currentAllocation, + getProposal: async () => proposal, + getPayouts: async () => payoutPromiseKit.promise, + getPayout: async keyword => + payoutPromiseKit.promise.then(payouts => payouts[keyword]), + getOfferResult: async () => offerResultPromiseKit.promise, + exit: async () => + exitObjPromiseKit.promise.then(exitObj => E(exitObj).exit()), + }; + + const seatData = harden({ proposal, initialAllocation, notifier }); + + instanceAdmin + .addSeatAdmin(invitationHandle, seatAdmin, seatData) + .then(({ offerResultP, exitObj }) => { + offerResultPromiseKit.resolve(offerResultP); + exitObjPromiseKit.resolve(exitObj); + }); + + return userSeat; + }); + }); + }, + }; + harden(zoeService); + + return zoeService; +} + +export { makeZoe }; diff --git a/packages/zoe/test/swingsetTests/zoe-metering/vat-zoe.js b/packages/zoe/test/swingsetTests/zoe-metering/vat-zoe.js index 32e9be38cbd..f80f7af8f46 100644 --- a/packages/zoe/test/swingsetTests/zoe-metering/vat-zoe.js +++ b/packages/zoe/test/swingsetTests/zoe-metering/vat-zoe.js @@ -1,5 +1,5 @@ // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; export function buildRootObject(_vatPowers) { return harden({ diff --git a/packages/zoe/test/swingsetTests/zoe/vat-bob.js b/packages/zoe/test/swingsetTests/zoe/vat-bob.js index 07939559d6d..78d9e06f3c7 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-bob.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-bob.js @@ -2,7 +2,6 @@ import { E } from '@agoric/eventual-send'; import { assert, details } from '@agoric/assert'; import { sameStructure } from '@agoric/same-structure'; import { showPurseBalance, setupIssuers } from '../helpers'; -import { makeGetInstanceHandle } from '../../../src/clientSupport'; const build = async (log, zoe, issuers, payments, installations, timer) => { const { @@ -17,12 +16,15 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { const [moolaPayment, simoleanPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; const inviteIssuer = await E(zoe).getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); return harden({ doAutomaticRefund: async inviteP => { const invite = await inviteP; const exclInvite = await E(inviteIssuer).claim(invite); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const instanceHandle = await getInstanceHandle(exclInvite); const { installationHandle, issuerKeywordRecord } = await E( diff --git a/packages/zoe/test/swingsetTests/zoe/vat-dave.js b/packages/zoe/test/swingsetTests/zoe/vat-dave.js index c5613925195..80e4ab07c52 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-dave.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-dave.js @@ -2,7 +2,6 @@ import { E } from '@agoric/eventual-send'; import { assert, details } from '@agoric/assert'; import { sameStructure } from '@agoric/same-structure'; import { showPurseBalance, setupIssuers } from '../helpers'; -import { makeGetInstanceHandle } from '../../../src/clientSupport'; const build = async (log, zoe, issuers, payments, installations, timer) => { const { @@ -17,7 +16,6 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { const [_moolaPayment, simoleanPayment, bucksPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; const inviteIssuer = await E(zoe).getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); return harden({ doPublicAuction: async inviteP => { @@ -78,6 +76,10 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { const { value: inviteValue } = await E(inviteIssuer).getAmountOf( exclInvite, ); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const instanceHandle = await getInstanceHandle(exclInvite); const { installationHandle, issuerKeywordRecord } = await E( zoe, diff --git a/packages/zoe/test/swingsetTests/zoe/vat-zoe.js b/packages/zoe/test/swingsetTests/zoe/vat-zoe.js index 32e9be38cbd..f80f7af8f46 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-zoe.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-zoe.js @@ -1,5 +1,5 @@ // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; export function buildRootObject(_vatPowers) { return harden({ diff --git a/packages/zoe/test/unitTests/contracts/escrowToVote.js b/packages/zoe/test/unitTests/contracts/escrowToVote.js index 5807fddf679..f28793d51e1 100644 --- a/packages/zoe/test/unitTests/contracts/escrowToVote.js +++ b/packages/zoe/test/unitTests/contracts/escrowToVote.js @@ -15,7 +15,7 @@ import { makeZoeHelpers } from '../../../src/contractSupport'; * issuerKeywordRecord. The instantiator gets the only Secretary * invite. * - * @typedef {import('../../../src/zoe').ContractFacet} ContractFacet + * @typedef {import('../../../src/zoeService/zoe').ContractFacet} ContractFacet * @typedef {import('@agoric/ERTP').Amount} Amount * @param {ContractFacet} zcf */ diff --git a/packages/zoe/test/unitTests/contracts/fakeVatAdmin.js b/packages/zoe/test/unitTests/contracts/fakeVatAdmin.js index cddbdfb4ab3..ae106d40701 100644 --- a/packages/zoe/test/unitTests/contracts/fakeVatAdmin.js +++ b/packages/zoe/test/unitTests/contracts/fakeVatAdmin.js @@ -1,10 +1,19 @@ import { E } from '@agoric/eventual-send'; -import { evalContractBundle } from '../../../src/evalContractCode'; +import { makePromiseKit } from '@agoric/promise-kit'; + +import { evalContractBundle } from '../../../src/contractFacet/evalContractCode'; export default harden({ createVat: bundle => { return harden({ root: E(evalContractBundle(bundle)).buildRootObject(), + adminNode: { + done: () => { + return makePromiseKit().promise; + }, + terminate: () => {}, + adminData: () => ({}), + }, }); }, }); diff --git a/packages/zoe/test/unitTests/contracts/test-atomicSwap.js b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js index 2957ad9cec7..0af8aa6dacd 100644 --- a/packages/zoe/test/unitTests/contracts/test-atomicSwap.js +++ b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js @@ -1,144 +1,171 @@ +// eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; +import { E } from '@agoric/eventual-send'; -// noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; import { setup } from '../setupBasicMints'; import { setupNonFungible } from '../setupNonFungibleMints'; -import fakeVatAdmin from './fakeVatAdmin'; const atomicSwapRoot = `${__dirname}/../../../src/contracts/atomicSwap`; test('zoe - atomicSwap', async t => { - t.plan(11); - const { - moolaIssuer, - simoleanIssuer, - moolaMint, - simoleanMint, - moola, - simoleans, - } = setup(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); - - // pack the contract - const bundle = await bundleSource(atomicSwapRoot); - // install the contract - const installationHandle = await zoe.install(bundle); - - // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - - // Setup Bob - const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); - const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - - // 1: Alice creates an atomicSwap instance - const issuerKeywordRecord = harden({ - Asset: moolaIssuer, - Price: simoleanIssuer, - }); - const { invite: aliceInvite } = await zoe.makeInstance( - installationHandle, - issuerKeywordRecord, - ); - - // 2: Alice escrows with zoe - const aliceProposal = harden({ - give: { Asset: moola(3) }, - want: { Price: simoleans(7) }, - exit: { onDemand: null }, - }); - const alicePayments = { Asset: aliceMoolaPayment }; - - // 3: Alice makes the first offer in the swap. - const { payout: alicePayoutP, outcome: bobInviteP } = await zoe.offer( - aliceInvite, - aliceProposal, - alicePayments, + t.plan(8); + const { moolaKit, simoleanKit, moola, simoleans, zoe } = setup(); + + const makeAlice = async moolaPayment => { + const moolaPurse = await E(moolaKit.issuer).makeEmptyPurse(); + const simoleanPurse = await E(simoleanKit.issuer).makeEmptyPurse(); + return { + installCode: async () => { + // pack the contract + const bundle = await bundleSource(atomicSwapRoot); + // install the contract + const installationP = E(zoe).install(bundle); + return installationP; + }, + makeInstance: async installation => { + const issuerKeywordRecord = harden({ + Asset: moolaKit.issuer, + Price: simoleanKit.issuer, + }); + const adminP = zoe.makeInstance(installation, issuerKeywordRecord); + return adminP; + }, + offer: async firstInvitation => { + const proposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(7) }, + exit: { onDemand: null }, + }); + const payments = { Asset: moolaPayment }; + + const seat = await E(zoe).offer(firstInvitation, proposal, payments); + + E(seat) + .getPayout('Asset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + moola(0), + `Alice didn't get any of what she put in`, + ), + ); + + E(seat) + .getPayout('Price') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.Price, + `Alice got exactly what she wanted`, + ), + ); + + // The result of making the first offer is an invite to swap by + // providing the other goods. + const invitationP = E(seat).getOfferResult(); + return invitationP; + }, + }; + }; + + const makeBob = (installation, simoleanPayment) => { + const moolaPurse = moolaKit.issuer.makeEmptyPurse(); + const simoleanPurse = simoleanKit.issuer.makeEmptyPurse(); + return harden({ + offer: async untrustedInvitation => { + const invitationIssuer = await E(zoe).getInvitationIssuer(); + + // Bob is able to use the trusted invitationIssuer from Zoe to + // transform an untrusted invitation that Alice also has access to, to + // an + const invitation = await invitationIssuer.claim(untrustedInvitation); + + const { + value: [invitationValue], + } = await invitationIssuer.getAmountOf(invitation); + + t.equals( + invitationValue.installation, + installation, + 'installation is atomicSwap', + ); + t.deepEquals( + invitationValue.asset, + moola(3), + `asset to be traded is 3 moola`, + ); + t.deepEquals( + invitationValue.price, + simoleans(7), + `price is 7 simoleans, so bob must give that`, + ); + + const proposal = harden({ + give: { Price: simoleans(7) }, + want: { Asset: moola(3) }, + exit: { onDemand: null }, + }); + const payments = { Price: simoleanPayment }; + + const seat = await zoe.offer(invitation, proposal, payments); + + t.equals( + await E(seat).getOfferResult(), + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + E(seat) + .getPayout('Asset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.Asset, + `Bob got what he wanted`, + ), + ); + + E(seat) + .getPayout('Price') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + simoleans(0), + `Bob didn't get anything back`, + ), + ); + }, + }); + }; + + const alice = await makeAlice(await E(moolaKit.mint).mintPayment(moola(3))); + + // Alice makes an instance and makes her offer. + const installation = await alice.installCode(); + + const bob = await makeBob( + installation, + await E(simoleanKit.mint).mintPayment(simoleans(7)), ); + const { creatorInvitation } = await alice.makeInstance(installation); + const invitation = await alice.offer(creatorInvitation); - // 4: Alice spreads the invite far and wide with instructions + // Alice spreads the invitation far and wide with instructions // on how to use it and Bob decides he wants to be the - // counter-party. - - const bobExclusiveInvite = await inviteIssuer.claim(bobInviteP); - const { - value: [bobInviteValue], - } = await inviteIssuer.getAmountOf(bobExclusiveInvite); - - const { - installationHandle: bobInstallationId, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInviteValue.instanceHandle); - - t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); - t.deepEquals(bobIssuers, { Asset: moolaIssuer, Price: simoleanIssuer }); - t.deepEquals(bobInviteValue.asset, moola(3)); - t.deepEquals(bobInviteValue.price, simoleans(7)); - - const bobProposal = harden({ - give: { Price: simoleans(7) }, - want: { Asset: moola(3) }, - exit: { onDemand: null }, - }); - const bobPayments = { Price: bobSimoleanPayment }; - - // 5: Bob makes an offer - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( - bobExclusiveInvite, - bobProposal, - bobPayments, - ); - - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; - - const bobMoolaPayout = await bobPayout.Asset; - const bobSimoleanPayout = await bobPayout.Price; - - const aliceMoolaPayout = await alicePayout.Asset; - const aliceSimoleanPayout = await alicePayout.Price; - - // Alice gets what Alice wanted - t.deepEquals( - await simoleanIssuer.getAmountOf(aliceSimoleanPayout), - aliceProposal.want.Price, - ); - - // Alice didn't get any of what Alice put in - t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); - - // Alice deposits her payout to ensure she can - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - - // Bob deposits his original payments to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); + // counter-party, without needing to trust Alice at all. - // Assert that the correct payouts were received. - // Alice had 3 moola and 0 simoleans. - // Bob had 0 moola and 7 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 7); - t.equals(bobMoolaPurse.getCurrentAmount().value, 3); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 0); + await bob.offer(invitation); }); test('zoe - non-fungible atomicSwap', async t => { - t.plan(11); + t.plan(8); const { ccIssuer, rpgIssuer, @@ -147,127 +174,163 @@ test('zoe - non-fungible atomicSwap', async t => { cryptoCats, rpgItems, createRpgItem, + zoe, } = setupNonFungible(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); - - // pack the contract - const bundle = await bundleSource(atomicSwapRoot); - // install the contract - const installationHandle = await zoe.install(bundle); + const makeAlice = async aliceCcPayment => { + const ccPurse = ccIssuer.makeEmptyPurse(); + const rpgPurse = rpgIssuer.makeEmptyPurse(); + return { + installCode: async () => { + // pack the contract + const bundle = await bundleSource(atomicSwapRoot); + // install the contract + const installationP = E(zoe).install(bundle); + return installationP; + }, + makeInstance: async installation => { + const issuerKeywordRecord = harden({ + Asset: ccIssuer, + Price: rpgIssuer, + }); + const adminP = zoe.makeInstance(installation, issuerKeywordRecord); + return adminP; + }, + offer: async (firstInvitation, calico37Amount, vorpalAmount) => { + const proposal = harden({ + give: { Asset: calico37Amount }, + want: { Price: vorpalAmount }, + exit: { onDemand: null }, + }); + const payments = { Asset: aliceCcPayment }; + + const seat = await zoe.offer(firstInvitation, proposal, payments); + + seat + .getPayout('Asset') + .then(ccPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + cryptoCats(harden([])), + `Alice didn't get any of what she put in`, + ), + ); + + seat + .getPayout('Price') + .then(rpgPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.Price, + `Alice got exactly what she wanted`, + ), + ); + + // The result of making the first offer is an invite to swap by + // providing the other goods. + const invitationP = seat.getOfferResult(); + return invitationP; + }, + }; + }; + + const makeBob = (installation, rpgPayment) => { + return harden({ + offer: async (untrustedInvitation, calico37Amount, vorpalAmount) => { + const ccPurse = ccIssuer.makeEmptyPurse(); + const rpgPurse = rpgIssuer.makeEmptyPurse(); + + const invitationIssuer = await E(zoe).getInvitationIssuer(); + + // Bob is able to use the trusted invitationIssuer from Zoe to + // transform an untrusted invitation that Alice also has access to, to + // an + const invitation = await invitationIssuer.claim(untrustedInvitation); + + const { + value: [invitationValue], + } = await invitationIssuer.getAmountOf(invitation); + + t.equals( + invitationValue.installation, + installation, + 'installation is atomicSwap', + ); + t.deepEquals( + invitationValue.asset, + calico37Amount, + `asset to be traded is a particular crypto cat`, + ); + t.deepEquals( + invitationValue.price, + vorpalAmount, + `price is vorpalAmount, so bob must give that`, + ); + + const proposal = harden({ + give: { Price: vorpalAmount }, + want: { Asset: calico37Amount }, + exit: { onDemand: null }, + }); + const payments = { Price: rpgPayment }; + + const seat = await zoe.offer(invitation, proposal, payments); + + t.equals( + await E(seat).getOfferResult(), + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + seat + .getPayout('Asset') + .then(ccPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.Asset, + `Bob got what he wanted`, + ), + ); + + seat + .getPayout('Price') + .then(rpgPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + rpgItems(harden([])), + `Bob didn't get anything back`, + ), + ); + }, + }); + }; - // Setup Alice const calico37Amount = cryptoCats(harden(['calico #37'])); - const aliceCcPayment = ccMint.mintPayment(calico37Amount); - const aliceCcPurse = ccIssuer.makeEmptyPurse(); - const aliceRpgPurse = rpgIssuer.makeEmptyPurse(); + const aliceCcPayment = await E(ccMint).mintPayment(calico37Amount); + const alice = await makeAlice(aliceCcPayment); + + // Alice makes an instance and makes her offer. + const installation = await alice.installCode(); - // Setup Bob const vorpalSword = createRpgItem('Vorpal Sword', 38); const vorpalAmount = rpgItems(vorpalSword); - const bobRpgPayment = rpgMint.mintPayment(vorpalAmount); - const bobCcPurse = ccIssuer.makeEmptyPurse(); - const bobRpgPurse = rpgIssuer.makeEmptyPurse(); - - // 1: Alice creates an atomicSwap instance - const issuerKeywordRecord = harden({ - Asset: ccIssuer, - Price: rpgIssuer, - }); - const { invite: aliceInvite } = await zoe.makeInstance( - installationHandle, - issuerKeywordRecord, - ); - - // 2: Alice escrows with zoe - const aliceProposal = harden({ - give: { Asset: calico37Amount }, - want: { Price: vorpalAmount }, - exit: { onDemand: null }, - }); - const alicePayments = { Asset: aliceCcPayment }; - - // 3: Alice makes the first offer in the swap. - const { payout: alicePayoutP, outcome: bobInviteP } = await zoe.offer( - aliceInvite, - aliceProposal, - alicePayments, + const bobRpgPayment = await E(rpgMint).mintPayment(vorpalAmount); + const bob = await makeBob(installation, bobRpgPayment); + const { creatorInvitation } = await alice.makeInstance(installation); + const invitation = await alice.offer( + creatorInvitation, + calico37Amount, + vorpalAmount, ); - // 4: Alice spreads the invite far and wide with instructions + // Alice spreads the invitation far and wide with instructions // on how to use it and Bob decides he wants to be the - // counter-party. + // counter-party, without needing to trust Alice at all. - const bobExclusiveInvite = await inviteIssuer.claim(bobInviteP); - const { - value: [bobInviteValue], - } = await inviteIssuer.getAmountOf(bobExclusiveInvite); - - const { - installationHandle: bobInstallationId, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInviteValue.instanceHandle); - - t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); - t.deepEquals(bobIssuers, { Asset: ccIssuer, Price: rpgIssuer }); - t.deepEquals(bobInviteValue.asset, calico37Amount); - t.deepEquals(bobInviteValue.price, vorpalAmount); - - const bobProposal = harden({ - give: { Price: vorpalAmount }, - want: { Asset: calico37Amount }, - exit: { onDemand: null }, - }); - const bobPayments = { Price: bobRpgPayment }; - - // 5: Bob makes an offer - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( - bobExclusiveInvite, - bobProposal, - bobPayments, - ); - - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; - - const bobCcPayout = await bobPayout.Asset; - const bobRpgPayout = await bobPayout.Price; - - const aliceCcPayout = await alicePayout.Asset; - const aliceRpgPayout = await alicePayout.Price; - - // Alice gets what Alice wanted - t.deepEquals( - await rpgIssuer.getAmountOf(aliceRpgPayout), - aliceProposal.want.Price, - ); - - // Alice didn't get any of what Alice put in - t.deepEquals( - await ccIssuer.getAmountOf(aliceCcPayout), - cryptoCats(harden([])), - ); - - // Alice deposits her payout to ensure she can - await aliceCcPurse.deposit(aliceCcPayout); - await aliceRpgPurse.deposit(aliceRpgPayout); - - // Bob deposits his original payments to ensure he can - await bobCcPurse.deposit(bobCcPayout); - await bobRpgPurse.deposit(bobRpgPayout); - - // Assert that the correct payouts were received. - // Alice had a CryptoCat and no RPG tokens. - // Bob had an empty CryptoCat purse and a Vorpal Sword. - t.deepEquals(aliceCcPurse.getCurrentAmount().value, []); - t.deepEquals(aliceRpgPurse.getCurrentAmount().value, vorpalSword); - t.deepEquals(bobCcPurse.getCurrentAmount().value, ['calico #37']); - t.deepEquals(bobRpgPurse.getCurrentAmount().value, []); + await bob.offer(invitation, calico37Amount, vorpalAmount); }); // Checking handling of duplicate issuers. I'd have preferred a raffle contract @@ -275,7 +338,7 @@ test('zoe - atomicSwap like-for-like', async t => { t.plan(13); const { moolaIssuer, moolaMint, moola } = setup(); const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const invitationIssuer = zoe.getInvitationIssuer(); // pack the contract const bundle = await bundleSource(atomicSwapRoot); @@ -319,10 +382,10 @@ test('zoe - atomicSwap like-for-like', async t => { // on how to use it and Bob decides he wants to be the // counter-party. - const bobExclusiveInvite = await inviteIssuer.claim(bobInviteP); + const bobExclusiveInvite = await invitationIssuer.claim(bobInviteP); const { value: [bobInviteValue], - } = await inviteIssuer.getAmountOf(bobExclusiveInvite); + } = await invitationIssuer.getAmountOf(bobExclusiveInvite); const { installationHandle: bobInstallationId, diff --git a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js index 540f438afff..012096dd215 100644 --- a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js +++ b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js @@ -6,9 +6,8 @@ import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; + import { setup } from '../setupBasicMints'; -import { makeGetInstanceHandle } from '../../../src/clientSupport'; import { setupNonFungible } from '../setupNonFungibleMints'; import fakeVatAdmin from './fakeVatAdmin'; @@ -18,19 +17,18 @@ test('zoe - simplest automaticRefund', async t => { t.plan(1); try { // Setup zoe and mints - const { moolaR, moola } = setup(); - const zoe = makeZoe(fakeVatAdmin); + const { moolaR, moola, zoe } = setup(); // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); // 1: Alice creates an automatic refund instance const issuerKeywordRecord = harden({ Contribution: moolaR.issuer }); - const { invite } = await zoe.makeInstance( - installationHandle, + const { creatorInvitation } = await zoe.makeInstance( + installation, issuerKeywordRecord, ); @@ -40,19 +38,19 @@ test('zoe - simplest automaticRefund', async t => { }); const alicePayments = { Contribution: aliceMoolaPayment }; - const { payout: payoutP } = await zoe.offer( - invite, + const seat = await zoe.offer( + creatorInvitation, aliceProposal, alicePayments, ); - const alicePayout = await payoutP; - const aliceMoolaPayout = await alicePayout.Contribution; + const aliceMoolaPayout = await seat.getPayout('Contribution'); // Alice got back what she put in t.deepEquals( await moolaR.issuer.getAmountOf(aliceMoolaPayout), aliceProposal.give.Contribution, + `Alice's payout matches what she put in`, ); } catch (e) { t.assert(false, e); @@ -64,8 +62,7 @@ test('zoe - automaticRefund same issuer', async t => { t.plan(1); try { // Setup zoe and mints - const { moolaR, moola } = setup(); - const zoe = makeZoe(fakeVatAdmin); + const { moolaR, moola, zoe } = setup(); // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); const installationHandle = await zoe.install(bundle); @@ -116,7 +113,6 @@ test('zoe with automaticRefund', async t => { const { moolaR, simoleanR, moola, simoleans } = setup(); const zoe = makeZoe(fakeVatAdmin); const inviteIssuer = zoe.getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); @@ -173,6 +169,10 @@ test('zoe with automaticRefund', async t => { // will check that the installationId and terms match what he // expects const exclusBobInvite = await inviteIssuer.claim(bobInvite); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const bobInstanceHandle = await getInstanceHandle(exclusBobInvite); const { @@ -280,11 +280,14 @@ test('multiple instances of automaticRefund for the same Zoe', async t => { ContributionB: simoleanR.issuer, }); const inviteIssuer = zoe.getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); const { invite: aliceInvite1 } = await zoe.makeInstance( installationHandle, issuerKeywordRecord, ); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const instanceHandle1 = await getInstanceHandle(aliceInvite1); const { publicAPI: publicAPI1 } = zoe.getInstanceRecord(instanceHandle1); diff --git a/packages/zoe/test/unitTests/contracts/test-autoswap.js b/packages/zoe/test/unitTests/contracts/test-autoswap.js index 07cc320ea77..981a03fefc4 100644 --- a/packages/zoe/test/unitTests/contracts/test-autoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-autoswap.js @@ -6,9 +6,8 @@ import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; -import { makeGetInstanceHandle } from '../../../src/clientSupport'; import fakeVatAdmin from './fakeVatAdmin'; const autoswapRoot = `${__dirname}/../../../src/contracts/autoswap`; @@ -26,7 +25,6 @@ test('autoSwap with valid offers', async t => { } = setup(); const zoe = makeZoe(fakeVatAdmin); const inviteIssuer = zoe.getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(10)); @@ -96,6 +94,10 @@ test('autoSwap with valid offers', async t => { // Bob claims it const bobExclInvite = await inviteIssuer.claim(bobInvite); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const bobInstanceHandle = await getInstanceHandle(bobExclInvite); const { publicAPI: bobAutoswap, @@ -254,7 +256,6 @@ test('autoSwap - test fee', async t => { } = setup(); const zoe = makeZoe(fakeVatAdmin); const inviteIssuer = zoe.getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(10000)); @@ -318,6 +319,10 @@ test('autoSwap - test fee', async t => { // Bob claims it const bobExclInvite = await inviteIssuer.claim(bobInvite); + const getInstanceHandle = iP => + E(inviteIssuer) + .getAmountOf(iP) + .then(amount => amount.value[0].instanceHandle); const bobInstanceHandle = await getInstanceHandle(bobExclInvite); const { publicAPI: bobAutoswap, diff --git a/packages/zoe/test/unitTests/contracts/test-barter.js b/packages/zoe/test/unitTests/contracts/test-barter.js index 341ff35637e..5cce91660d6 100644 --- a/packages/zoe/test/unitTests/contracts/test-barter.js +++ b/packages/zoe/test/unitTests/contracts/test-barter.js @@ -2,20 +2,20 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; -// eslint-disable-next-line import/no-extraneous-dependencies -import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; -// noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; import { setup } from '../setupBasicMints'; -import { makeGetInstanceHandle } from '../../../src/clientSupport'; -import fakeVatAdmin from './fakeVatAdmin'; +import { + installationPFromSource, + assertPayout, + assertOfferResult, + getInviteFields, +} from '../../zoeTestHelpers'; const barter = `${__dirname}/../../../src/contracts/barterExchange`; test('barter with valid offers', async t => { - t.plan(9); + t.plan(10); const { moolaIssuer, simoleanIssuer, @@ -24,15 +24,10 @@ test('barter with valid offers', async t => { amountMaths, moola, simoleans, + zoe, } = setup(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); - - // Pack the contract. - const bundle = await bundleSource(barter); - - const installationHandle = await zoe.install(bundle); + const inviteIssuer = zoe.getInvitationIssuer(); + const installation = await installationPFromSource(zoe, barter); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); @@ -44,16 +39,17 @@ test('barter with valid offers', async t => { const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - // 1: Simon creates a barter instance and spreads the invite far and + // 1: Simon creates a barter instance and spreads the instance far and // wide with instructions on how to use it. - const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { + const { creatorFacet } = await zoe.makeInstance(installation, { Asset: moolaIssuer, Price: simoleanIssuer, }); - const instanceHandle = await getInstanceHandle(simonInvite); - const { publicAPI } = zoe.getInstanceRecord(instanceHandle); + const publicFacet = await E(creatorFacet).getPublicFacet(); + const simonInvite = await E(publicFacet).makeInvite(); + const { instance } = await getInviteFields(simonInvite); - const aliceInvite = await E(publicAPI).makeInvite(); + const aliceInvite = await E(E(zoe).getPublicFacet(instance)).makeInvite(); // 2: Alice escrows with zoe to create a sell order. She wants to // sell 3 moola and wants to receive at least 4 simoleans in @@ -64,23 +60,26 @@ test('barter with valid offers', async t => { exit: { onDemand: null }, }); const alicePayments = { In: aliceMoolaPayment }; - // 4: Alice adds her sell order to the exchange - const { payout: alicePayoutP, outcome: aliceOutcomeP } = await zoe.offer( + // 3: Alice adds her sell order to the exchange + const aliceSeat = await zoe.offer( aliceInvite, aliceSellOrderProposal, alicePayments, ); - const bobInvite = await E(publicAPI).makeInvite(); + assertOfferResult(t, aliceSeat, 'Trade completed.'); - // 5: Bob decides to join. - const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); + const bobInvite = await E(E(zoe).getPublicFacet(instance)).makeInvite(); + const { + installation: bobInstallation, + instance: bobInstance, + } = await getInviteFields(bobInvite); - const { installationHandle: bobInstallationId } = zoe.getInstanceRecord( - instanceHandle, - ); + // 4: Bob decides to join. + const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - t.equals(bobInstallationId, installationHandle); + t.equals(bobInstallation, installation); + t.equals(bobInstance, instance); // Bob creates a buy order, saying that he wants exactly 3 moola, // and is willing to pay up to 7 simoleans. @@ -91,29 +90,24 @@ test('barter with valid offers', async t => { }); const bobPayments = { In: bobSimoleanPayment }; - // 6: Bob escrows with zoe - // 8: Bob submits the buy order to the exchange - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( + // 5: Bob escrows with zoe + const bobSeat = await zoe.offer( bobExclusiveInvite, bobBuyOrderProposal, bobPayments, ); - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - t.equals( - await aliceOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; + const { + In: bobSimoleanPayout, + Out: bobMoolaPayout, + } = await bobSeat.getPayouts(); - const bobMoolaPayout = await bobPayout.Out; - const bobSimoleanPayout = await bobPayout.In; - const aliceMoolaPayout = await alicePayout.In; - const aliceSimoleanPayout = await alicePayout.Out; + assertOfferResult(t, bobSeat, 'Trade completed.'); + + const { + In: aliceMoolaPayout, + Out: aliceSimoleanPayout, + } = await aliceSeat.getPayouts(); // Alice gets paid at least what she wanted t.ok( @@ -128,19 +122,13 @@ test('barter with valid offers', async t => { // Alice sold all of her moola t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); - // 13: Alice deposits her payout to ensure she can - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - - // 14: Bob deposits his original payments to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); - - // Assert that the correct payout were received. - // Alice had 3 moola and 0 simoleans. - // Bob had 0 moola and 7 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 4); - t.equals(bobMoolaPurse.getCurrentAmount().value, 3); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 3); + // 6: Alice deposits her payout to ensure she can + // Alice had 0 moola and 4 simoleans. + assertPayout(t, aliceMoolaPayout, aliceMoolaPurse, 0); + assertPayout(t, aliceSimoleanPayout, aliceSimoleanPurse, 4); + + // 7: Bob deposits his original payments to ensure he can + // Bob had 3 moola and 3 simoleans. + assertPayout(t, bobMoolaPayout, bobMoolaPurse, 3); + assertPayout(t, bobSimoleanPayout, bobSimoleanPurse, 3); }); diff --git a/packages/zoe/test/unitTests/contracts/test-brokenContract.js b/packages/zoe/test/unitTests/contracts/test-brokenContract.js index 8134bd95343..8642b35d8ce 100644 --- a/packages/zoe/test/unitTests/contracts/test-brokenContract.js +++ b/packages/zoe/test/unitTests/contracts/test-brokenContract.js @@ -5,7 +5,7 @@ import { test } from 'tape-promise/tape'; import bundleSource from '@agoric/bundle-source'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import fakeVatAdmin from './fakeVatAdmin'; diff --git a/packages/zoe/test/unitTests/contracts/test-coveredCall.js b/packages/zoe/test/unitTests/contracts/test-coveredCall.js index 8955311aebb..eb9823addfa 100644 --- a/packages/zoe/test/unitTests/contracts/test-coveredCall.js +++ b/packages/zoe/test/unitTests/contracts/test-coveredCall.js @@ -3,12 +3,13 @@ import '@agoric/install-ses'; import { test } from 'tape-promise/tape'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; +import { E } from '@agoric/eventual-send'; import { sameStructure } from '@agoric/same-structure'; import buildManualTimer from '../../../tools/manualTimer'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import { setupNonFungible } from '../setupNonFungibleMints'; import fakeVatAdmin from './fakeVatAdmin'; @@ -17,125 +18,171 @@ const coveredCallRoot = `${__dirname}/../../../src/contracts/coveredCall`; const atomicSwapRoot = `${__dirname}/../../../src/contracts/atomicSwap`; test('zoe - coveredCall', async t => { - t.plan(13); + t.plan(11); try { - const { moolaR, simoleanR, moola, simoleans } = setup(); - const zoe = makeZoe(fakeVatAdmin); - // Pack the contract. - const bundle = await bundleSource(coveredCallRoot); - const coveredCallInstallationHandle = await zoe.install(bundle); + const { moolaKit, simoleanKit, moola, simoleans, zoe } = setup(); + + const makeAlice = async (timer, moolaPayment) => { + const moolaPurse = await E(moolaKit.issuer).makeEmptyPurse(); + const simoleanPurse = await E(simoleanKit.issuer).makeEmptyPurse(); + return { + installCode: async () => { + // pack the contract + const bundle = await bundleSource(coveredCallRoot); + // install the contract + const installationP = E(zoe).install(bundle); + return installationP; + }, + makeInstance: async installation => { + const issuerKeywordRecord = harden({ + UnderlyingAsset: moolaKit.issuer, + StrikePrice: simoleanKit.issuer, + }); + const adminP = zoe.makeInstance(installation, issuerKeywordRecord); + return adminP; + }, + offer: async createCallOptionInvitation => { + const proposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { afterDeadline: { deadline: 1, timer } }, + }); + const payments = { UnderlyingAsset: moolaPayment }; + + const seat = await E(zoe).offer( + createCallOptionInvitation, + proposal, + payments, + ); + + E(seat) + .getPayout('UnderlyingAsset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + moola(0), + `Alice didn't get any of what she put in`, + ), + ); + + E(seat) + .getPayout('StrikePrice') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.StrikePrice, + `Alice got exactly what she wanted`, + ), + ); + + // The result of making the first offer is the call option + // digital asset. It is simultaneously actually an invitation to + // exercise the option. + const invitationP = E(seat).getOfferResult(); + return invitationP; + }, + }; + }; + + const makeBob = (timer, installation, simoleanPayment) => { + const moolaPurse = moolaKit.issuer.makeEmptyPurse(); + const simoleanPurse = simoleanKit.issuer.makeEmptyPurse(); + return harden({ + offer: async untrustedInvitation => { + const invitationIssuer = await E(zoe).getInvitationIssuer(); + + // Bob is able to use the trusted invitationIssuer from Zoe to + // transform an untrusted invitation that Alice also has access to + const invitation = await E(invitationIssuer).claim( + untrustedInvitation, + ); + + const { + value: [invitationValue], + } = await invitationIssuer.getAmountOf(invitation); + + t.equals( + invitationValue.installation, + installation, + 'installation is atomicSwap', + ); + t.equal(invitationValue.description, 'exerciseOption'); + + t.deepEquals( + invitationValue.underlyingAsset, + moola(3), + `underlying asset is 3 moola`, + ); + t.deepEquals( + invitationValue.strikePrice, + simoleans(7), + `strike price is 7 simoleans, so bob must give that`, + ); + + t.equal(invitationValue.expirationDate, 1); + t.deepEqual(invitationValue.timerAuthority, timer); + + const proposal = harden({ + give: { StrikePrice: simoleans(7) }, + want: { UnderlyingAsset: moola(3) }, + exit: { onDemand: null }, + }); + const payments = { StrikePrice: simoleanPayment }; + + const seat = await E(zoe).offer(invitation, proposal, payments); + + t.equals( + await E(seat).getOfferResult(), + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + E(seat) + .getPayout('UnderlyingAsset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.UnderlyingAsset, + `Bob got what he wanted`, + ), + ); + + E(seat) + .getPayout('StrikePrice') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + simoleans(0), + `Bob didn't get anything back`, + ), + ); + }, + }); + }; + const timer = buildManualTimer(console.log); // Setup Alice - const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); - const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); - - // Setup Bob - const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7)); - const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); - - // Alice creates a coveredCall instance - const issuerKeywordRecord = harden({ - UnderlyingAsset: moolaR.issuer, - StrikePrice: simoleanR.issuer, - }); - // separate issuerKeywordRecord from contract-specific terms - const { invite: aliceInvite } = await zoe.makeInstance( - coveredCallInstallationHandle, - issuerKeywordRecord, - ); - - // Alice escrows with Zoe - const aliceProposal = harden({ - give: { UnderlyingAsset: moola(3) }, - want: { StrikePrice: simoleans(7) }, - exit: { afterDeadline: { deadline: 1, timer } }, - }); - const alicePayments = { UnderlyingAsset: aliceMoolaPayment }; - // Alice creates a call option - const { payout: alicePayoutP, outcome: optionP } = await zoe.offer( - aliceInvite, - aliceProposal, - alicePayments, - ); + const aliceMoolaPayment = moolaKit.mint.mintPayment(moola(3)); + const alice = await makeAlice(timer, aliceMoolaPayment); - // Imagine that Alice sends the option to Bob for free (not done here - // since this test doesn't actually have separate vats/parties) + // Alice makes an instance and makes her offer. + const installation = await alice.installCode(); - // Bob inspects the option (an invite payment) and checks that it is the - // contract instance that he expects as well as that Alice has - // already escrowed. - - const inviteIssuer = zoe.getInviteIssuer(); - const bobExclOption = await inviteIssuer.claim(optionP); - const { - value: [optionValue], - } = await inviteIssuer.getAmountOf(bobExclOption); - const { installationHandle } = zoe.getInstanceRecord( - optionValue.instanceHandle, - ); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.inviteDesc, 'exerciseOption'); - t.ok(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); - t.ok(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); - t.equal(optionValue.expirationDate, 1); - t.deepEqual(optionValue.timerAuthority, timer); - - const bobPayments = { StrikePrice: bobSimoleanPayment }; - - const bobProposal = harden({ - want: { UnderlyingAsset: optionValue.underlyingAsset }, - give: { StrikePrice: optionValue.strikePrice }, - exit: { onDemand: null }, - }); - - // Bob redeems his invite and escrows with Zoe - // Bob exercises the option - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( - bobExclOption, - bobProposal, - bobPayments, - ); - - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; - - const bobMoolaPayout = await bobPayout.UnderlyingAsset; - const bobSimoleanPayout = await bobPayout.StrikePrice; - const aliceMoolaPayout = await alicePayout.UnderlyingAsset; - const aliceSimoleanPayout = await alicePayout.StrikePrice; - - // Alice gets what Alice wanted - t.deepEquals( - await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), - aliceProposal.want.StrikePrice, - ); - - // Alice didn't get any of what Alice put in - t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); - - // Alice deposits her payout to ensure she can - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); + // Setup Bob + const bobSimoleanPayment = simoleanKit.mint.mintPayment(simoleans(7)); + const bob = makeBob(timer, installation, bobSimoleanPayment); - // Bob deposits his original payments to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); + const { creatorInvitation } = await alice.makeInstance(installation); + const invitation = await alice.offer(creatorInvitation); - // Assert that the correct payouts were received. - // Alice had 3 moola and 0 simoleans. - // Bob had 0 moola and 7 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 7); - t.equals(bobMoolaPurse.getCurrentAmount().value, 3); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 0); + // Alice spreads the invitation far and wide with instructions + // on how to use it and Bob decides he wants to be the + // counter-party, without needing to trust Alice at all. + await bob.offer(invitation); } catch (e) { t.isNot(e, e, 'unexpected exception'); } diff --git a/packages/zoe/test/unitTests/contracts/test-escrowToVote.js b/packages/zoe/test/unitTests/contracts/test-escrowToVote.js index 2d49e3fae5a..a7f0564c4a0 100644 --- a/packages/zoe/test/unitTests/contracts/test-escrowToVote.js +++ b/packages/zoe/test/unitTests/contracts/test-escrowToVote.js @@ -5,7 +5,7 @@ import { test } from 'tape-promise/tape'; import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import fakeVatAdmin from './fakeVatAdmin'; diff --git a/packages/zoe/test/unitTests/contracts/test-grifter.js b/packages/zoe/test/unitTests/contracts/test-grifter.js index f8a783b90e9..cb5d80f93a1 100644 --- a/packages/zoe/test/unitTests/contracts/test-grifter.js +++ b/packages/zoe/test/unitTests/contracts/test-grifter.js @@ -5,7 +5,7 @@ import { test } from 'tape-promise/tape'; import bundleSource from '@agoric/bundle-source'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import fakeVatAdmin from './fakeVatAdmin'; diff --git a/packages/zoe/test/unitTests/contracts/test-mintPayments.js b/packages/zoe/test/unitTests/contracts/test-mintPayments.js index f74a0c646a1..6e9425f111a 100644 --- a/packages/zoe/test/unitTests/contracts/test-mintPayments.js +++ b/packages/zoe/test/unitTests/contracts/test-mintPayments.js @@ -9,7 +9,7 @@ import makeIssuerKit from '@agoric/ertp'; import fakeVatAdmin from './fakeVatAdmin'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; const mintPaymentsRoot = `${__dirname}/../../../src/contracts/mintPayments`; @@ -17,36 +17,69 @@ test('zoe - mint payments', async t => { t.plan(2); try { const zoe = makeZoe(fakeVatAdmin); - // Pack the contract. - const bundle = await bundleSource(mintPaymentsRoot); - const installationHandle = await E(zoe).install(bundle); - const inviteIssuer = await E(zoe).getInviteIssuer(); - - // Alice creates a contract instance - const { - instanceRecord: { publicAPI }, - } = await E(zoe).makeInstance(installationHandle); - - // Bob wants to get 1000 tokens so he gets an invite and makes an - // offer - const invite = await E(publicAPI).makeInvite(); - t.ok(await E(inviteIssuer).isLive(invite), `valid invite`); - const { payout: payoutP } = await E(zoe).offer(invite); - - // Bob's payout promise resolves - const bobPayout = await payoutP; - const bobTokenPayout = await bobPayout.Token; - - // Let's get the tokenIssuer from the contract so we can evaluate - // what we get as our payout - const tokenIssuer = await E(publicAPI).getTokenIssuer(); - const amountMath = await E(tokenIssuer).getAmountMath(); - - const tokens1000 = await E(amountMath).make(1000); - const tokenPayoutAmount = await E(tokenIssuer).getAmountOf(bobTokenPayout); - - // Bob got 1000 tokens - t.deepEquals(tokenPayoutAmount, tokens1000); + + const makeAlice = () => { + return { + installCode: async () => { + // pack the contract + const bundle = await bundleSource(mintPaymentsRoot); + // install the contract + const installationP = E(zoe).install(bundle); + return installationP; + }, + makeInstance: async installation => { + const adminP = zoe.makeInstance(installation); + return adminP; + }, + }; + }; + + const makeBob = installation => { + return { + offer: async untrustedInvitation => { + const invitationIssuer = E(zoe).getInvitationIssuer(); + const invitation = E(invitationIssuer).claim(untrustedInvitation); + + const { + value: [invitationValue], + } = await E(invitationIssuer).getAmountOf(invitation); + + t.equals( + invitationValue.installation, + installation, + 'installation is mintPayment', + ); + + const { instance } = invitationValue; + + const seat = await E(zoe).offer(invitation); + + const paymentP = E(seat).getPayout('Token'); + + // Let's get the tokenIssuer from the contract so we can evaluate + // what we get as our payout + const publicFacet = await E(zoe).getPublicFacet(instance); + const tokenIssuer = await E(publicFacet).getTokenIssuer(); + const amountMath = await E(tokenIssuer).getAmountMath(); + + const tokens1000 = await E(amountMath).make(1000); + const tokenPayoutAmount = await E(tokenIssuer).getAmountOf(paymentP); + + // Bob got 1000 tokens + t.deepEquals(tokenPayoutAmount, tokens1000); + }, + }; + }; + + // Setup Alice + const alice = await makeAlice(); + const installation = await alice.installCode(); + const { creatorFacet } = await E(alice).makeInstance(installation); + const invitation = E(creatorFacet).makeInvitation(1000); + + // Setup Bob + const bob = makeBob(installation); + await bob.offer(invitation); } catch (e) { t.assert(false, e); console.log(e); @@ -57,61 +90,101 @@ test('zoe - mint payments with unrelated give and want', async t => { t.plan(3); try { const zoe = makeZoe(fakeVatAdmin); - // Pack the contract. - const bundle = await bundleSource(mintPaymentsRoot); - const installationHandle = await E(zoe).install(bundle); - const inviteIssuer = await E(zoe).getInviteIssuer(); - - const moolaBundle = makeIssuerKit('moola'); - const simoleanBundle = makeIssuerKit('simolean'); - - // Alice creates a contract instance - const issuerKeywordRecord = harden({ - Asset: moolaBundle.issuer, - Price: simoleanBundle.issuer, - }); - const { - instanceRecord: { publicAPI }, - } = await E(zoe).makeInstance(installationHandle, issuerKeywordRecord); - - // Bob wants to get 1000 tokens so he gets an invite and makes an - // offer - const invite = await E(publicAPI).makeInvite(); - t.ok(await E(inviteIssuer).isLive(invite), `valid invite`); - const proposal = harden({ - give: { Asset: moolaBundle.amountMath.make(10) }, - want: { Price: simoleanBundle.amountMath.make(100) }, - }); - const paymentKeywordRecord = harden({ - Asset: moolaBundle.mint.mintPayment(moolaBundle.amountMath.make(10)), - }); - const { payout: payoutP } = await E(zoe).offer( - invite, - proposal, - paymentKeywordRecord, + const moolaKit = makeIssuerKit('moola'); + const simoleanKit = makeIssuerKit('simolean'); + + const makeAlice = () => { + return { + installCode: async () => { + // pack the contract + const bundle = await bundleSource(mintPaymentsRoot); + // install the contract + const installationP = E(zoe).install(bundle); + return installationP; + }, + makeInstance: async installation => { + const issuerKeywordRecord = harden({ + Asset: moolaKit.issuer, + Price: simoleanKit.issuer, + }); + const adminP = await E(zoe).makeInstance( + installation, + issuerKeywordRecord, + ); + return adminP; + }, + }; + }; + + const makeBob = (installation, moolaPayment) => { + return { + offer: async untrustedInvitation => { + const invitationIssuer = E(zoe).getInvitationIssuer(); + const invitation = E(invitationIssuer).claim(untrustedInvitation); + + const { + value: [invitationValue], + } = await E(invitationIssuer).getAmountOf(invitation); + + t.equals( + invitationValue.installation, + installation, + 'installation is mintPayment', + ); + + const { instance } = invitationValue; + + const proposal = harden({ + give: { Asset: moolaKit.amountMath.make(10) }, + want: { Price: simoleanKit.amountMath.make(100) }, + }); + const paymentKeywordRecord = harden({ + Asset: moolaPayment, + }); + const seat = await E(zoe).offer( + invitation, + proposal, + paymentKeywordRecord, + ); + + const tokenPaymentP = E(seat).getPayout('Token'); + const moolaRefundP = E(seat).getPayout('Asset'); + + // Let's get the tokenIssuer from the contract so we can evaluate + // what we get as our payout + const publicFacet = await E(zoe).getPublicFacet(instance); + const tokenIssuer = await E(publicFacet).getTokenIssuer(); + const amountMath = await E(tokenIssuer).getAmountMath(); + + const tokens1000 = await E(amountMath).make(1000); + const tokenPayoutAmount = await E(tokenIssuer).getAmountOf( + tokenPaymentP, + ); + + // Bob got 1000 tokens + t.deepEquals(tokenPayoutAmount, tokens1000); + + // Got refunded all the moola given + t.deepEquals( + await E(moolaKit.issuer).getAmountOf(moolaRefundP), + moolaKit.amountMath.make(10), + ); + }, + }; + }; + + // Setup Alice + const alice = await makeAlice(); + const installation = await alice.installCode(); + const { creatorFacet } = await E(alice).makeInstance(installation); + const invitation = E(creatorFacet).makeInvitation(1000); + + // Setup Bob + const bob = makeBob( + installation, + moolaKit.mint.mintPayment(moolaKit.amountMath.make(10)), ); - - // Bob's payout promise resolves - const bobPayout = await payoutP; - const bobTokenPayout = await bobPayout.Token; - const bobMoolaPayout = await bobPayout.Asset; - - // Let's get the tokenIssuer from the contract so we can evaluate - // what we get as our payout - const tokenIssuer = await E(publicAPI).getTokenIssuer(); - const tokenAmountMath = await E(tokenIssuer).getAmountMath(); - - const tokens1000 = await E(tokenAmountMath).make(1000); - const tokenPayoutAmount = await E(tokenIssuer).getAmountOf(bobTokenPayout); - - const moola10 = moolaBundle.amountMath.make(10); - const moolaPayoutAmount = await moolaBundle.issuer.getAmountOf( - bobMoolaPayout, - ); - - // Bob got 1000 tokens - t.deepEquals(tokenPayoutAmount, tokens1000, `bobTokenPayout`); - t.deepEquals(moolaPayoutAmount, moola10, `bobMoolaPayout`); + await bob.offer(invitation); } catch (e) { t.assert(false, e); console.log(e); diff --git a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js index ebc66da5130..ada073357b6 100644 --- a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js @@ -10,7 +10,7 @@ import { E } from '@agoric/eventual-send'; import fakeVatAdmin from './fakeVatAdmin'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; const multipoolAutoswapRoot = `${__dirname}/../../../src/contracts/multipoolAutoswap`; diff --git a/packages/zoe/test/unitTests/contracts/test-publicAuction.js b/packages/zoe/test/unitTests/contracts/test-publicAuction.js index 8870260eee5..4e38572a779 100644 --- a/packages/zoe/test/unitTests/contracts/test-publicAuction.js +++ b/packages/zoe/test/unitTests/contracts/test-publicAuction.js @@ -6,7 +6,7 @@ import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import { setupMixed } from '../setupMixedMints'; import fakeVatAdmin from './fakeVatAdmin'; @@ -14,302 +14,215 @@ import fakeVatAdmin from './fakeVatAdmin'; const publicAuctionRoot = `${__dirname}/../../../src/contracts/publicAuction`; test('zoe - secondPriceAuction w/ 3 bids', async t => { - t.plan(34); + t.plan(15); try { - const { moolaR, simoleanR, moola, simoleans } = setup(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const { moolaKit, simoleanKit, moola, simoleans, zoe } = setup(); + + const makeAlice = async moolaPayment => { + const moolaPurse = await E(moolaKit.issuer).makeEmptyPurse(); + const simoleanPurse = await E(simoleanKit.issuer).makeEmptyPurse(); + return { + installCode: async () => { + // pack the contract + const bundle = await bundleSource(publicAuctionRoot); + // install the contract + const installationP = E(zoe).install(bundle); + return installationP; + }, + makeInstance: async installation => { + const issuerKeywordRecord = harden({ + Asset: moolaKit.issuer, + Ask: simoleanKit.issuer, + }); + const terms = harden({ numBidsAllowed: 3 }); + const adminP = zoe.makeInstance( + installation, + issuerKeywordRecord, + terms, + ); + return adminP; + }, + offer: async sellInvitation => { + const proposal = harden({ + give: { Asset: moola(1) }, + want: { Ask: simoleans(3) }, + }); + + const payments = { Asset: moolaPayment }; + + const seat = await E(zoe).offer(sellInvitation, proposal, payments); + + E(seat) + .getPayout('Asset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + moola(0), + `Alice didn't get any of what she put in`, + ), + ); + + E(seat) + .getPayout('Ask') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + simoleans(7), + `Alice got the second price bid, Carol's bid, even though Bob won`, + ), + ); + + const makeBidInvitationObj = await E(seat).getOfferResult(); + return makeBidInvitationObj; + }, + }; + }; + + const makeBob = (installation, simoleanPayment) => { + const moolaPurse = moolaKit.issuer.makeEmptyPurse(); + const simoleanPurse = simoleanKit.issuer.makeEmptyPurse(); + return harden({ + offer: async untrustedInvitation => { + const invitationIssuer = await E(zoe).getInvitationIssuer(); + + // Bob is able to use the trusted invitationIssuer from Zoe to + // transform an untrusted invitation that Alice also has access to, to + // an + const invitation = await invitationIssuer.claim(untrustedInvitation); + + const { + value: [invitationValue], + } = await invitationIssuer.getAmountOf(invitation); + + t.equals( + invitationValue.installation, + installation, + 'installation is publicAuction', + ); + t.deepEquals( + invitationValue.auctionedAssets, + moola(1), + `asset to be auctioned is 1 moola`, + ); + t.deepEquals( + invitationValue.minimumBid, + simoleans(3), + `minimum bid is 3 simoleans`, + ); + + t.deepEquals( + invitationValue.numBidsAllowed, + 3, + `auction will be closed after 3 bids`, + ); + + const proposal = harden({ + give: { Bid: simoleans(11) }, + want: { Asset: moola(1) }, + }); + const payments = { Bid: simoleanPayment }; + + const seat = await zoe.offer(invitation, proposal, payments); + + t.equals( + await E(seat).getOfferResult(), + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + E(seat) + .getPayout('Asset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + proposal.want.Asset, + `Bob wins the auction`, + ), + ); + + E(seat) + .getPayout('Bid') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals( + amountDeposited, + simoleans(4), + `Bob gets the difference between the second-price bid (Carol's 7 simoleans) and his bid back`, + ), + ); + }, + }); + }; + + const makeLosingBidder = (bidAmount, simoleanPayment) => { + const moolaPurse = moolaKit.issuer.makeEmptyPurse(); + const simoleanPurse = simoleanKit.issuer.makeEmptyPurse(); + return harden({ + offer: async untrustedInvitation => { + const invitationIssuer = await E(zoe).getInvitationIssuer(); + const invitation = await invitationIssuer.claim(untrustedInvitation); + + const proposal = harden({ + give: { Bid: bidAmount }, + want: { Asset: moola(1) }, + }); + const payments = { Bid: simoleanPayment }; + + const seat = await zoe.offer(invitation, proposal, payments); + + t.equals( + await E(seat).getOfferResult(), + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + E(seat) + .getPayout('Asset') + .then(moolaPurse.deposit) + .then(amountDeposited => + t.deepEquals(amountDeposited, moola(0), `didn't win the auction`), + ); + + E(seat) + .getPayout('Bid') + .then(simoleanPurse.deposit) + .then(amountDeposited => + t.deepEquals(amountDeposited, bidAmount, `full refund`), + ); + }, + }); + }; // Setup Alice - const aliceMoolaPayment = moolaR.mint.mintPayment(moola(1)); - const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); - - // Setup Bob - const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(11)); - const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); - - // Setup Carol - const carolSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7)); - const carolMoolaPurse = moolaR.issuer.makeEmptyPurse(); - const carolSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); - - // Setup Dave - const daveSimoleanPayment = simoleanR.mint.mintPayment(simoleans(5)); - const daveMoolaPurse = moolaR.issuer.makeEmptyPurse(); - const daveSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); - - // Alice creates a secondPriceAuction instance - - // Pack the contract. - const bundle = await bundleSource(publicAuctionRoot); + const alice = await makeAlice(moolaKit.mint.mintPayment(moola(1))); + const installation = await alice.installCode(); - const installationHandle = await zoe.install(bundle); - const numBidsAllowed = 3; - const issuerKeywordRecord = harden({ - Asset: moolaR.issuer, - Ask: simoleanR.issuer, - }); - const terms = harden({ numBidsAllowed }); - const { - invite: aliceInvite, - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, issuerKeywordRecord, terms); - - // Alice escrows with zoe - const aliceProposal = harden({ - give: { Asset: moola(1) }, - want: { Ask: simoleans(3) }, - }); - const alicePayments = { Asset: aliceMoolaPayment }; - // Alice initializes the auction - const { payout: alicePayoutP, outcome: aliceOutcomeP } = await zoe.offer( - aliceInvite, - aliceProposal, - alicePayments, + // Setup Bob, Carol, Dave + const bob = makeBob( + installation, + await simoleanKit.mint.mintPayment(simoleans(11)), ); - - const [bobInvite, carolInvite, daveInvite] = await E(publicAPI).makeInvites( - 3, + const carol = makeLosingBidder( + simoleans(7), + await simoleanKit.mint.mintPayment(simoleans(7)), ); - - t.equals( - await aliceOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - 'aliceOutcome', - ); - - // Alice spreads the invites far and wide and Bob decides he - // wants to participate in the auction. - const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - const { - value: [bobInviteValue], - } = await inviteIssuer.getAmountOf(bobExclusiveInvite); - - const { - installationHandle: bobInstallationId, - terms: bobTerms, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInviteValue.instanceHandle); - - t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); - t.deepEquals( - bobIssuers, - { Asset: moolaR.issuer, Ask: simoleanR.issuer }, - 'bobIssuers', + const dave = makeLosingBidder( + simoleans(5), + await simoleanKit.mint.mintPayment(simoleans(5)), ); - t.equals(bobTerms.numBidsAllowed, 3, 'bobTerms'); - t.deepEquals(bobInviteValue.minimumBid, simoleans(3), 'minimumBid'); - t.deepEquals(bobInviteValue.auctionedAssets, moola(1), 'assets'); - const bobProposal = harden({ - give: { Bid: simoleans(11) }, - want: { Asset: moola(1) }, - }); - const bobPayments = { Bid: bobSimoleanPayment }; + const { creatorInvitation } = await alice.makeInstance(installation); - // Bob escrows with zoe - // Bob bids - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( - bobExclusiveInvite, - bobProposal, - bobPayments, - ); - - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - 'bobOutcome', - ); - - // Carol decides to bid for the one moola - - const carolExclusiveInvite = await inviteIssuer.claim(carolInvite); - const { - value: [carolInviteValue], - } = await inviteIssuer.getAmountOf(carolExclusiveInvite); - - const { - installationHandle: carolInstallationId, - terms: carolTerms, - issuerKeywordRecord: carolIssuers, - } = zoe.getInstanceRecord(carolInviteValue.instanceHandle); - - t.equals(carolInstallationId, installationHandle, 'carolInstallationId'); - t.deepEquals( - carolIssuers, - { Asset: moolaR.issuer, Ask: simoleanR.issuer }, - 'carolIssuers', - ); - t.equals(carolTerms.numBidsAllowed, 3, 'carolTerms'); - t.deepEquals(carolInviteValue.minimumBid, simoleans(3), 'carolMinimumBid'); - t.deepEquals( - carolInviteValue.auctionedAssets, - moola(1), - 'carolAuctionedAssets', - ); + const makeInvitationsObj = await alice.offer(creatorInvitation); - const carolProposal = harden({ - give: { Bid: simoleans(7) }, - want: { Asset: moola(1) }, - }); - const carolPayments = { Bid: carolSimoleanPayment }; - - // Carol escrows with zoe - // Carol bids - const { payout: carolPayoutP, outcome: carolOutcomeP } = await zoe.offer( - carolExclusiveInvite, - carolProposal, - carolPayments, - ); - - t.equals( - await carolOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - 'carolOutcome', - ); - - // Dave decides to bid for the one moola - const daveExclusiveInvite = await inviteIssuer.claim(daveInvite); - const { - value: [daveInviteValue], - } = await inviteIssuer.getAmountOf(daveExclusiveInvite); - - const { - installationHandle: daveInstallationId, - terms: daveTerms, - issuerKeywordRecord: daveIssuers, - } = zoe.getInstanceRecord(daveInviteValue.instanceHandle); - - t.equals(daveInstallationId, installationHandle, 'daveInstallationHandle'); - t.deepEquals( - daveIssuers, - { Asset: moolaR.issuer, Ask: simoleanR.issuer }, - 'daveIssuers', - ); - t.equals(daveTerms.numBidsAllowed, 3, 'bobTerms'); - t.deepEquals(daveInviteValue.minimumBid, simoleans(3), 'daveMinimumBid'); - t.deepEquals(daveInviteValue.auctionedAssets, moola(1), 'daveAssets'); - - const daveProposal = harden({ - give: { Bid: simoleans(5) }, - want: { Asset: moola(1) }, - }); - const davePayments = { Bid: daveSimoleanPayment }; - - // Dave escrows with zoe - // Dave bids - const { payout: davePayoutP, outcome: daveOutcomeP } = await zoe.offer( - daveExclusiveInvite, - daveProposal, - davePayments, - ); - - t.equals( - await daveOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - 'daveOutcome', - ); - - const aliceResult = await alicePayoutP; - const bobResult = await bobPayoutP; - const carolResult = await carolPayoutP; - const daveResult = await davePayoutP; - - const aliceMoolaPayout = await aliceResult.Asset; - const aliceSimoleanPayout = await aliceResult.Ask; - - const bobMoolaPayout = await bobResult.Asset; - const bobSimoleanPayout = await bobResult.Bid; - - const carolMoolaPayout = await carolResult.Asset; - const carolSimoleanPayout = await carolResult.Bid; - - const daveMoolaPayout = await daveResult.Asset; - const daveSimoleanPayout = await daveResult.Bid; - - // Alice (the creator of the auction) gets back the second highest bid - t.deepEquals( - await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), - carolProposal.give.Bid, - `alice gets carol's bid`, - ); - - // Alice didn't get any of what she put in - t.deepEquals( - await moolaR.issuer.getAmountOf(aliceMoolaPayout), - moola(0), - `alice gets nothing of what she put in`, - ); - - // Alice deposits her payout to ensure she can - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - - // Bob (the winner of the auction) gets the one moola and the - // difference between his bid and the price back - t.deepEquals( - await moolaR.issuer.getAmountOf(bobMoolaPayout), - moola(1), - `bob is the winner`, - ); - t.deepEquals( - await simoleanR.issuer.getAmountOf(bobSimoleanPayout), - simoleans(4), - `bob gets difference back`, - ); - - // Bob deposits his payout to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); - - // Carol gets a full refund - t.deepEquals( - await moolaR.issuer.getAmountOf(carolMoolaPayout), - moola(0), - `carol doesn't win`, - ); - t.deepEquals( - await simoleanR.issuer.getAmountOf(carolSimoleanPayout), - carolProposal.give.Bid, - `carol gets a refund`, - ); - - // Carol deposits her payout to ensure she can - await carolMoolaPurse.deposit(carolMoolaPayout); - await carolSimoleanPurse.deposit(carolSimoleanPayout); - - // Dave gets a full refund - t.deepEquals( - await simoleanR.issuer.getAmountOf(daveSimoleanPayout), - daveProposal.give.Bid, - `dave gets a refund`, - ); - - // Dave deposits his payout to ensure he can - await daveMoolaPurse.deposit(daveMoolaPayout); - await daveSimoleanPurse.deposit(daveSimoleanPayout); - - // Assert that the correct payout were received. - // Alice had 1 moola and 0 simoleans. - // Bob had 0 moola and 11 simoleans. - // Carol had 0 moola and 7 simoleans. - // Dave had 0 moola and 5 simoleans. + const bidInvitation1 = E(makeInvitationsObj).makeBidInvitation(); + const bidInvitation2 = E(makeInvitationsObj).makeBidInvitation(); + const bidInvitation3 = E(makeInvitationsObj).makeBidInvitation(); - // Now, they should have: - // Alice: 0 moola and 7 simoleans - // Bob: 1 moola and 4 simoleans - // Carol: 0 moola and 7 simoleans - // Dave: 0 moola and 5 simoleans - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 7); - t.equals(bobMoolaPurse.getCurrentAmount().value, 1); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 4); - t.equals(carolMoolaPurse.getCurrentAmount().value, 0); - t.equals(carolSimoleanPurse.getCurrentAmount().value, 7); - t.equals(daveMoolaPurse.getCurrentAmount().value, 0); - t.equals(daveSimoleanPurse.getCurrentAmount().value, 5); + bob.offer(bidInvitation1); + carol.offer(bidInvitation2); + dave.offer(bidInvitation3); } catch (e) { t.assert(false, e); console.log(e); diff --git a/packages/zoe/test/unitTests/contracts/test-sellTickets.js b/packages/zoe/test/unitTests/contracts/test-sellTickets.js index 00d23efee6b..6df83099b3a 100644 --- a/packages/zoe/test/unitTests/contracts/test-sellTickets.js +++ b/packages/zoe/test/unitTests/contracts/test-sellTickets.js @@ -10,7 +10,7 @@ import { E } from '@agoric/eventual-send'; import fakeVatAdmin from './fakeVatAdmin'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { defaultAcceptanceMsg } from '../../../src/contractSupport'; const mintAndSellNFTRoot = `${__dirname}/../../../src/contracts/mintAndSellNFT`; diff --git a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js index 41ccf5b6dad..e1184dbe270 100644 --- a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js +++ b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js @@ -8,11 +8,16 @@ import { E } from '@agoric/eventual-send'; import { assert, details } from '@agoric/assert'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import { setupNonFungible } from '../setupNonFungibleMints'; -import { makeGetInstanceHandle } from '../../../src/clientSupport'; import fakeVatAdmin from './fakeVatAdmin'; +import { + installationPFromSource, + assertPayout, + assertOfferResult, + getInviteFields, +} from '../../zoeTestHelpers'; const simpleExchange = `${__dirname}/../../../src/contracts/simpleExchange`; @@ -26,14 +31,10 @@ test('simpleExchange with valid offers', async t => { amountMaths, moola, simoleans, + zoe, } = setup(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); - - // Pack the contract. - const bundle = await bundleSource(simpleExchange); - - const installationHandle = await zoe.install(bundle); + const inviteIssuer = zoe.getInvitationIssuer(); + const installation = await installationPFromSource(zoe, simpleExchange); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); @@ -45,26 +46,43 @@ test('simpleExchange with valid offers', async t => { const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - // 1: Simon creates a simpleExchange instance and spreads the publicAPI far + // 1: Alice creates a simpleExchange instance and spreads the publicFacet far // and wide with instructions on how to call makeInvite(). const { - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, { + creatorInvitation: aliceInvite, + creatorFacet, + } = await zoe.makeInstance(installation, { Asset: moolaIssuer, Price: simoleanIssuer, }); - const { value: initialOrders } = await E( - E(publicAPI).getNotifier(), - ).getUpdateSince(); + const publicFacet = await creatorFacet.getPublicFacet(); + const instance = creatorFacet.getInstance(); + + const aliceNotifier = publicFacet.getNotifier(); + E(aliceNotifier) + .getUpdateSince() + .then(({ value: beforeAliceOrders, updateCount: beforeAliceCount }) => { + t.deepEquals( + beforeAliceOrders, + { + buys: [], + sells: [], + }, + `Order book is empty`, + ); + t.equals(beforeAliceCount, 2); + }); + + const { + value: initialOrders, + } = await publicFacet.getNotifier().getUpdateSince(); t.deepEquals( initialOrders, { buys: [], sells: [] }, `order notifier is initialized`, ); - const aliceInvite = await E(publicAPI).makeInvite(); - // 2: Alice escrows with zoe to create a sell order. She wants to // sell 3 moola and wants to receive at least 4 simoleans in // return. @@ -75,58 +93,48 @@ test('simpleExchange with valid offers', async t => { }); const alicePayments = { Asset: aliceMoolaPayment }; // 4: Alice adds her sell order to the exchange - const { - payout: alicePayoutP, - outcome: aliceOutcomeP, - offerHandle: aliceOfferHandle, - } = await zoe.offer(aliceInvite, aliceSellOrderProposal, alicePayments); + const aliceSeat = await E(zoe).offer( + aliceInvite, + aliceSellOrderProposal, + alicePayments, + ); - const { value: afterAliceOrders } = await E( - E(publicAPI).getNotifier(), - ).getUpdateSince(); - t.deepEquals( - afterAliceOrders, - { - buys: [], - sells: [ + E(aliceNotifier) + .getUpdateSince() + .then(({ value: afterAliceOrders, updateCount: afterAliceCount }) => { + t.deepEquals( + afterAliceOrders, { - want: aliceSellOrderProposal.want, - give: aliceSellOrderProposal.give, + buys: [], + sells: [ + { + want: aliceSellOrderProposal.want, + give: aliceSellOrderProposal.give, + }, + ], }, - ], - }, - `order notifier is updated with Alices sell order`, - ); - - aliceOfferHandle.then(async handle => { - const aliceNotifier = zoe.getOfferNotifier(handle); - const firstUpdate = await aliceNotifier.getUpdateSince(); - t.notOk(firstUpdate.value, 'notifier start state is empty'); - t.notOk( - firstUpdate.updateCount === undefined, - 'notifier start state is not done', - ); - t.ok(firstUpdate.updateCount, 'notifier start state has handle'); - const nextUpdateP = aliceNotifier.getUpdateSince(firstUpdate.updateCount); - Promise.all([nextUpdateP]).then(([nextRecord]) => { - t.ok(nextRecord.value.Asset, 'following state has update'); - t.ok(nextRecord.value.Price, 'following state has Price'); + `order notifier is updated with Alice's sell order`, + ); + t.equals(afterAliceCount, 3); + + aliceNotifier.getUpdateSince(afterAliceCount).then(update => { + t.notOk(update.value.sells[0], 'accepted offer from Bob'); + t.equals(update.updateCount, 4); + }); }); - }); - const bobInvite = await E(publicAPI).makeInvite(); + const bobInvite = await E(publicFacet).makeInvite(); + const { installation: bobInstallation } = await getInviteFields( + inviteIssuer, + bobInvite, + ); // 5: Bob decides to join. const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); - const bobInstanceHandle = await getInstanceHandle(bobExclusiveInvite); - const { - installationHandle: bobInstallationId, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInstanceHandle); + const bobIssuers = zoe.getIssuers(instance); - t.equals(bobInstallationId, installationHandle); + t.equals(bobInstallation, installation); assert( bobIssuers.Asset === moolaIssuer, @@ -148,14 +156,14 @@ test('simpleExchange with valid offers', async t => { // 6: Bob escrows with zoe // 8: Bob submits the buy order to the exchange - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( + const bobSeat = await zoe.offer( bobExclusiveInvite, bobBuyOrderProposal, bobPayments, ); const { value: afterBobOrders } = await E( - E(publicAPI).getNotifier(), + E(publicFacet).getNotifier(), ).getUpdateSince(); t.deepEquals( afterBobOrders, @@ -163,21 +171,18 @@ test('simpleExchange with valid offers', async t => { `order notifier is updated when Bob fulfills the order`, ); - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - t.equals( - await aliceOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; + assertOfferResult(t, bobSeat, 'Trade Successful'); + assertOfferResult(t, aliceSeat, 'Trade Successful'); - const bobMoolaPayout = await bobPayout.Asset; - const bobSimoleanPayout = await bobPayout.Price; - const aliceMoolaPayout = await alicePayout.Asset; - const aliceSimoleanPayout = await alicePayout.Price; + const { + Asset: bobMoolaPayout, + Price: bobSimoleanPayout, + } = await bobSeat.getPayouts(); + + const { + Asset: aliceMoolaPayout, + Price: aliceSimoleanPayout, + } = await aliceSeat.getPayouts(); // Alice gets paid at least what she wanted t.ok( @@ -187,26 +192,21 @@ test('simpleExchange with valid offers', async t => { await simoleanIssuer.getAmountOf(aliceSimoleanPayout), aliceSellOrderProposal.want.Price, ), + `Alice got the simoleans she wanted`, ); // Alice sold all of her moola t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); - // 13: Alice deposits her payout to ensure she can - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - - // 14: Bob deposits his original payments to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); + // 6: Alice deposits her payout to ensure she can + // Alice had 0 moola and 4 simoleans. + assertPayout(t, aliceMoolaPayout, aliceMoolaPurse, 0); + assertPayout(t, aliceSimoleanPayout, aliceSimoleanPurse, 4); - // Assert that the correct payout were received. - // Alice had 3 moola and 0 simoleans. - // Bob had 0 moola and 7 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 4); - t.equals(bobMoolaPurse.getCurrentAmount().value, 3); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 3); + // 7: Bob deposits his original payments to ensure he can + // Bob had 3 moola and 3 simoleans. + assertPayout(t, bobMoolaPayout, bobMoolaPurse, 3); + assertPayout(t, bobSimoleanPayout, bobSimoleanPurse, 3); }); test('simpleExchange with multiple sell offers', async t => { @@ -219,14 +219,10 @@ test('simpleExchange with multiple sell offers', async t => { simoleanMint, moola, simoleans, + zoe, } = setup(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); - - // Pack the contract. - const bundle = await bundleSource(simpleExchange); - - const installationHandle = await zoe.install(bundle); + const inviteIssuer = zoe.getInvitationIssuer(); + const installation = await installationPFromSource(zoe, simpleExchange); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(30)); @@ -236,16 +232,17 @@ test('simpleExchange with multiple sell offers', async t => { await aliceMoolaPurse.deposit(aliceMoolaPayment); await aliceSimoleanPurse.deposit(aliceSimoleanPayment); - // 1: Simon creates a simpleExchange instance and spreads the invite far and - // wide with instructions on how to use it. + // 1: Simon creates a simpleExchange instance and spreads the publicFacet + // far and wide with instructions on how to use it. const { - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, { + creatorInvitation: aliceInvite1, + creatorFacet, + } = await zoe.makeInstance(installation, { Asset: moolaIssuer, Price: simoleanIssuer, }); - const aliceInvite1 = await E(publicAPI).makeInvite(); + const publicFacet = await creatorFacet.getPublicFacet(); // 2: Alice escrows with zoe to create a sell order. She wants to // sell 3 moola and wants to receive at least 4 simoleans in @@ -258,7 +255,7 @@ test('simpleExchange with multiple sell offers', async t => { const alicePayments = { Asset: aliceMoolaPurse.withdraw(moola(3)) }; // 4: Alice adds her sell order to the exchange - const { outcome: aliceOutcome1P } = await zoe.offer( + const aliceSeat = await zoe.offer( aliceInvite1, aliceSale1OrderProposal, alicePayments, @@ -266,103 +263,61 @@ test('simpleExchange with multiple sell offers', async t => { // 5: Alice adds another sell order to the exchange const aliceInvite2 = await inviteIssuer.claim( - await E(publicAPI).makeInvite(), + await E(publicFacet).makeInvite(), ); const aliceSale2OrderProposal = harden({ give: { Asset: moola(5) }, want: { Price: simoleans(8) }, exit: { onDemand: null }, }); - const { outcome: aliceOutcome2P } = await zoe.offer( + const proposal2 = { + Asset: aliceMoolaPurse.withdraw(moola(5)), + }; + const aliceSeat2 = await zoe.offer( aliceInvite2, aliceSale2OrderProposal, - { Asset: aliceMoolaPurse.withdraw(moola(5)) }, + proposal2, ); // 5: Alice adds a buy order to the exchange const aliceInvite3 = await inviteIssuer.claim( - await E(publicAPI).makeInvite(), + await E(publicFacet).makeInvite(), ); const aliceBuyOrderProposal = harden({ give: { Price: simoleans(18) }, want: { Asset: moola(29) }, exit: { onDemand: null }, }); - const { outcome: aliceOutcome3P } = await zoe.offer( + const proposal3 = { Price: aliceSimoleanPurse.withdraw(simoleans(18)) }; + const aliceSeat3 = await zoe.offer( aliceInvite3, aliceBuyOrderProposal, - { Price: aliceSimoleanPurse.withdraw(simoleans(18)) }, + proposal3, ); - await Promise.all([aliceOutcome1P, aliceOutcome2P, aliceOutcome3P]).then( - async () => { - const expectedBook = { - buys: [ - { want: { Asset: moola(29) }, give: { Price: simoleans(18) } }, - ], - sells: [ - { want: { Price: simoleans(4) }, give: { Asset: moola(3) } }, - { want: { Price: simoleans(8) }, give: { Asset: moola(5) } }, - ], - }; - t.deepEquals( - (await E(E(publicAPI).getNotifier()).getUpdateSince()).value, - expectedBook, - ); - }, - ); + await Promise.all([ + aliceSeat.getOfferResult(), + aliceSeat2.getOfferResult(), + aliceSeat3.getOfferResult(), + ]).then(async () => { + const expectedBook = { + buys: [{ want: { Asset: moola(29) }, give: { Price: simoleans(18) } }], + sells: [ + { want: { Price: simoleans(4) }, give: { Asset: moola(3) } }, + { want: { Price: simoleans(8) }, give: { Asset: moola(5) } }, + ], + }; + t.deepEquals( + (await E(E(publicFacet).getNotifier()).getUpdateSince()).value, + expectedBook, + ); + }); } catch (e) { t.assert(false, e); console.log(e); } }); -test('simpleExchange showPayoutRules', async t => { - t.plan(1); - const { moolaIssuer, simoleanIssuer, moolaMint, moola, simoleans } = setup(); - const zoe = makeZoe(fakeVatAdmin); - - // Pack the contract. - const bundle = await bundleSource(simpleExchange); - - const installationHandle = await zoe.install(bundle); - - // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - // 1: Simon creates a simpleExchange instance and spreads the invite far and - // wide with instructions on how to use it. - const { - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, { - Asset: moolaIssuer, - Price: simoleanIssuer, - }); - - const aliceInvite = await E(publicAPI).makeInvite(); - - // 2: Alice escrows with zoe to create a sell order. She wants to - // sell 3 moola and wants to receive at least 4 simoleans in - // return. - const aliceSale1OrderProposal = harden({ - give: { Asset: moola(3) }, - want: { Price: simoleans(4) }, - exit: { onDemand: null }, - }); - - const alicePayments = { Asset: aliceMoolaPayment }; - - // 4: Alice adds her sell order to the exchange - const { offerHandle: aliceOfferHandleP } = await zoe.offer( - aliceInvite, - aliceSale1OrderProposal, - alicePayments, - ); - - const expected = { want: { Price: simoleans(4) }, give: { Asset: moola(3) } }; - - t.deepEquals(await E(publicAPI).getOffer(await aliceOfferHandleP), expected); -}); - test('simpleExchange with non-fungible assets', async t => { t.plan(9); const { @@ -374,15 +329,10 @@ test('simpleExchange with non-fungible assets', async t => { rpgItems, amountMaths, createRpgItem, + zoe, } = setupNonFungible(); - - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); - - // Pack the contract. - const bundle = await bundleSource(simpleExchange); - - const installationHandle = await zoe.install(bundle); + const inviteIssuer = zoe.getInvitationIssuer(); + const installation = await installationPFromSource(zoe, simpleExchange); // Setup Alice const spell = createRpgItem('Spell of Binding', 'binding'); @@ -398,13 +348,13 @@ test('simpleExchange with non-fungible assets', async t => { // 1: Simon creates a simpleExchange instance and spreads the invite far and // wide with instructions on how to use it. const { - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, { + creatorInvitation: aliceInvite, + creatorFacet, + } = await zoe.makeInstance(installation, { Asset: rpgIssuer, Price: ccIssuer, }); - - const aliceInvite = await E(publicAPI).makeInvite(); + const publicFacet = await creatorFacet.getPublicFacet(); // 2: Alice escrows with zoe to create a sell order. She wants to // sell a Spell of Binding and wants to receive CryptoCats in return. @@ -415,26 +365,24 @@ test('simpleExchange with non-fungible assets', async t => { }); const alicePayments = { Asset: aliceRpgPayment }; // 4: Alice adds her sell order to the exchange - const { payout: alicePayoutP, outcome: aliceOutcomeP } = await zoe.offer( + const aliceSeat = await zoe.offer( aliceInvite, aliceSellOrderProposal, alicePayments, ); - const bobInvite = await E(publicAPI).makeInvite(); + const bobInvite = await E(publicFacet).makeInvite(); // 5: Bob decides to join. - const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - const getInstanceHandle = makeGetInstanceHandle(inviteIssuer); - const bobInstanceHandle = await getInstanceHandle(bobExclusiveInvite); - const { - installationHandle: bobInstallationId, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInstanceHandle); + installation: bobInstallation, + instance: bobInstance, + } = await getInviteFields(inviteIssuer, bobInvite); + const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - t.equals(bobInstallationId, installationHandle); + t.equals(bobInstallation, installation); + const bobIssuers = zoe.getIssuers(bobInstance); assert( bobIssuers.Asset === rpgIssuer, details`The Asset issuer should be the RPG issuer`, @@ -455,27 +403,24 @@ test('simpleExchange with non-fungible assets', async t => { // 6: Bob escrows with zoe // 8: Bob submits the buy order to the exchange - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( + const bobSeat = await zoe.offer( bobExclusiveInvite, bobBuyOrderProposal, bobPayments, ); - t.equals( - await bobOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - t.equals( - await aliceOutcomeP, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; + assertOfferResult(t, bobSeat, 'Trade Successful'); + assertOfferResult(t, aliceSeat, 'Trade Successful'); - const bobRpgPayout = await bobPayout.Asset; - const bobCcPayout = await bobPayout.Price; - const aliceRpgPayout = await alicePayout.Asset; - const aliceCcPayout = await alicePayout.Price; + const { + Asset: bobRpgPayout, + Price: bobCcPayout, + } = await bobSeat.getPayouts(); + + const { + Asset: aliceRpgPayout, + Price: aliceCcPayout, + } = await aliceSeat.getPayouts(); // Alice gets paid at least what she wanted t.ok( @@ -493,19 +438,11 @@ test('simpleExchange with non-fungible assets', async t => { rpgItems(harden([])), ); - // 13: Alice deposits her payout to ensure she can - await aliceRpgPurse.deposit(aliceRpgPayout); - await aliceCcPurse.deposit(aliceCcPayout); - - // 14: Bob deposits his original payments to ensure he can - await bobRpgPurse.deposit(bobRpgPayout); - await bobCcPurse.deposit(bobCcPayout); - // Assert that the correct payout were received. // Alice has an empty RPG purse, and the Cheshire Cat. // Bob has an empty CryptoCat purse, and the Spell of Binding he wanted. - t.deepEquals(aliceRpgPurse.getCurrentAmount().value, []); - t.deepEquals(aliceCcPurse.getCurrentAmount().value, ['Cheshire Cat']); - t.deepEquals(bobRpgPurse.getCurrentAmount().value, spell); - t.deepEquals(bobCcPurse.getCurrentAmount().value, []); + assertPayout(t, aliceRpgPayout, aliceRpgPurse, []); + assertPayout(t, aliceCcPayout, aliceCcPurse, ['Cheshire Cat']); + assertPayout(t, bobRpgPayout, bobRpgPurse, spell); + assertPayout(t, bobCcPayout, bobCcPurse, []); }); diff --git a/packages/zoe/test/unitTests/contracts/test-useObj.js b/packages/zoe/test/unitTests/contracts/test-useObj.js index f265d0d323d..f53514ed43d 100644 --- a/packages/zoe/test/unitTests/contracts/test-useObj.js +++ b/packages/zoe/test/unitTests/contracts/test-useObj.js @@ -5,7 +5,7 @@ import { test } from 'tape-promise/tape'; import bundleSource from '@agoric/bundle-source'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import fakeVatAdmin from './fakeVatAdmin'; diff --git a/packages/zoe/test/unitTests/contracts/test-zcf.js b/packages/zoe/test/unitTests/contracts/test-zcf.js index 6bd61e47bb5..48945ec5f70 100644 --- a/packages/zoe/test/unitTests/contracts/test-zcf.js +++ b/packages/zoe/test/unitTests/contracts/test-zcf.js @@ -5,7 +5,7 @@ import { test } from 'tape-promise/tape'; import bundleSource from '@agoric/bundle-source'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoe'; +import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import fakeVatAdmin from './fakeVatAdmin'; diff --git a/packages/zoe/test/unitTests/contracts/useObjExample.js b/packages/zoe/test/unitTests/contracts/useObjExample.js index 48b1f653887..1b0b3e3cb65 100644 --- a/packages/zoe/test/unitTests/contracts/useObjExample.js +++ b/packages/zoe/test/unitTests/contracts/useObjExample.js @@ -7,7 +7,7 @@ import { makeZoeHelpers } from '../../../src/contractSupport'; /** * Give a use object when a payment is escrowed * - * @typedef {import('../../../src/zoe').ContractFacet} ContractFacet + * @typedef {import('../../../src/zoeService/zoe').ContractFacet} ContractFacet * @typedef {import('@agoric/ERTP').Amount} Amount * @param {ContractFacet} zcf */ diff --git a/packages/zoe/test/unitTests/contracts/zcfTesterContract.js b/packages/zoe/test/unitTests/contracts/zcfTesterContract.js index 22722149398..03311d1a0be 100644 --- a/packages/zoe/test/unitTests/contracts/zcfTesterContract.js +++ b/packages/zoe/test/unitTests/contracts/zcfTesterContract.js @@ -5,7 +5,7 @@ import { assert } from '@agoric/assert'; /** * Tests ZCF * - * @typedef {import('../../../src/zoe').ContractFacet} ContractFacet + * @typedef {import('../../../src/zoeService/zoe').ContractFacet} ContractFacet * @typedef {import('@agoric/ERTP').Amount} Amount * @param {ContractFacet} zcf */ diff --git a/packages/zoe/test/unitTests/setupBasicMints.js b/packages/zoe/test/unitTests/setupBasicMints.js index c8284ef73b2..a5d7976ea2b 100644 --- a/packages/zoe/test/unitTests/setupBasicMints.js +++ b/packages/zoe/test/unitTests/setupBasicMints.js @@ -1,4 +1,6 @@ import makeIssuerKit from '@agoric/ertp'; +import { makeZoe } from '../../src/zoeService/zoe'; +import fakeVatAdmin from './contracts/fakeVatAdmin'; const setup = () => { const moolaBundle = makeIssuerKit('moola'); @@ -17,13 +19,17 @@ const setup = () => { brands.set(k, allBundles[k].brand); } + const zoe = makeZoe(fakeVatAdmin); + return harden({ moolaIssuer: moolaBundle.issuer, moolaMint: moolaBundle.mint, moolaR: moolaBundle, + moolaKit: moolaBundle, simoleanIssuer: simoleanBundle.issuer, simoleanMint: simoleanBundle.mint, simoleanR: simoleanBundle, + simoleanKit: simoleanBundle, bucksIssuer: bucksBundle.issuer, bucksMint: bucksBundle.mint, bucksR: bucksBundle, @@ -32,6 +38,7 @@ const setup = () => { moola: moolaBundle.amountMath.make, simoleans: simoleanBundle.amountMath.make, bucks: bucksBundle.amountMath.make, + zoe, }); }; harden(setup); diff --git a/packages/zoe/test/unitTests/setupMixedMints.js b/packages/zoe/test/unitTests/setupMixedMints.js index 514eb6a0a01..74686d62eea 100644 --- a/packages/zoe/test/unitTests/setupMixedMints.js +++ b/packages/zoe/test/unitTests/setupMixedMints.js @@ -1,4 +1,6 @@ import makeIssuerKit from '@agoric/ertp'; +import { makeZoe } from '../../src/zoeService/zoe'; +import fakeVatAdmin from './contracts/fakeVatAdmin'; const setupMixed = () => { const ccBundle = makeIssuerKit('CryptoCats', 'strSet'); @@ -22,7 +24,10 @@ const setupMixed = () => { const moolaMint = mints.get('moola'); const cryptoCats = allBundles.cc.amountMath.make; const moola = allBundles.moola.amountMath.make; + + const zoe = makeZoe(fakeVatAdmin); return { + zoe, ccIssuer, moolaIssuer, ccMint, diff --git a/packages/zoe/test/unitTests/setupNonFungibleMints.js b/packages/zoe/test/unitTests/setupNonFungibleMints.js index b6ef79349dc..5abb9463862 100644 --- a/packages/zoe/test/unitTests/setupNonFungibleMints.js +++ b/packages/zoe/test/unitTests/setupNonFungibleMints.js @@ -1,4 +1,6 @@ import makeIssuerKit from '@agoric/ertp'; +import { makeZoe } from '../../src/zoeService/zoe'; +import fakeVatAdmin from './contracts/fakeVatAdmin'; const setupNonFungible = () => { const ccBundle = makeIssuerKit('CryptoCats', 'strSet'); @@ -19,6 +21,7 @@ const setupNonFungible = () => { function createRpgItem(name, power, desc = undefined) { return harden([{ name, description: desc || name, power }]); } + const zoe = makeZoe(fakeVatAdmin); const ccIssuer = issuers.get('cc'); const rpgIssuer = issuers.get('rpg'); @@ -36,6 +39,7 @@ const setupNonFungible = () => { amountMaths, brands, createRpgItem, + zoe, }; }; harden(setupNonFungible); diff --git a/packages/zoe/test/unitTests/test-cleanProposal.js b/packages/zoe/test/unitTests/test-cleanProposal.js index c1d66d3f689..9fdb987a312 100644 --- a/packages/zoe/test/unitTests/test-cleanProposal.js +++ b/packages/zoe/test/unitTests/test-cleanProposal.js @@ -3,7 +3,7 @@ import '@agoric/install-ses'; import { test } from 'tape-promise/tape'; import makeStore from '@agoric/weak-store'; -import { cleanProposal } from '../../src/cleanProposal'; +import { cleanProposal } from '../../src/zoeService/cleanProposal'; import { setup } from './setupBasicMints'; import buildManualTimer from '../../tools/manualTimer'; diff --git a/packages/zoe/test/unitTests/test-offerSafety.js b/packages/zoe/test/unitTests/test-offerSafety.js index 261661ab65b..d2ceba59abd 100644 --- a/packages/zoe/test/unitTests/test-offerSafety.js +++ b/packages/zoe/test/unitTests/test-offerSafety.js @@ -3,7 +3,7 @@ import '@agoric/install-ses'; import { test } from 'tape-promise/tape'; -import { isOfferSafe } from '../../src/offerSafety'; +import { isOfferSafe } from '../../src/contractFacet/offerSafety'; import { setup } from './setupBasicMints'; function makeGetAmountMath(mapping) { diff --git a/packages/zoe/test/unitTests/test-rightsConservation.js b/packages/zoe/test/unitTests/test-rightsConservation.js index 4661b450ff7..e733fb8feb7 100644 --- a/packages/zoe/test/unitTests/test-rightsConservation.js +++ b/packages/zoe/test/unitTests/test-rightsConservation.js @@ -4,7 +4,7 @@ import { test } from 'tape-promise/tape'; import makeStore from '@agoric/weak-store'; import makeIssuerKit from '@agoric/ertp'; -import { areRightsConserved } from '../../src/rightsConservation'; +import { areRightsConserved } from '../../src/contractFacet/rightsConservation'; const setupAmountMaths = () => { const moolaIssuerResults = makeIssuerKit('moola'); diff --git a/packages/zoe/test/zoeTestHelpers.js b/packages/zoe/test/zoeTestHelpers.js new file mode 100644 index 00000000000..30520174340 --- /dev/null +++ b/packages/zoe/test/zoeTestHelpers.js @@ -0,0 +1,35 @@ +import bundleSource from '@agoric/bundle-source'; +import {E} from "@agoric/eventual-send"; + +export const assertPayout = (t, payout, purse, amount) => { + payout.then(payment => { + purse.deposit(payment); + t.deepEquals( + purse.getCurrentAmount().value, + amount, + `payout was ${amount}`, + ); + }); +}; + +export const assertOfferResult = (t, seat, expected) => { + seat.getOfferResult().then( + result => t.equals(result, expected, `offer result as expected`), + e => t.fail(`expecting offer result to be ${expected}, ${e}`), + ); +}; + +export const assertRejectedOfferResult = (t, seat, expected) => { + seat.getOfferResult().then( + result => t.fail(`expected offer result to be rejected, got ${result}`), + e => t.equals(e, expected, 'Expected offer to be rejected'), + ); +}; + +export const installationPFromSource = (zoe, source) => + bundleSource(source).then(b => zoe.install(b)); + +export const getInviteFields = (inviteIssuer, inviteP) => + E(inviteIssuer) + .getAmountOf(inviteP) + .then(amount => amount.value[0]); From db33c09f35500a05a251bd95246ba819bf8950e6 Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Wed, 5 Aug 2020 16:53:49 -0700 Subject: [PATCH 02/31] refactor: update seat namings, add seats.md to docs with diagrams --- packages/zoe/docs/seats.md | 107 ++++++++++++++++++ packages/zoe/docs/user-seat-exit-flow.png | Bin 0 -> 15252 bytes packages/zoe/docs/user-seat-exit-flow.puml | 27 +++++ packages/zoe/docs/zcf-reallocate-flow.png | Bin 0 -> 24569 bytes packages/zoe/docs/zcf-reallocate-flow.puml | 30 +++++ packages/zoe/docs/zcf-seat-exit-flow.png | Bin 0 -> 13116 bytes packages/zoe/docs/zcf-seat-exit-flow.puml | 26 +++++ packages/zoe/docs/zoe-zcf.png | Bin 500034 -> 466090 bytes packages/zoe/docs/zoe-zcf.puml | 2 +- .../zoe/src/contractFacet/contractFacet.js | 18 +-- packages/zoe/src/contractFacet/exit.js | 6 +- packages/zoe/src/contractFacet/seat.js | 16 +-- packages/zoe/src/internal-types.js | 17 +-- packages/zoe/src/types.js | 2 +- packages/zoe/src/zoeService/zoe.js | 31 +++-- 15 files changed, 240 insertions(+), 42 deletions(-) create mode 100644 packages/zoe/docs/seats.md create mode 100644 packages/zoe/docs/user-seat-exit-flow.png create mode 100644 packages/zoe/docs/user-seat-exit-flow.puml create mode 100644 packages/zoe/docs/zcf-reallocate-flow.png create mode 100644 packages/zoe/docs/zcf-reallocate-flow.puml create mode 100644 packages/zoe/docs/zcf-seat-exit-flow.png create mode 100644 packages/zoe/docs/zcf-seat-exit-flow.puml diff --git a/packages/zoe/docs/seats.md b/packages/zoe/docs/seats.md new file mode 100644 index 00000000000..4921a6f159f --- /dev/null +++ b/packages/zoe/docs/seats.md @@ -0,0 +1,107 @@ +# Seats in the Zoe Service and Zoe Contract Facet + +Note: These are internal documentation notes. For how to use Zoe and +how to develop smart contracts, please see +https://agoric.com/documentation/ + + +__UserSeat.exit() Flow__ +![UserSeat Exit Flow](./user-seat-exit-flow.png) + +__ZCFSeat.exit() Flow__ +![ZCFSeat Exit Flow](./zcf-seat-exit-flow.png) + +__ZCF.reallocate() Flow__ +![ZCF Reallocate Flow](./zcf-reallocate-flow.png) + + +## UserSeat + +The `UserSeat` is what is returned when a user calls +`E(zoe).offer(invitation, proposal, payments)`. It has the following +type: + +```js +/** + * @typedef {Object} UserSeat + * @property {() => Promise} getCurrentAllocation + * @property {() => Promise} getProposal + * @property {() => Promise} getPayouts + * @property {(keyword: Keyword) => Promise} getPayout + * @property {() => Promise} getOfferResult + * @property {() => void=} exit + */ +``` + +Note that exit is only present if an immediate `exit` is possible. The +user can use the seat to get their payout, get the result of their +offer (whatever the contract chooses to return. This varies, but +examples are a string and an invitation for another user.) + +## ZCFSeat + +The `ZCFSeat` is a facet of the same seat, specifically for the +contract to manipulate. It is the `ZCFSeat` that is passed as the only +parameter to `offerHandlers`: + +```js +const buyItems = buyerSeat => { + const proposal = buyerSeat.getProposal(); + const moneyGiven = buyerSeat.getAmountAllocated('Money', moneyBrand); + ... +``` +The type of the ZCFSeat is: + +```js +/** + * @typedef {Object} ZCFSeat + * @property {() => void} exit + * @property {(msg: string=) => never} kickOut + * @property {() => Notifier} getNotifier + * @property {() => boolean} hasExited + * @property {() => ProposalRecord} getProposal + * @property {(keyword: Keyword, brand: Brand) => Amount} getAmountAllocated + * The brand is used for filling in an empty amount if the `keyword` + * is not present in the allocation + * @property {() => Allocation} getCurrentAllocation + * @property {(newAllocation: Allocation) => Boolean} isOfferSafe + * @property {(newAllocation: Allocation) => SeatStaging} stage + */ + ``` + +## ZoeSeatAdmin + +Internal to Zoe Service code and passed to ZCF. Never external. + +The `ZoeSeatAdmin` is the administrative facet of a seat within Zoe. +When `exit()` is called on this object, the payouts accessible through +the `UserSeat` are resolved. `replaceAllocation` changes the Zoe +allocation to the `replacementAllocation`. + +The type of the `ZoeSeatAdmin` is: + +```js +/** + * @typedef {Object} ZoeSeatAdmin + * @property {() => void} exit - exit seat + * @property {(replacementAllocation: Allocation) => void} replaceAllocation - replace the + * currentAllocation with this allocation + */ + ``` + +## ZCFSeatAdmin + +Internal to ZCF code only. + +The `ZCFSeatAdmin` is used by `reallocate` within ZCF to commit the +allocation from a `seatStaging` to the corresponding `ZCFSeat`, and +to tell Zoe the allocation has changed. + +The type of the ZCFSeatAdmin is: + +```js +/** + * @typedef {Object} ZCFSeatAdmin + * @property {(seatStaging: SeatStaging) => void} commit + */ + ``` \ No newline at end of file diff --git a/packages/zoe/docs/user-seat-exit-flow.png b/packages/zoe/docs/user-seat-exit-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..22a26dc371bb375dd7abe597c13950daf4119b91 GIT binary patch literal 15252 zcmb_@bySsKx2_@}NGdAb5`uJhDJ6)2(wi2LO-i?PBMs6>3v5biQv%YRn?|Iiq#N$q z`1`)|opH|n>t4rjINb01t~J+MGoCr;vw~m0lEJx0a_`oyTR3vE&sA>Sx}6ICl4GEN zUo>!7SinC|?O$lv8(G`9SiCc_za{g|>fHwe`*+4vhAvcQ_VzY{Y-~0b23GbCmKLl= z)|U6#1<1e%aqr(~*#GPITerb5&M6)$5NL}yzPnhb*Mv+CQP`N4Ptf=$pSKUQRL&gR z-}cixeW;y~pT3?8=OAG@uyk^?a!hPcGp^yaCHgKbxkb&#>v>O=u|@DT<>QKpi1QtH z-#@v7QVoQnl35%t=51;*ODFxJ=-LPlQLc`>02I!g9>{sv6ZZX>j_ zyC0IN@;IwE&CXI@7aYv?M%{b0EBj3&fE@FbJ?KrBEK4l2U}4+Dlb^%AGEx&+5sVZz zoAR|PRjD7EL+Qd!Zl?&wGH-kI6^|JVr%G_LmAC90V?>4Uq`K45WVLa=+G8odJ#UxT zJ{*J(A=-J33uhy~B~nYM)rFs9;M=w)>6{;*=I6acniIHs9hrDOAzw-#Q=^$QBm zp84n$gdM>8`M_YanR(+^QpfFOMvqFG0IYq@ri0D^`^iKrMFaV9<)!D8W-DcSmsaz$ z@mHJNw{8je%RQHP`_o9v*j2cprB&#w28~sp6YUzKC{DeW2oNT7HL`)c5xi&?cYwB zblK@mtxJBkjw*qFMuW9Oh2b;EpAAKoSi>3S@u50EUnh~kclEUor^0BEfJ@{1%~3&U zF(j=cVfRrp`D78`b<+QzuXh%~M4bQV<4KsMOiXB8_0xY-Fmb>+e*PGU<#xQiykjBR zGa-#w=}WFopyKYFskM_s90Xe0hg)GuW)0#Ly#yj91k8S?prB}OZqCfiBxKd8v0KGW z4<01RhWfp1wfb3R)NyupCOOs8+M1P>b*kVuP6eq*ij9q>Qk`jVOBnc?s9kNv0)g9i z$3BH3U^rMV`K)wy<{I$G1%EK$59ss#>5QUnsINysL6MY{q@kt#*D&YDJ7SO* zZc&!gRlAyTLnV4m7L(<2%k_uj+U~f@=Ho@dDC#hsE-*%?GCqA`ruSLZvQ zxlGqOG#^tzM(DDkpIZ=!1f25tXPPa-c$A_Sr>p6OZwjLMtaZcc=@kwo{ax7^d?^}hJLj_)pulmo-)v=-f^k(et`zosYC)R#C$a0n|Q-C-9xR$W`RP) zRp04gZD`oLH2|j&Y>tY`gP!2^NBd%~2N+xfX|8c1&fCs=E@+J3Z}?$sYHdUay7X=pW3WvOlPCjT`x3k!~x zYN@`&=1T8pf-iS8V?#JUi(O7`KA?Sm(HFz;DKPLZudd7Tqlf;ZCnqQFr+X4Z*(t-q zv2k~_zL^b`zk3;Lz3_|`Wj%?N8bXjK6^NT(mz+EuPRf_KI+!V?z|UUygB4xg;&^ki z;{0f}KWn`$(%_(>K_PiaNEodtVk|kGY;i#jIAO&$+O*#UcQ-4g)XX zWmi|%g#WKnwA+mR)=ii3bc=4^a*0bhlDMNoA|kfOi%FUBX-k00Xm<{bnxF1154&(Y z>5b;N*LoC4>4_1iASV~~p*%~&n`oYno?ia07Jq1{I6N2V`17EU5O9F_Ojnbp>0Otb znPeC-&HCTB7dt-LEq9}R%FA25INdLc;(?A|aXRkIv51jDHiApvF^2kIv=^obIc|1F z@6wUO>3BUac4bx)R*ew>lZ6K!l%+P03BBzV6Cz=nINVrLq@!^A_2J{~*%$^iG&Bx_ zmLCLlz^E*af%?}uZW&GoC0^hQ$jQlBNDEla@ME2ZJK$6(^ZGy2`j&6+$eL$w3DhMH z_uGGrs@;srV?KJK)xJKGr>d$7EG#@5XwUUt58K8sE(BpSR$SXoTJETUO%Bf$ERuD{p?C^EifdJso%oA?-#Q z2ZK^tm?29_+l#;Ep;qCzrIkxXw&_$GnH|rfU0yO)ZaOemYiIiY?E>&8vv7x=DB8O{ z*S`x(U|qLH#8qupV<&2L)loTQ0QtN5}L8L+zZLOc4Pw z<0X1xvlp5=sKY!ENw^6ig{b?niT<^-B`G&Jh$MOxIC?ezsQ% zduu~kp@eMR>UhIjHjj({o-6QOH=BQ-73W5eX>~|$hLo=1zOk!toA)=mMzSy*30r?K z%+r!XePjH7FQsl2X}NT4H&<({E)zm9IWFP8KjlP zj6G%W%k0#arRBhVsSWQBA(-EAZ`NATuQ7m`RKfk^$rE8=G7l<2K|xm5h!WG>A=iu) zLzH6V0uPFy!NGw8X|pG5L-!Y-7#N9kn6_+w{CMlYdvNfzy|0kd_MgLEZd|NbkuS!> zxk{AfP{aeHCLF~K2e5C!TJ~GJi?3rBmw6QT>aWfU=jNJ-=IicR{`pDak44C++`^(= zWl^Z6$k9(_QnXq`vAnRnyu7gyYW30fHaeQS2v>Nmco)kP<9G3dr)yd8_Q_|7mRSc~ z3^@DCJp`m~-}y1s8qP>Za&dT%lDTz;_m&C5F5~~18*Xp8rG?j}+UUOCbd_Y~zQa`J zdsp3fAZ_cyMa)94vHoZle8Yw{l^Y(vdRBD<2i5J>eCtSMMBVYvLORe@p_Wr>CEou%1M{IT$l> zgjz|O538TI_-a38MfM+?9jCbhT&13P9P9bHzLcGYV_w(8yOg5=cKa)O%2QRASW9{A zE~BLeBGB=or4_Z4{ejs@9c-yYGJ$eJ<|FwW`I6a&IvHJ`1Bs?S`3oTj_K zUJQ=pz9M(toS4ea&RR<$3Jar{y-TOfZ$4AAQ)R8^{PX)>ih$k8`GD{0ueOiFIiWa| zSIa|Lg&eridf5{Bt3d{=uj#j@t%an5S#|z&@X`bf@>tK^ic1ncb(5I zdKn+4+2l#z@4V8B?x>$C_*T2p_4>q?&3$R9J5F_&hx@UktxMC@8P{F{)7yuN*zca7 z;!$jx5>E)n@@x!d>VepEU*LFCvs<41{a@7|g+bTcPBK8$XzNQB`Ygs|PWs{Ov`^%N z@gvdi9OC+(_O}j{*8c?JeL7elxi27gQF>IYUDr$AhOPByT>BBjs|&Fz?MzAH13nk@ zE>zbDq7{^P77`LzuX)yd-1(ju(@hs^9Xeo__r9H1Q*M!)h{$)s~oEycFkM>!zu^Rp_Yl#|eRk@l89zYk0(zaBo__cENO|F^ZV&@JOD$eR{t;yXV^gZ_TIO(Gg517tT9NRPP}4oz3^X7aP^ttwMq%U<91|Wxt&kU zpjPF!}sxI4`|Q z{KDY_-KH&a`@osPbDQNK8_tVH+m^gbzl) zKm)>rYP_yt7Nq-0^)7ZVV@h)~OJ9DgXBEy&5yjQ2CkY3a}`}Zd0XCHbLVy))q z!sX~l%!adXm)@s!&BWK67cdjGkVOPU(XNv$5WUHUk_+*+&FueKcndsSo)RSrq|VNC zpux?J&ms{GGTm_g2>=&JZ8;D10=_hAB28v1Ro*D(GPyniMkgxD_3IaIM$cH0W`Smf zlOl!x1;ANwn`X*9My+puBB|3WT1oj}hz7TpF?z~a2V@-2Q02=lr_&S!I26xU(-nL1 zDW79=9a}eTxnEy3DkhuIn&szq{hnr$uQ0`vX_dPpO(YMUZ{%1>B$_|mn#Lpy$g(K_ z+h_DT3YIFi6IgIhUW(0!Mdw6}e@OXd0&6^%Del2onNia_flQA?Is<%f1Ig9^p3kaR zp$xsBY%s7dzrHOz9x73w@Mnb-s=Z6O&s8MqAt;G*ak0{}vvt|ST=MY8C^m6T=)*Mu zLk)~rV+`4N!uO;3xEDl@2J}HmE1gjuz-udHgcn*DGQKU*jq=c>CyvF!iu*1k0@`R?5y3_Yp#WEUIh^|G(@iofro_cyuNnSSP^n_$M= zGXI}nRjy4PtV_+z+^e>jj1=)8$7gSPmHKtFw#8~zt4L3bi=O@@oUhxk+H_!?e-T;| zr`6ypFWEwEQTm|qzaa2Uds@>*GH}qa}KT zD;D#OyQ}GSZZ3+@!3=5h-?qKQAC@|;I-SueOn=zi$%+OkVHuwW5vQ(IbHm#fiko4H z8GUpAbJ+cZls9nuvPKV~O3Ued^D)F{0*?peHLo)-Hrseq^x_7JJ+D2UNHadWZt=g~ zn5qN$O!WYA+cUtd8=bZ1R2iWpyBp03X2 zDV2EbG*p}Ro=B)r_MQVI1Q2!BX+sC=6i4#8ZAqos%`&x1B+1rF?_h4|J_9^)=DqN-SPBW0C?zf{;4iz&!WUVIe2h zs*Bjd|K^eardsA)iUy>&8<`a zf7yd2!FLx$`NEGzs+Ghnsa+%`)eJrPysnfQTq})!mD{h^knqgK%JZ_aFqvy3b^om^ z&e7RMi6Q&pyccylyvl6IENoqann?EQWKmrQY(8%Rw0*g=(AS00zHjeeN&+kzkCLKU zBs_V-St?Bt=O6FU8rltJXH=nHEhGtlJgPX4(?2}A$pL{A9*IO6W9(h| ztD=1F?;NK|*1hKwCG8VE4xbIoi7CAXjo+&gcQ==%$S_SV73ADiXGv(C#MxJ>z4z}- z5$QV(y$#t6xsEn^dqw_Pr=R`*2(AAp{eL8ywh^!wy`TNHUxx-TS6BVFa0rsuZ&qrD zWE=612N|KP z8sbq@<$2jGplNQhJbRY?I-cxh#VC0~&eXK#Jxv&=eYn#);V@5XlqMuJJbd;X8xQXj zD3M2uQZdW(CZA0@)vM=)T`hEIrn}dEc~ddwFP4PJKd-qh_D6v8BW#!TvfONl#(xJ& z%PnEzp!lk^b?&|ibo_vL{cRoT&X`ak!+`GemlCJucHJ?d5(;*cvGkthf+WxDq)J9F zJ=HWu z<-)_#kG?bqCh(Ko;Art6EfVKN(u)+~K8Cf_kJWXQdX2lMSEu_1X~|X9Jqh!un%LFFg=%MvP<#HYcP9A*rX(<{ z&p%BD7q%RlmKp?qZ7+;ye|7J6!<1wL4_fpmRB8(&0=YUG_fJfd7{J^O;&79s{rv_h z_@|WyIMt!SFn#q3Ue@}$l_W5~&-4P@O$@2m0k}ffabw!Y)x^l5WWWPDpwM|8EDH@& zrcNW*byWs@U@&qCxJi7CUDQ>McAm(q6#`D@lfg~4re{wCQvJM2&g0CUmc;rORz^O@ zHxIDyd*G&xU0-fOx}&Hh25JL^YQ+eiOlBWJS(ZrM#_x%;c1Fu?`H}JqtYl91?9BhW{M9zpF(E2eb(QR-piJQR|tY1u> z8-~@>fcVk-IVnl&%kyB*%d;|wgPI@JGoURG`ulY3mI^UHC@pu#i3kfXQ5Zp{0SW`H z4QiKi9i{I&hcQZ`&7i5*lIslW;YC`NOeW^fT9bI7zefw~j<+Ee)lh`h-cr{D5r^S( zfOEhSf>go$T54$yWWSG^UWW3@*R?+~00s6ekMko7?bJuq7(J1&2EUHuMZQPxj~G`( zryJZ)Il&gUul|m-l1EUX{>iZJ`H+*BlQRage3$)|j!%@7+-9%AJ|}wp`Vl-B0jYD$ zv^hAXdzr@CBB_UtOO}{sJ6t{QSqB*_)^?^T=|E~RLg#YnEA zko{WZr%!lQpd-*li<8&~60|CfGD9#?+n{teTbBf!CD)qJ6XH~}>iiyIj8qsc(DPEx zlI}`1FT{|je>hy3u>W(Z?PIwm&eMYRfhU_kf(iDp+u;EeX?)gmpo;<4>gMJ~s|~sp zgC2^>m5$|Q5M=b{SC@J7-5e?^DmE5CnX0Yl(_&-&YPH|I zd3ZrUufV8V?`&`XEg^xho8wCB-T|mZspQDxQ3yxMSA>8lHlU!@F0lN_o5%qDp0{Xh zGw4hv>RAF^p^Jn)MTr^w_+g9U!sGTd(PGB&T%8l!<>U@x=?Oi(=qx z>jS$72lh@*N{SwP9w~Cae*LnVscCl8Lw|!FBZJkJMB5ELpNsc(u|}WziXp)W+L=$~ zs&}lJhBtQ`_^X8MR(c6X(ksk{zMMEex>Q{d>AR~7sg5}om68Gr_6ONsbCfQ@K;Pg* ziQIK){epJ=JFV#cYsejmPHISn&w0lHTH{=EWPCeAe&WdDH9yD6I@c}K1sd>SM}L3^ z?0jpn!F;)Ezj%4NVj0)Rh8(1ti``QhB7Nys#uAq^Gq(0Jm%6~g9{`NyfQ&5(R^-P@ zPtr5wCE=eMYyPaqfZ+teaDD5$USyQ)zQovb<*Qj8ea9y z>=ouG80Qjj`pzR)z1fM!EpBh6CXTuKUtPrjY{*~Mh~30CG&9PkHBPb`c0As}Gh989_LNB2?b>f&v6#9a_g zZyq#1#~W#gy&gSO8oI6{>${Y3^05I;bI!JARsq-%lLS@8 zu1<3s!Zp`xukc}*>r<)}AX3N#;XInQPOk_%77WhNdu*LQhdGRQ^!lhI4| z7YWGvt*%)_(MUlZ05Amn8WTaDyPiao2-6ITMQaHhxIWLDcP?oa5IZiR)l>Wl1{Bhr z(w0M{?G4LnQZ3AHG`4ByiYojfAnU2BcAJ?yZV~&!fB|W7Ees5lv9Yl+Gg|{KD&V1M z2gKn=vAt^~6Z2LlO+6PbC3k9_4#K)A+ZZskn_V{mfxyGVgW7UoVPR-!Xb(FZs??PY zo1L-c&UaLr+Ypa9Og{P)$N<31)2B}*a%=cha1VfmPw)>XG>i%fS?e3^zPbR z^vM%Hp^!@~Q%Qo-Il&K0vTST6pw;%x#Mn4n_zu`n&H^YAqh;~b*3p&C+z!490VTlT zSL|p0nKj$BUe{M6{$+%dRhFbGF)=Y92Bb46QLs9!59i1hYgW*&TF=6Hyig>IW#It> zOq7(A*puv!;!LR^Zt^A$EFzaJ)}n#%%TLFGew}gJN7dx*ZilbT&3^-EDw#f1Y9RGZ z-x~$(%a<>af(9+9o015V{*+*4Z5^GNtIKn*%R}`g1+QGF?_Ioj+$QH;8stR46VdI% z;~%8g6qtnuBCPi@j~Hrfuc66De{|Jv$P;ydJmE77TGq>ef>1|PzN^Wl59fuBKNIyH zM9GHAch$qZOC>5^g=_Y@Z7E#%7kcK*j6|!Rpe9h$)8{(x{xTDFa&lUkthHl^sbyA& z)h8Z5&4wm{p6*lLp~{mWW~C?e>m2F&O0Occ{v3bWcPU{W%9OhME%hIwf@!l4+8<-k zlCv8l+!ysm{w3x;_&u-uh_O?A6%1yJ+!H7Sr5Ux=x?l^V9) zYey^$1+yklLmK*@{?HhlWyw!O8h~g@k4}2~T6Z?b*Tm?Cq&a zX3&n6Wzj5O0)4++rS!Q1RR%u3YS5R@i-El-8|KNGc#Z5+TLZ0Ii(j{PwCDsmUxep= zb^@Fp5DVVH+nYTTumBtbfjEH&sGw)f&YqoB;y8yK+!S|PHH+l)COuK7|3tj&8PAv>F{mm2+INvSu z$!ZphQqW22sy$$|_f?R}0}3mq;ojSN3-|qy(JQ3S0q55vjmFDMg8S53_9C^2kZfihS=VXDFEYu z^PiYE$rpHGl#;$f&MSMdo4WS#a)y&xq6Uz%ZmcSK`V+{I4u59n@W}%xm)p49>Q&RcY8AJ|2PkOcc*RlNgL z6QgL51DUNyN&quWp5N?si?g*_Ng5PYMM_VEls*&Y9i$1ZQrj@ui+UPX#rx0a;)tl2;5HvOtwjNEhzD5O^siDGBI$nXf@av$eH#Z!-qIyp02nPLWby15k|K zQzy}8Z0cDVJ)P1ta5lt#4lDwv6Sa29tdM&@0&Kq;|0ipN!w9~@W#A|-e(NC&Ye+^O#~m(2Qb8}NW3;h>$_qF1W#>9PV^^4JtJSvCtO zbn-Rtdg3iXCA&QjpFI~^DC6Sn;&KYuh}rDst&!m+iJ<(GqfrRm+#U+iJY4oCnz|QM z(QANXpn||A_T@CuuD*&O_3TffT-Y2fcz?Q=1B&b#X=`ibG7&{B(%vYiD}AKY~2-3W*!Ct_sp8V}Xlf(foaZK>tIUP0)2@rq)BS(KAu3u*iwu9u*xzuXU+<82+OwkBBg zrC??a@svt*)Bb-kmb_LJ>2U6ox@CZb7w8us3rm;_k{}_J?g4El=EIA_4x}moy-#K} zg&$>!TAb%UeW#wah7pU2pMaD*Qp`(tn8(ym3c{uPg0sK1=s9Nfau0(|KOi=EoIAGM zYP6V)2KB?Qde_COdF~o2fEE+B->UV^c_)xkl<$bt?{PBdo#Iy(H+$^KNhvhjI4(ZX z0Cj(bWM0518dl&<7qD}cVn1I=#?2Qs@sGYcjj@@?;StW2JixH z;`{egpa8SgJ4#aR&)&kI!&J&v*-*zH0r2)>8e-GPXWaIR3JRqQZ8={+AIAGr<4vQO zA1ytkEu^8P(VQm_O4Os%SU)*ojjv_#pjA?Qv;v)wxE9TW2vO?1OSIUF&g`IQE=rdh z5HL}-)rD;jm?&4LrUVtt>Qyb*%L$f$FyY2sMD8I}E0Is=O;cb7Pc|00Xj!+vZ%x+$ zX47cVWF01EAmI9XuQU9(f6PR{Kl6ogs|D>Eht$M?M98>vL}>;Pm_UP%ljo4%=`B+F zDtNm`ccz`>*TKTF)aVcOfXguX@uJ~)PI|g0Aq1C9BckW~EG>Cx)Qe2X(~eI&rk#V8 zT`^78QwQYO<}&D3E*0%r!k=sZg~pl*Oey)|=c z6ejTmBGvyQRGPNGq+SKTl|Uh$V?gpf0sTP&XU6G7JEd3H)?lVw6pg>@v6c0V1I}-= zAfQl2BCsh-ZcPG;0R>6-zTWYreO~}sVj@=jU zm+{YBH^y0KIEW&zkM)8UCQn{AzWHX-_eQ53aFmwOJWmWP?FHh+tmkG%^6;9;1GJwH zmjf3in;w$#uZ8E8G$RR9L~)?4PhgsmlneU&YdJ(veX8L8r|TqN!Xc={L(-mL-I0Er zUoP(A9aUgkoP^;pwg8$`=}xlEvQ7@RxjBYiz-yzQY#y%?xw&ue*|eaxl7r@C3(o~c!$w`C8*VlOqF{*B-P$Fx8)!3SOYPULvQjUs}|7IFY))R(@1uFg+f ztQzdyksL8?N5KSMW!TX$gAW}MKGFT@*96n?$?_zU{`CJ6LBVtS_qqUP-uF?tJ)(^y z(D7fw#7 z1;#j5N1F@FJqhvXVeGNvjUGndaNC`buFHRcQRl0uy`r7TTVo`;k%BCGdi(FTt@Jg0 zsVgt(wVs^o zyOvb!>A}=KvO87(nL~OY|LaKqK!w1WZsNB$G4uROp4{_0lHdeWJCDk^Z{gYx1j z?{KX!07|7tU0Ff>{7pL>dL*TQ_-3d!%WY`JOx4dB$m5p?o2@D1a@y9hNM>o~ItLLx zoMenx@F4VEPdh1PoE$Brg4Ph8d-ayVyN#aX+XH;l&%UHI(y|B@m52&L)*tN@;!cqi zI?&;4PyL@4$$U=nPgQ-~i0JpF;U+Ra>=ie+ZL^WqZbk>@q@>zQ?$@T@HktctDZ`ZtU*bOU_oU3>r=`WnC$NrJTjzoD#oOXDK|9GNDo6i& z%cf1-@%DB~ZkQPSO4& zM3{hcmmE?B_^D70)dFfG^4?Z}`g>zYHN*ZU&GQxjL=;Xuvr;e>4T|;bG${1fA+eK9-iCs zcduh^duJ@}&yUzWfD;5LuQ8zATN1MGB>18S?%I^Hvpv5fadI@>pTK450t<;8sxqLb$;lW)q^fNR~;} zY@L&vEZ&zW5jS35#(b4!2VSzZ`KFB@!Plk&T~Qq0f(5FnM=elpe4?VgqW{ZRD1707 z==Cp7i6bEk$t(aJ7NqwM7eDFA4Y3;s5xOPQGw42!CL;hxVMi+2_rFMym}u1di0ooX zEZ^us2(jxz3mQAMKZSuXR)Gr5|N1HRZ|x5cYTg4N7s+7+?mMv)fU6Bv0b$qcni=Aa zfZ2gfI!<5^C38o<+Uxp*uR?w769Fo)8+m+w=KnrP;x|=)w}F}p$&U0YV{x3kb}x;a z6asJZaQg}V^JXRtGg%})1=Wg#WAi@#?Ft!eOGDT=e)r$U%D%A4R^&6J#S-)Q2@nJ! zi&3C@8;pe9Spn>(e>W@rn&AOff$FBu>)onbKlz(Csa z#>U1PJ@v1TbV&ccill4kt8(85ukQTUtJJyRQVj;HsGrB^Js6I9(8iB3a4{vY#8+|X zxp<0y1F57P)(8r78?smu23t@iG-&H!GkM%>j)n+e{^9V;eDT+>6TyjxiXd&9sH5hF zvnzsID4bc)8lm0h&}nW?&OINk=E{Y8wy8CM_x_|2m-hw1ZQI93Rv5*s3fdJa3dmZ` z8i_&nc<=8^7|4q)|1UonL?wYs9TYxPqc~?zK|%e<7>J3l z!v8PV9Iq$~Z1i#}!lN6({n%S_FJ3)|N$UIj E9}@0s5C8xG literal 0 HcmV?d00001 diff --git a/packages/zoe/docs/user-seat-exit-flow.puml b/packages/zoe/docs/user-seat-exit-flow.puml new file mode 100644 index 00000000000..e72b1fc419c --- /dev/null +++ b/packages/zoe/docs/user-seat-exit-flow.puml @@ -0,0 +1,27 @@ +@startuml UserSeat.exit() flow + +package ZoeService <> { + object UserSeat + UserSeat : exit() + UserSeat : ... + + object ZoeSeatAdmin + ZoeSeatAdmin : exit() + ZoeSeatAdmin : replaceAllocation() +} + +package ZCF <> { + object ZCFSeat + ZCFSeat : exit() + ZCFSeat : ... + + object ZCFSeatAdmin + ZCFSeatAdmin : commit() + + object ExitObj + ExitObj : exit() +} + +UserSeat --|> ExitObj : (1) exit +ExitObj --|> ZoeSeatAdmin: (2) exit +@enduml \ No newline at end of file diff --git a/packages/zoe/docs/zcf-reallocate-flow.png b/packages/zoe/docs/zcf-reallocate-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..29525f493f4a04ccee17f5f16ed4f392629f5042 GIT binary patch literal 24569 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 { + object UserSeat + UserSeat : exit() + UserSeat : ... + + object ZoeSeatAdmin + ZoeSeatAdmin : exit() + ZoeSeatAdmin : replaceAllocation() +} + +package ZCF <> { + object ZCFSeat + ZCFSeat : exit() + ZCFSeat : ... + + object ZCFSeatAdmin + ZCFSeatAdmin : commit() + + object SeatStaging + SeatStaging : getSeat() + SeatStaging : getStagedAllocation() +} + +SeatStaging --|> ZCFSeat : 1) getSeat +ZCFSeat --|> ZCFSeatAdmin : 2) looked up in internal map +ZCFSeatAdmin --|> ZoeSeatAdmin : 3) commit() +ZoeSeatAdmin --|> ZoeSeatAdmin : 4) replaceAllocation() +@enduml \ No newline at end of file diff --git a/packages/zoe/docs/zcf-seat-exit-flow.png b/packages/zoe/docs/zcf-seat-exit-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..d87d2b6dbe2a8ba8a79ad3077c90b9763cd256ae GIT binary patch literal 13116 zcmb_@1ymGV*RH;Zbc1vVqI5}vAdMg>DAI#;Gl0~PBHf}OEg~Ty3?V&$beD8UGaw-` zblwB_zTfxVd;him|E}v=$Xak_pR@Pb`8Y8g*i_ipu3f`>q$sO??b`JL@I{OH z7x>AjHgk1vV<2~TX0V6+EN9h~27i|t0@408s36oU1 z4cz@%?`az?pfCM-ARkWt~1YPaNB>XRlsZDQF0W|9a|(UeCo# zacsipFLe$2H9+~J{I|Q5x#+x}@!?_t_lDCOGo6&iV@*`#RCgk+t{Tu4cHF9iEdD2=(r0sDsTPbfW!+pUaw`(q%I*`M%<42oDf_)8iL>vq#XAHtt- z&e8$`mh$s+-}ZN-N{l0v7RUSlxjb25W3e;Nl~8(Nwj-8(d?4P9#=JW$vP%XtI%srM zYu)m(c%*fgnCtIJE$QIOg!|_B-PXi<#*jVc+sGjEJlH1~))slw7!@5|t?fkF!NEaO zQxj^b*P5Lu>LH4Fjyt%-d9uPmO-=1BR9aeENh!39cngysKJBnNoZnsPFx!Bh+4F1Q zOWo<-gyle%N+SQSPVPF{b4aZx<)VXwL!v9KxZ9?W3dt*`o?%|&7Fl_Dc?E@Zq4)3J zeTxlN_yjwRRF301J$!rYEoeLLU#hLGO>*}!i&o($v-P>=u%a?4znh8MswtwDLJzw_ zjUXq2`lVK6fi)o^A>&F-OnnG%1s9jSv(w}GjeZ2|)y~UkM!9$I-xmpTaNu$qPEp=B zd%2MG@#EQil%)4gzd|-_=_4$NfW826I$C5rjz?G`jY`Bw9D#>~h7!w+$c5dSU95by zw7t|D6-TjWm4{^L5_^SwypGL?#t#R!I*l6~9ON_Y_;f7lvc{rhWMt$Y{N$rJ%;%?M z8Z0vqDL}vvKmU=hE3~7psMrRqE!%!_h*BdaBz&8a;ssU_Avv`QU$)b*X%>9VmwJrb zZB1tZku?4FBh$#O+Hp}Sb8{75cPoIL8}fYUl)eFRFm^x4`}D}tnvg{1@}%^SR8_fN ze5DXA(1b%)wq=6wu}}rN<*NDx->dP|uhwi7YH`?(f0Kf1WIvU<6^!4?PJU=;Fg=EWk1DYbRxn7`_s@ zwq}8qWQgt6!5qo8Y)n74W`unk^&5X;M18gUq+I-dTXI5<*b5~h1+(4^*kS;Zd4a0| z!IRkRs2D0-?#0~A&JB+38a;?eGUtvA3sY89d@b`#UH!+vKq>uW^eb(C8;v5gwU5tH zsn96;>DTD1rAm6$W&4}6r9yUVB@j~%f0$-KcW7i}pv?UId|LlSj7r91gG0EDBOD({ z4e!p3QaCa<>CXB_Dwf(#Ra9`JCVQVf5s*5JWv{+T;LYoE_8pr{DURC!8wc&Cc+~c1 zIlrM;xWTlLr`rYywHQun;HHH(V?N$`oHiY<)?3*9FQFnd=<791Y<5Qu>zMw|#b740g^}$Dv4h^SkO|@9m zGd3qH(__omrfT>NYG0=-j2I4ukWl-adzCmJyL>im)JgT^YpK$UcO8poB_bkv%Pn6E z63C-RkH!hI-hiv0;)NfkGElqO+u&*w&*g90D`brGz=L;o^a&)iYE3&^#>Ozb&km_+ z4%uJ$>uUPJB+m|f3MMPImF#H;~g46j4T*I0AB3_e6!Q~Cl zX}4OaAIx+#HSKBzzH(SFPPW#^L(=ko?v11kBl@Ch+tGD`^{%3|>5y3>^>lAI%x3RxG4 zrTYP{xUgU#2iDe$_%BR?(2r3vUcV>%=a_G8@{QaxRcV}9xI1xpP%WcdK6?Hty`2pYF0?@ou#^I=j${_Nw|-=v-86T z4-#PxgJ(ypD`DiuLjm(GHNLm~oK3|pRSpN<)jMF@{LPZ7%ue4#|F>s+22+9Fm`0^z z<(H3EAe^Ptie1(@Xb$Io=ilXbrH=WoTbK)-R#lEuZSt@l_IxTEb}K?QfI#v@Y*4S< zu5@Nbnja1|W<=>o5|tG&eYP2U%Uz2(Kx$^PMkib-#x=mE!A4H{d?s39<#g2`uPK+$ zR7qz<;hXjT!Swl5Yt^LDGPnsF<|doofmE+D5w34V&Z`>1N4wUKQ$#1L#7Ejnk8J`* z#H5>6;jWFXt)+G9Z%ps(o$h1kXqK5mZXDsaesO#3I`cg>3Mp$d%EJwvLWsF~+qGAc zrwPLO`d(mkvkTnigCj!4h|wN z>k8YN0Un*-$r(sTL*1w<-Gvf=!1_x}>*n$1uP+~jn48)|1OdA+l)C!ggu=J#nF@h!1ut-8YUd^PeU9zU6m{ZLjT<(Jqijv5`x z^I&_yaXk5oGYv|UCtWWr+pl9ZcO+nK_JK{qvng1z^+vvqfY;&T9*75i4>^=lL}gbV zCF?`_x2y=iMcs0Ir{zumiZ%C>tsn=dT>9hRns&-rH-U(9S zWN(5>Em)>duTp)f$F`X_qO(BqEVyKnx)B(`dv}O*t!1o)cjacPXE9L*!wNYYDZlMF zW-^*^FxfY{-M}nts|D=R-=?#Ef}s=4&*Q0nz@0qNPLvQ{HC^qu3c#J^uSKbx8SR^Z^AE=a2i!L|it*?;h_LXAeaUbiR5+eEnDs~jHZ`ODyD({7_NNC; zBzT7On9`(dUkbK2rNk|6-76#3uM%f&xtlF%-t$x0`9{l&3Qv+wq!66);}>K4CRzx~ zO&LuJ$^Z+(d4a{s%p*IrVoolBr}VnXlIIVUdrq-A=tXx*vp>a?$8FB(+n*v}=Y*Xr zJdYIOd2mfGnj0h^w4j%nkx}CNU3=fJFaosD} z^nKFEo!$Wh_Z(W{QQ$kW&7r0aSZ{poO%|TVYaY^m;)S>E8;yKE_5$uG+}Z?Jud`&nszh=@W(#cVQnPb>7Yn123>D zD3VlFeg+!6zb>+~K7j?*?8@7T!1k=Gq++=pI0nBb(7oZY>9 zQ3nKk4bbAa*1vJN8pb1-5EM80E}G9@-PRmB-z)}eJd&gX-VC~`4H?c93I;wRHX45k zy#4BCQxjcRhvmY^qss4_@!M-rH{QYGwJl__3GnR#yXv(( zGzP-Vdt07=)p~)#_|S7v06^bSG(YkFQjg2}o5qx-LQO=hj_^80&UM22`%xhLnQ907 zOU$c>^2{@`?DEvoi6ML74!$({5qvPH_1p$oyO2T0Z827LZo-)h_tFk%-ANu4!e6hn zglq(V`?hY{8JH|l!z}JO`bmmAxB)oN?ymt}K1Zt(vow&-dy+%2aJzyWS%jbToZO~B zZ*AcsGN>5TXB7^`zw>fU~nPhq)JXkv>P=dBu@$pomUlPPYVm{ja%{izO9*1 zv|S838NBcydU|T0v7s;!*LWq4OK(&y`)S{7qcx|9&uv!hRG-@Jj7LyB;QDqN)3y~3 z#s$~mPFs%%s@R^?y5s2wdVd_cB!0vu zDhiKgxTP@mZK<`nRB(NL01s*DLRUy+r*4MgHvez%V+6iX0v+O-l?qjCk-Olrt614I04T!s* zo;Dy_ci51ABg;cQY`qKr_2)A(ZCfjxC(7hLBqs%ac>-mH zAlg6y=DAyd#qdmF$I3j`+;REcrkz5($W`^p;(<4N{&N@m)AX2!A2_ME%j=w5I%gX_ z$XV4ZbV@@(PcNr4IQBZs=VS{fObH(Ob^GDfB@Fy=YYt>-{MuI0RVTCEO7F1ZL+2Ye z=QB{rHkksW(s)QBG+MI}urUiJDg8wh)13Hz!H_zi33KqPI0;9KKQ%gl)K#MRu&Ht>P~w5 z_H}JkXlMiFZl-p9tsy~C>gYsF4C%VLJ*)biCf$l^CJV^iBZMVn$KBIYyow$zLBkCF zIPgJ}w-K~ART6$7F%(F2b@g{L;N#uQC%Bkm2M`pMpXPb}>v!h`OWt@j(iuq>mH+zn zYrJ3|@raouQBxgAPL`xV%*ZN%fBfvDLRV)^NXYp&$+~qWjHJIl^r8Ni z?jKJQC%NJeE0yDFt~Zam!VLQ7wiQx%;AL&j!e?vIpu+)!x(>1aX*LOR=vF0uYwCj^ zmtcf!SSP)617e(UWcHv$;cD`?a5zj?jn~v&5T)_iQ@p|`%F$3(VTkKjBmE?Pw=zEf zC@N?Z<17Hq<{-w;-G2e9xmZ9xUmzUoeF)hLgkvHN7IG6H9OVE!!~-X=3qmqe=^0On zyh5%q_>W!#XMv$5f8mEevX213qRVTb#FQUW0Ier+AAo*E` z0zLenX$8R0|G8Px1vhqAM>MQ|H})|Tr)o+ENyfWqN7|J6b7eJ~3E*5=mMFIib^c*O8a zbzeeo&C;IZJ|9{fMxxi(*FVd8tQNQSvzmM;OEsm+y!WF<-V4LSB~Y#3ze1(GTI$JE zjG00IdjplSU(f`^GTgeb3SavbsHNvb^7^Ss>MrFhq^x#pDy zF%j3><6tfW4Fc|a1D{jpOb{`e23{K4a*zMI2r8l{qf`P`{0?)nYHy0x18~Scr>Cb4 z{;YNlyJyUB_wHR5mSr%wcX-t2>y~d$>6BX8jukWCzmEbssj8~V!?d)t68KEj)zul9 znEE4ky&!$%&}ra5;0m-YBjT@L1T0fm*mZ7M7&wsFer<0am96CR6Dn!!cc%7$+ z0rV*bK-aY0NFXT=W+*i}a&pZ~C9*-i zVv~>`R9m8#9&|`uhDCB0afnRXho8^`T#Q!CrK_iBxS&G8-Teq4%-=G7(EYtv23f_r ziF+@vZV)Bd70)YEij=s;r57I^oyN&ri+N#@Sv}MLI^X<1MdrUJtpA1pvxV)&PBBkR zkTqpbUbAZ!SVXOP^dXwUXR?P*lK66bL1Jqn2#$ytq8r=)1Ph@gLZ*nh+I@X;4W!lJ zC-=>JXcFVvf_UI5XqsGZsUy<|ooMk$X0ruu0=-Ir&f_^;3knK?PLy#r4H=<5F)HW1$ZseV;^kG- z2i3r=iJ&_vC8fsa{OrJkA38-t2_RENN5|r_T(q_EQXD7&F79MBDqsCXugoT)?48=# z@$~uG-UQU8_N$Twgpt|X0wMr-8lz8D7X6HBj;BYvuG3!SAUlAlYgiWG;8;U;Cm9V5 zv6adgj7^jU9k(pUmc1W0on)iH5#MXQ(5I}$YqS6txlgATdp{bzGv2K~8 zva+&1us*vLNly|H1I?+A`Xv=e|fU<{wvdG zT4>S#*)2iR$J>Hv?nPop=~;@$J|Ry>OE1XMfk8pWX|f^27TDJJ`S`HOxp0@*1Y)c4 zKGXIU=vRZWE8R;>cZ=l0CTBKNc0a3TOM?9UlSN+1$E=N&C}i7?6zIE!)MvTD4mM|j ze;M5d6WW}zax9xhgEFa+2p3m@=i&Beqzu7>JMt+mD>$hy;E>a{Xr@9FcX!d2LY9#5 z@TPHQ4z1V3xFVox zY!a)IM)k}zMM<840q%5CP$bx)fR9S$(W75uC0{HE(?j4Y?F_bI&xhpUwiZU_~QI2zqNp-1C43($d-*7IzHH z4j?-%`IbSb+WUkA*5B0O^paDmUe>6oS})y678Z@%m||xY71Qb3^D~dd2$!hNeTCwgdMv24xeeY!icwzei8L1m}`vS`+mR7kapEl0V{ z7;$kR4ZY8a1A+qtToSaF^46{4QtOdSk-_TcSxoyJxI*XPoL=*1Ac2(_=4YJ z=mz?QO;jIlFRu2d2W_9M15bM3v4@L`OS((%HscF-{N7y4!V0*(YM1rl0)6W*5*^fn zHo8vvWecR!57{M}=&5q7~>WjXl`CL9NP=g`hs_)Nq#ld}^CR zmhG1~{-bX0Xm|Debi-%J^*LE_ad9NQ@=ZJUGrRgM-k zDz`7PUIZfojYU_-GtBq{9*~=fe|38-^>7;_nP^z=)M9|0n=rbPIyZ+~9>yQgT`ZvI z*`UqSt#YwDBc}Je@Oo?_M6qsIExA0K8bye2q%C$|cAg#w71j075&-B*0 z&kG|Ch%Y3h^1}J#8VyvD4?VFi?9>~t`B>QnrQTwo`cnSyAMLC5JV!Xxw21Oqa&J*y zoVwOzibPrHYZoe>={)<*0q%vU8f14>a&*=FClMyo`QHNd`I`gov6RxMbu*Je31V$q z_TrjX%bK+fPr2_>M#}1}N!beid<}JaJ74)39FOAuMzqNNXRcpOged@PIA}^@{5QUnZqr1eLJPs4@_yf9RS9LP2dZL3UA5Dy!hAs36)NFpH@a zuy1N%Xip9(xWAGEc611W&Ck_#y^?(LV5q4oF@-_`0opCOjt_WPn3!4sIB-VS-&!l_ z2C?O~*unK5Y|D2puPwQF=gL#C&l$wc#Kh$JbJDff{WO7&;|o=P>C!r(%)~_*`5bfA z)k{~<*fmwlr*ttIOT9Auuhg3~gvh=a_9mM4Ima4LeQ->>Dq3c=|2y+=8=dAs6@KSE zRhyI3{qAAsHQC0%Qi(&8@NGW8uVi2U7rz4aJkiX_nfThd9#q&~%=--FJ_gcoq&;g2 z7-fO7g+o#e2*3}rGXsM1;Aquy*ke_(qAY4K?aF#W37WF*M+F%49Mi?7^J{BpZilPz zJl#?&oeagnY7sO?YcF?mL+X=I37cXARAG#f|iHt`nQht!pYFEmhUdsFOpYPm9-An#Xp&mgi zcCD6BMO#xQocPW9#dy#QJ8C#6^@w%qZk)??IBw$DCEc%c0G<(0_ z@;SDRa5J87aTaxQoBQson4R&^_aqe78N(V9H(CRzNAlgyIBo&g-xd|r>jt$a>Ddgw z1z%o2q7d1R%~Zbk841W#Ih@KsNRqH$K7O$&WC!O?a8$_kwVssJFpWwxUC#y9XMw+` z>ly;QGNvAdon6Mtr1?~L(o(u<@{g4`2M2@DH~@V%Gq8xjNU%rplS-h|eBm^_d}P)a z3^*W5wRW$_Qmec~%!>4X<3{D&o{eQ!G~zt_&MiEl7JL)DaR?iL4604rzSf)3R*pO^ zYCc3wmn=-ezi+3>7=&Sm!h-PEnWxaBx{~$@iju9DOGECDt}y5#o9{Ij)|mIY0DL*v zQFp9GA<#UwygE0E{~zBYFV54eoCKtOrPuK%mikf=!3=SXji1$MJzBgkn>4;)>b2)m zYgfCxh-M4`71tzhJc#~`HexVems7anX@U7JASV5|LFIUptyN@G>6ppW78;(6jbiio z%nK)6{TJWzMfIn1XelgK!4|ZG{B^MAdq*dz^9OXY7+$FEpZ9F21-lwsrE;6|?RCdq z_?R#!Z!oDWcavQtW)-B@>8>4x&0Li_Ykllp!5z3yJ~6jL&3OY(Z}1YdVx`rg zq=bN!jWlEOPJ7#EgzW8901vhcOw6qhBTI!x6o&s8%r)K>@Tfw!?0L`rrct>e;msQB z)X<#@C(EVs=^ilqUi-2I(03lv=2%v7xAk#|!$nJIF_cR%FyJbcf=4yziA-p9%nvNb zKqpgttXF5&-KCxx8b#5sU{j-!m&IK-NoSTIuxhLKl-k|Ul|umCQn;`J3O!uX z(=C73GAHM*Qd2|1ecq1Ey_N_spTm6J@P3}7GA>tz%ZS)u$iU1!U5wN4IU)MOrBD?E zXJIi+v30JS1`o{&I4HunYx$VEn6L2aq5B zDTBsHQwq#hu(-BgDst&hT~%1>Q7k=z{)MaS;M9YwFX(V{OiU44a8c)5x{}z|ZDrw2 zA}RY^DN=j*Ka$u%n}F55>U&|Hf2y-I%AH|nyou6egQ4cKl*czAS|jE1?!h*TE*9D= zV8Du9|7h?C1#n#w(&rC3464gCSL> zvc`G!do+{Uhh&jEMQV$^8vjX3J%0GxIBn!^&&vwG$3MjWWb-uGmwyN;qqCUhWmp9D z2+=F1owYH$-4#ku53Rt{kT}r!H^|d_7M^S`zK278`l0tXu*02*-wz&W{mD_~MR0+U^f^Ls*UYu(pIKN?xkbISM!wAqi0@<7~QAtjn#4?N~uYX3stzQ^npWU4m=20*nzpY_s$ zx#@wUOKbh7G{_Cr`A?tzDz(zE9LyGka2ZHc0vmz17T^sRIafI=`)84xb#38BToxhK10a5OeRIYR59R{yi9;$2+ z_}cfl{@NZ~?msPqo_AFxUU^Uo{=3hA`i^+S+ddDzwmSnR!Df%DZH68M;yqjf$3 zZaMtRCY>EI=qoT>8Z5E@;}g*%h>OID4EWWg92c*7s`0{<4h^HR=j7(a_5~{p(15>h z0QbeKveJS((4GvF^IOd4o-IPs_q+&b@zpytIU& z7(5-4{+c*U`0H17C@HHrpzpzB3#sT^)E9se{~pGM7idNDb<3u{xp({pwqA8<$_2DM zm&VF(Nd4g6lF;9#{?mHunL-Zm*Kz4i=5n4TeKp4-v>k z@k1|oxdY1w%CFsNL|-9U!uf}oH-9Dy+~*~w75zpI;nEeI>no)diax(GU6dZ_O~zG(rV4G)pB_0A9qA(6JS~=Glsbp=neRcdvDu!M zApL@KHR!*;-ieZ&``TR%`s460IVOtmI=}_;bLv7xHBma{>1V?06GB0-^ zXM%O*k`;}J;gTCKZ67{-2wHdW0unq=Tl|t*3m>GHJnJThxZRv@)r9==_swTESCR>5 zDYw-%X|McIZpSI^skJdh+f>%~a;*6F#go4za;7=w7hNPaGVFvtUhg5mtkhX)r;||p-CHbC*x17x4dlt2tzvD- ze^lB}*smR6HD}XWb)?7n6Veo`45&X2FK!HxB)IsZ@5ta2Lrw~|M3pS$s)upNo{W(2*g^j2D z{cq4L14L6;ruGj0YuVfSid5=x@7{e@$>ZiX?r8}za#={}j6D!>xr@q#g^WFlapBUOTN+L1iUVk}!c?VR mNUE_ppTU3eHwTA4PS?Kr)As6zfxlz9_DD`uwm|yjoBsvd4j=pg literal 0 HcmV?d00001 diff --git a/packages/zoe/docs/zcf-seat-exit-flow.puml b/packages/zoe/docs/zcf-seat-exit-flow.puml new file mode 100644 index 00000000000..fa27223b434 --- /dev/null +++ b/packages/zoe/docs/zcf-seat-exit-flow.puml @@ -0,0 +1,26 @@ +@startuml ZCFSeat.exit() flow + +package ZoeService <> { + object UserSeat + UserSeat : exit() + UserSeat : ... + + object ZoeSeatAdmin + ZoeSeatAdmin : exit() + ZoeSeatAdmin : replaceAllocation() +} + +package ZCF <> { + object ZCFSeat + ZCFSeat : exit() + ZCFSeat : ... + + object ZCFSeatAdmin + ZCFSeatAdmin : commit() + + object ExitObj + ExitObj : exit() +} + +ZCFSeat --|> ZoeSeatAdmin : (1) exit +@enduml \ No newline at end of file diff --git a/packages/zoe/docs/zoe-zcf.png b/packages/zoe/docs/zoe-zcf.png index 8bf09c5b4cc188da3327dc45314419e6f4e398a8..ff932598f33f155635075c7b78308f4ead0ac1c6 100644 GIT binary patch literal 466090 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 z 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%0A|~IC2yzr>aZ7tl{>@5`?%xjU^HY_ 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%0ASKMG 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

h-|v@Rmc_%wm1w_ z>$3uD{^~(Z=qKt$uAf^2GQbd6B?1AoQ4O%_gyuY!olofHHq%d(iWBlN`@yam6ht>c z0nRpFt{h5AO7w5+8{>sSikSJ<6=q?^AP~z<%GL*~&2t|!ZWMRI0Z7sty+5wC@hPo& zcg(oJ@wnsVhx0Nohg{_#WUe@UJk6-L9Mi|ersw8Z>|vy^^;l#hhEnX1{ZbM!7{P(X z7%1iDnB-;cwAPA_jBJ2n%2b)Qn8~`&6vJ54ob$(0FFZL&;~pV;=7=*77Z4fMR4Zh; zHS?%fMHBFC)qfY7RT$iwio)_t^3Q$CYlOkI4mq81&)2AQaZD)Q2x1f%BC_}jU?Sx5 z9j4PQ>d{9DK4)F8N6Rb)f6uY|i(2Z&X}C!jwBr+2oF$t+09!{@D0e?x*>rZ%Nz{0|k-EjhAD;QRKP%qG9H0&v;HNLf~-QBn*#zs>>AbWy+ z71@h{bm-;xv#@iP{hOTwDyG+KvdOg?#igaWjeMV$ns-FhxsJ=9dYiOO+frmU^}z6X zYJuFqP7rv`i|$z*f&&t%93+0^G;G=Nj zV?peFJ257VZ|BDU3Xb4owt7`Y3bmbxUhr0tC|3~S6CyTAAFY4dg;|=0(B|dHMtb+R zcg>NN5wR3{GZsI87c-w&%m{P>WjXxEr&B00c>-?|V%jU=^pSBwUCgs z2@z1IP1AWxm?k-D-H2^CLP5}R#c&1M!1_G}mY$6RGdl!gS6yj4Bg05#MfSV&>Roy_ zm)Z;coYpvaD!0A)VHxYo_G?ss4Yx?+iuz}7QG!Mn(5^A-Dd>R{%gqKXpUAjPVwAG&*_5%4P6yr3+Sb;p;bK zoNO3F^c$;?aW2ux{QvF^QdSe_gfNrg@pa~_MkXgzun^5PxA|ZRx0vJv{r|l^1!vFi&agT7MuGr1if7} zN8~yFRYD?;4d;)^9hyeYpf2+|{#!--eYR!c6gJ_0&@yr4`Spm!+Z*r!zSG;)uvX;Vet`K8pr1}$9|E}oi|)} zc^a}m*Bxy2X7!yz?asKVd<;G7-TWj%apQ>0S?WFne+0aJLjejv94Ws_cqj-vGVh|h zdVrP>r>hqcp?c5qyU>es6bmX(wDg!xp}_rC=hB{AtMtw(xUb<1n%tGDuZMh)g*SP% z#@z7nA{0*!GTAnelXJojag_{ol>QEh{A^b2onMJ+5 zs63{VB+N5WI;Uw>&a#CpwDTSwCZS>`V(@)UYxm{lGO(=;(_+tNa6^%F$9oITO$qPD zB3LU&UAA#c+8Br}Oay_Yr3Juz*piPy_?2+NHwhe?2Pr$L2BM<4qvowU>WQ@D*)Aa= zaaBu7JqEnDEn|mUEiCe9ugH9zqe@QH5RGjVhXpVFA>ohTG))*V-+8isg_Lc6@Ln*c z0U`a1`$dSP_4RL1QJ+xnHGH^lL+Wu$lX$vZO*6H=1QDy=^wuN9sOa}uS-7FBrl+z^Exj+lw5EbwaJf4n@3=k%eJJ3&YZ5}g!fu(=ygYE%F3LTesYu@d)&6hN! z{8y?Kls)&HhS3ZTc>l?5hxQXMyl=x$1*#v=C4=L{bpe&8>RN`6COkcJ2X8>1pX#0$DR%qT)sr|Z(1w+ac` z1G!DBpJ1Y&DH5UP5}udR*E)wjvn*H-KJ#a+{rK3V!@8P@d;*yE(CzeIzdnf^N1 zKR^EcLI-8vIE)<{hiwXei za4p+U*DCiI+#-mf37F4@LRjcYf@PHrF34V+GUgL5MILmEwRQ@V&gLYT# zN1<`?@ccZY4c#GzMrmMB@QJyeBvo8p%@fa&syT57!0Msv^4;VOV;+__d{p{TKp_vB z7Q|@4oOxzBs6KW@ah=N&GgzA3!r*ob!FcT5g=w=m-OtJ!M9+!kq_(yf5eba_S1_gP zqlocppw_QA7o%AftJSwv9D7M(b#D) zvzDT#1|#GFKaw$s-iM3SsRS_Oe0VjHdShMO!8zebVk^QDo_y>EcW zC8W4RCiq6G&yMdwYEumn60mujw&qL411^gsHpy>zX%ymR>gany7J3DQ$s}2fMmD05q0d{$N6LqP5NG&_MEmS^D zh#gCDyK+>8@)uCVZA42PMAr$|ApYxI6Xk=x(Ky9XcWmW(s~bDdO~C#?Tj%91gj~X~ z9AZ8Z{YMJH>-GDlyw86Pt)O5m_cK&ZTfcm%T`2E3Ol$#4w{iItS(QX6>zE;{sH0XY zdTdcitv~*`gQ!;UrC$KZZ-{a8jJ=L@`bIhKYV*K;ke7>#i^1Qlze8YA&VZUYjIp{l(h(ErS~^ll_}L z=U04InHP_u4iPbwk&s|FvXC(++}hk}2fA>Nnxm>N@vigJ z+Jsx9Hia^Y?=Xz6nnEEczSasRg#MIUNw9)$Uc@!jgtiY?3aQ6SP}737*qzW;enQm= z6O&ih1s&}XUL?ofg;#qfmPV}H3oyDbVuO;Q#2jinQL)KY!99id`Iw@FBz4*60B{(U z4Fj*j?>$&z^QAoANMLQ0-_m4rYRS{O90V!oyz#9BLt>5~^`pA%jF9+~k06c;7Wj#Oh{wDWQuCNuM-_BhubMcBg*)Q4YIWkQ7I+Nl&m zD_8u^uU=hiXHXDWaKr~4c5$j0v%dNn+8H&W%}&O6XijC%fZZ;BzONb$L7CS|F#R+~ zA}}UEXDyQc!Lbuh2z{^g$$Dpl-6oqIk!W$hw^$@2l*`7hS|A5-ED&=sw9*gR(6CM? zx|@dUr~-f+%gNtvMxi-wo)G&D@yt~D!m)w87-D#1nzMKLmKp%?WnT01#Y8B|=fY1T ziXiXOM{-bD-{F`>tS~U0)7ROpK`jYhkpXNZA>tF70yO4{$;qBl{+t(;5n1K9iVByr z9VE0=E%q-}MAR4)jLNrnmZ|oY?I%SYMbAZGc1B`G&UYDB1d?yugM_m!041;qcV54` z)m?)teqt~jaqA0$RSDe%>$7i5( ziNZHRO1o<*&5wj7|( z8&l_Ed8>$!5lQ3U#CY=Q+&5D5wjA*6_Pr*d{*g|Me-d z{V=A&WfAkE!pxB5f{*F$$*}x_D<=+(PAF71Uy{0$hE?POv20gm714F>8+LE-rTUCSBR&@ z8(W1SCFSmW3S1JzbaVUdO@*>=+^>q9_X z#+}|LC7}viEMs%Gr==I@2Ff!Wo{^13p!ap-K;krtQ&@NR19;N31Bpi!pek_Av&a^Y z=U}cyZ+@Oi(o%DB6e%$$h!uKMO_XGqKmwDyBEsYRygMoL+Bj6RULo>nG02auI5w{` zt#R+(H5NjC`j(0|9*@PH&9oL$M}+iqpFMdZo^S82aC!SO#6MRu%Vvuapyr<1QS+^O z=8PPS)chY8k0v3?1v>Xs^@Twk&%4Fqobw~&ERRlgKjt!HwsS9+=6mk0TtwTa0~WTT zQDQL}aT4{!HO@;!pMvV~aof3XS*l!mOv9^qEu;Dw2O${u3+@jPd9SENOY}RtiZ>RE z+6i`r_7}wNY&ESy6CBni}YXNURf&XgfSeeN*W4?ge=SzJ`5jO2+!a|KTp|g}e zd;NF#matR)pe4F7M?ZTe808&K-%HS?&Z43`*ODFv7M6li(2Ba64G!h;HY^)?}*Tw`>gW9 zX<=A;9{tx=&$jv+DRpe14N`j2WYzAiA<)gXYhbmkvDlx0$LSt=jNs}oYKre?Dh2UC|%hy_fm7C_EO?F>jrTB%`1 zlXthk$b7Dojkbq6_o*Fs?_%oxxt)Dp%<)(59Q}}q#%byVQ0wQ;@v=EL zpCnmWeXN4{|KBJ`Y~(wmpe!idKn{t*AdJ*P`%PdxhW$&XFvJibCkk_;0SvS52x;4* zGxsgbVdXmr>1|Gad(=^K$!tb>si`owB~8wS`$XF$P)j6|yu@VHAsh7`%wx-JXA-Yv zEUgE}QTeKDuwQx9^eTxH=^~DIXb8+VK6Pe9m0K6N$FiT&uF|{!Fj6_3>)i7NQUOH4 zT*3c7J~MO>z#{f(JFF+aW#>d;o9Oh_&fDY8y3@qZ?hsI6)REh?MXpCDy5clB&1vDk_LMeC zE)4EWLIUv$7-Xa{r=l&oX7Oa8@9~pdprzY7>W(ASmwX$I#<+>ti2Ncl)V%Zrk_2Md z?U(?j!2ESN1rQk`UsYu5}0A&=J6$5TMfa5QQhe3`(cK0~B(G0a2p?A1qB0Y=> z0+nx^%g0gBo$~l-sKjylGQ5+FD^}9ZWeOpNieH~MERjtsVZH&EXW z90lZbgV-Lno$t&;H|4uXwhu__m#0BPwdE(`+gT;fv)hQT{!c-)CB=C$)QrB<_BY9OYnN}CQTsTPb5@o%x1^GkNPC{xyX980J}h*{vF25ahKf+wWgTL56yuFHHgZqq zQfE`qvCo3iduG-lYt5OCPlt|h%AAm{&&*#X!Q=7rVFAWLWaE7ZV3Twl2r~BP>@P$O zn#E_#vlwbn7U@AL1L;mBZI(#QM>jjaWG$WRQC9!<*snC+K#RCzZh79+{mGsjuTY(K z>TDQOiOLq7M(x{pj~yrB%$iJNd?5e|OBVQ(YOrV>XGG+aJc~ zl+!Y>5y5p7gXoF8{!kNBofnv>7Y|_@?mn5h`~Ua{5jSJ|Mm%@ZY1;|7{rb`w((`&V z49uT=Rbb+?{lEFM|3I6YIQPhFD%I6mS+PKJ$M1%W)5<^v9l=}8s?JNoI<#WbsFjDDD>}4mX5axc zxIGdxo(788?}+1n^LM?86cr?m%l+9;D;gbc>$mPtw5k5Wo7x%Fl7E=9^l$nbTjRJ$=&Lqo z95em5cR%H#!4Cf0|JH9lbjjB9rr~eo`R!u=@Es;Ne3QPzemp{)5~w(#GCSJvSH}IM z6)N7_>E;Y{^)C80^wo8&gK@me>g$y2O89%X ziTn7MJm~bOR^H*H#fKs1@o%mN$Ac=MY7vfLCiiagLwLb;*FJwdcD#4IaBRV&0Va(* zQ4*)gMg8@$xZP=%{jg|rQ+-w<$LT}A6A{heBpVetqOo|!*ylQO-S5c>Ro|miuX!6w z$lU=Z*JyA2=TJhCk6gJ zpUIUOr3sQ}&ioI_qT|Bf{|3}>yM)$e*Ks4gvDf1P!d`qPqJw-!stPWOXG^;MwO~#dEc^qw>8=I z#%dXXsO@$^Y4Y>o9KSF9VQdKk)sGn>s;NF~tZBS3^cUydGDd;wtDqSNX=i+hPg}5# zaJT=}Tct-DREncqX=**S@>Yy!?=P)F?@~dzb~G-mD|9GZu|@jJKlJ zn^UwLh0lfG3ZLW7Q45i17EVqy6#>UHRyLump7h1QU{+#08ySPIAd2H$CvDNT`22N$ zs}KL1ES!GeBkjoQiLmOHy5P5x@Q;GvzY-0QSclho|@zesVJ67tfKN!@|P4DDg-{0SoJb3KrHS zEL?2(7fV_~VfaFCFREs*Z*AjZVQ6HJC2nYCXsc&$XmI_h%XJfbdmA1W78?saD|-h^ z3ub+5OM<)Hb^@>juxQ&fk0_-R?tt>9T{GCSdBIu)t#FuQ; zIh~}q+C*H2J4AdUT7u=x@Ina!ggNJD3>wU_SMl<#+#$u+M0|T5_Sp z_B7@9C+BZ*OqP^;j$fM@29n*mGBFIZtH0@N(wv+sC3f zR|M6Z3b4YmndHdID4d(i6tU*kQ&W95YwNO;R;felsSmo)rF;`uwx(ZeJXnYAzx;Z-u8k>}%nUkw7yJS%;?& z&is;(SX=eHLcZRAnF7ZoV_VrWRsDA9-sMQ1h?prR7s+=6%8OZ~oBJV&g99SEYFm2`efG0%bBg&5AU;BtAa*sc1qaoY}dLK=JduTAt+pv`ddr<+ep`qi}lVlW?X0JwOJ@f`+vONONQ+uDMfoW{q3a=i5q7n9^K%_k+^;%>}{Ly z?je;iM`5UI3;E@DSZ7CC9dxyvTlib~jAdL})jgJ0KkW&^Y5UL1q)=<(;(z`M-?cej z^2VI+e_m~`2>u`V2b-_yZzNiywird_uN?iSG>|QwKf98OXM;_Hed_pWf4vC3zpQE< zNk;eP&>TGRKhnWvDcGD_R|kj%e99X@sHmk z`#J9M`@{b&t6mG8FcLvxA+IhBlvKIN+wYi5@mQVUi-e zOm2uE|6bW_P88?dUCf;^<>nw<>skD%ttqF6^xm#lJkrmsz0U4KDjs-S_V}XFKghi` zSZDgSIIC0sz5&mHc|G!8oiz!yoEHfJ9hQki7s)BVVE38M1*0fqGqLn`)O#}n7uKV9 zut{nnNQ2+jIf?nJ<(HM78di@+2!tkh0-?s<*!NeRMq;|L6wgPH>c)x2Upx9OHVSxO z%+C`L(0#_kTqB1+TvLT_-;X^XC7Ilk+(Ji-N$yx!(LT6W;EPwuYuqbOeBpaGsdoDj z(bOTmiwwnP>Gv~M8`6({&`a!iGmuQw*r$4Lz~hUjIg$8M)7iMz*G(CDx zFR|-K+xD-Yd}`i+{wGO*Z;CMU#qq^e(Y*%ka&AZb-V&@nGy_oOO{{NY+({M#bb+qxpgS zucQt<7U_wod^4Q6yl#b6`NDUE$MLrTiw$pJ;tyXPF$+(a6<5<+QG4ASS3+s9s?}QZ zck$_%AS8;*jBSqIz~P$6IZSQF@~ZpqMT3m-?5kdnr-tq9{#B`=Y>5=32vWsB3;wt= z)L<{cQLK82{rCL_7O)!@TL*vW{?&e%QHyNayiML4V|MXw@uT#RuC10Hq{4lr)iA{~ zm}_DY{nwKH`i(B>KAmugWVba4JB%u|R+~3#W-4}0FHEyjr+pF@of@6(cJ!NA$Q#sh znD6?jAvP)CdmAoayzhcrLF^U=NOiWCCJhSzweq?Mc zxlWUGGIM{1a^^%fR`lO6<|<6JZT#4|m}eBQcCPIAaAJaIhg4ioy@p(M>R?37dkqBz z1q}@bAGv`N%gpTTp)$Ma#i5E#_+vd%4HqQmbAEdrb26}gy?BUo4lBrKwllTn@L)Gl zERdALLaoMQUq*1>o|rh2Hs1N+#)%(Q2_8GMRhz@EQ&Gl?m8&xAPZ#?NQxW$BHwWzs z@NbW}Zw~P;Rj#5GWrsX=7b0~%woOyxQkeU1Eto++(*a$ z^I3m5$>gVu;~XOVPZ7`+iSi@X-m(}{%lUSG5I*Bl539)J{pY~<_Q$@Tg!IL(y=Lri ztw=9FlRd&dOse@y{e{q}T|;Bz>B-5fL_`r`VOiQ0C6$$xA3r|4b4K(yz-_QGp(=g? zu0bY(48kIyLxw|gn#WWs&1aj{^md}_;oHMF=GSN6hei82AP}eUU6`4enC{=dPfJTH zC|Hx0_Q;*FBl3Cwr)T3+%`usonawd{AwI|3&4t-+c_Ey03#SRvXyB{Ktv`9-b3S?v zyW&(tgcjn^o1ea$t>Yd&J0UyO+|9!*yys7+x06y*Qc_cm4Gpt(s>+eb&uVxLy;pKe zN*sR#Q6wQT8;eDIg<4K1lYxQZji2B0+S&ztd|6r9NGB=+_Li~l{`~Cu`S~wjzAQM& zL`zRk&&(`S)9>x=y}Z0!koYWU8dph1rlr8J5p{BHbJIFqHT`2wOlfJU#}EY8#1NCD zH1Exu-&$L(CmX_7sv}ZbitT3=_HTWViGKU`I&btN?ZdP*I`_{=HVlonW_t@ag9$lA9h^*J^3=`jNGno?d)XK zz~0BfISaaU zR8%6k+}b~i7Dyz<|6r-vvz_=EqN?@my990A&76{sV)LF8Cr?)%?97dklq$4FKP!i$ z+vu&O#auo^-5GGOHsmx?FCX;C!=w6|I;U|9c_J+*r_b#J&Yt#FCXu~X>Cn>XgvLn^*;(?@-)6=y0R&49NHTxR_yGu2?RqPWp z(f3>V6stINoJU_q-8aq%CLI@I=N~`86CkARu|1h${+o84+nVRTS1pvYquP&$Qnucl zbu46;Phwacp3c6Jq_1P9(w%;wuKh~nlyAn!0(x2zLRd^p%+awV#D{@}g)y3z>8c(kX|V}>pi-YHTvdz`}^y!($dmkV}Dji=Tz^YZ!^8Q4~5`7OQl@U z)Anr5($W!+0f}mcn>R%$$H#1&*?WFgztVA;yy_C*-xw+@K)*|RnGj-v=Fqvmp~3c= zD0--(H1rJP<0#heZWfT;RkJm2Ay8d&J-G>G&1JCI>2ptH_;9zkw^!cRlJFyn$R}UJ z8Tt8veW~WKdidAu>~>Ghf%32=1e|J@mV!cu%@fV2s3^0Q?upPY!$@tSYuD(4OJDo@ zhl-m8a1Av@-Ou(;A_^9I`RWxO8Aq1>kDxITN2OHqj~~8%{d$)0_8PiNigNZf>B3^g zk_r{Q?GtzRcyD(js6c6N56$DgX8LvkQvjiw!jM?~DNz!!J7|0&;{r4C1bzOyNY zQ`UNGZ?!dh$;lkY3{t=Fs|)wel;O?Qky!`$`4NnBMTUedcSy19PqKK1==a?>em^!o zJ~`x@DDd*}^cfrHzvK!dgWyeGC%nA%8YCqpwK6 zaZ~4C`AIW%;vz(4j79N{ho<-fTSyZ+W@Zjfw)k=Z#|nFjQi3$7!JMqTD>I3~9Jfzh zB`2S^VW;8Mu5gTFH;*7szA;UZRv${+$jiob&0B%RuSdr9U%UInYu5kQEgbGu&giVISjf--}+{3J^+HHH~D}o}k#M3#MWbw>BC5`ZCaO|Ljn?gB_(< zzUzYf@^|9Z>DE%si1cJ?IdmBrhsAsHDuP%Mq|fu`tz6?5T!az>N$!gU^}Y?oMHQKL z&gZ=EnojU!_7z|tNXxC>ksQ`&-7$4BAfO|*T>sS6^!Q$M*F=3NS?IOGf3KEM#(+je z-uM&qh)2q^l_cHs?avgUgf$; z33!Lhx^nsJd6vBeYtK+8^8oGS7faQ zHaRldwTbAC+V#^~c3NV{VZ>Z^okfO8cfP*3prD|O52C%jo!@mM?8m21_A_mIzkhwn zt=fn#_i`F>FRg7;hP)hSVQ50^9er2NCzMmeUj&J29IxJ83>D_IhityKG;&x8hiNiQ zv8i-G6p5Ze9x7A(dW&p z_!K;-`kFbYqnoyC2*JJI&*!`OwkJYeTwKI7p@P>Yp_Em%4N2!c0bFJIe}N?0NP4z| zTm2P!*mWs|1@CAU-;~zk_ia49KqW9}(#9?OojqO~N76zyRi_BhdoUux zb#u|OTKgK84U*4WOq(sg7AaG;#m~pr6o+R2_4}OL=8)4AMnfxOf*^SxL!$p}40ZpN&|{=7Eh(UYUB;7x7?9 zrd>8O8#vl_g;8)1Zfxn(I}w^z@~x)O6E&B{0FvOOy`*vdl6Yp$N=)}Es)cK~Ko}R&k z@bJmW$@Pr^>rDK;mN?!R8acEJXU?nI)s*oIu=0r#t+Qh)vWuu71LDOd9%IHSawd~UIPFFbF1As-9rZ6phpiM z`mk3fr~P2oB#XYM|EjD;F;&Klt}p3(xLCk7f~!|=E#*1?@;=ja5)B8QW|x#_E~{|j z9y7Bn-|~xj^Bax?bi#)R`|fUT0Uqm02fu6apFW_uZ+=@Wf1u0)AWVTnb97&!aiain zt>0L#e5+_Z{_?LwSE%*IC~~`E%(nSJiR(EL=X49k9}U?yGEIeC zM6Wccglbw>bq(@C+3af1+zh#6{@`^Bak*H9<&zJE!vs@W&gYb?5IZ z4|&$v{A8Xx;0vs0lvL~5w?NP%S`q|xDLXHtXu@ebp_J(N6&My&5WMmCr?47N+goX5 zDX7qqmk+6J%zPZfA?;`$3+aUH1~0EBIjdpAmA=$xpM2v!tV3GUvhl8!4X23!dZP6M z0V6jFYN4Qb_y?8eor)Ns+S(+csXAhignjY0_a{|IZggba=0rvF@Z?A~xA80pX6{N4 z>*!2fQF3Ik_XNdEV}LzH2RroX-|0hFbgbHAr1{O11&)sv>V?>#f)y< zx|JH|7!8PlgwHw8qDu>Uw1Tehll|to;qL3Od4{F_Qb1f7B-C|wD#DSvK+5)LBU}Bx z?Zl(Lp115=%5rkoRW9!=l#YnB670^VCN$BI!HKz{Id|*!?Kh8GTLlmA#6;6UW6Ov9 zVztCKv`pxYSZfzIEUg#D=f8>HrUfbPV<&U3F>#wb@Fce?w6&`DH#A{=E_?3`j2uka zN&o^+o8bwo(FE+5mRp%b8IMM2+wDbSXFNu^<}`Xl+UKbtgNn1 zo-pseNlV+P(%dO4$fPN)L67YKC^B2COo6h^0?)YO$rtRtRQV;mzKk+RDRF}8n8^Nz zDnNQ`8;fiL*-p+xB%Gx71-K1Xco_H|4>U5xs{+3G-^KuEUB1%57iEqJP$QX zx*3_77rV1#W|jaZ=;*{-bUmb`O>iqPfdpa)&25=Mz%4S~+4mpnp zP6=O8RaLbU#`G$=I!uV5_XsY$aeBZrmqIx!p=FzDfkmi-vc|zKucr3i}SsS9z(#oy4$yJ19M|!WRyX*y5s14_3BjsfviM6 zPg#zGeiwj<2}1=gDvVyyAhZ6n_sPi<90YU~3)4o;(YG|E0Wgufc=`KlD;V_V=@oPp z(Dr;3^>5l7hQ-aTIVj&IN;dDwp(b9LFKmq_vjUD{wW`@T^7b?5WdMLJf`@K)1MlzY z8z^=b&*Gw>L1xh;1B%G@n$5HP%2@L1q*mS~KD*$%`gIpvyqeD$V9KscfmbsvtKF** z8u5m1F-&twE<F;)X>=Mq03QL*<(|@q-$h-2tuC*Z+`| z%3Gt{K7GCunhnJ&76T_jsF50H{}T64;*3AB3nnof*GOj-gR_z6ciM!8G1K1G;mlAn%u!rR#kQS_4$NLGA4ep zOd2uFjv=x4%I0Qd_HE8Vo9BF=Z(|!0e}y&Ttj?dltEV3sZTsn3FfM-pmEf@3(&7Hd zA$Joqw4beRN(ln)O`9_De0^@#s{lu$esF35EH3D3_YwQ>;X^?^hPuhjoP(UV2WuR3 z?cP(ls)PMqe?5l<8tmdafdZO%bKkj1Ew|Yz5p&0uXhp0N2UtO7UU}5~;w+&-raMl<=sWpW7~X>N$u`?14I1oQ zI@;2Z!7g38gcPs=XM1~lH#axP&D9j~ z)t6AOE|RUi#IPIbOD}12kCFtIB}hBSbPZnYb)bCft7!})g@9%Z$!2Drj(5!|n}&3E zcZBTf)k&K(RnNxjNPJ&UhVVdpK+3D>=SPyYoZ?I6(L2~y7$@C(RLb|w2_nnT+pX2w3<3d07tjz@*mXK zCiKOLvU~4shVs&0!oQX$-c518@?I*Eh)Dw&=_{n9W){Aswv&?Ug?Cw4tOH9fDJd&= zbpvN(J(9EZ5>xG6@{hx>5EFh~{RC;gl!!+g6zNXGF5*EtOMrUb4ly&2j*jkBVKB32 zUJbN2>(Ha`J1Xub0N5}N*aq4pdZ~Qu4P!usMjM2fg@FG93){%3sJ<#!C%fm`*PG@u zbE|FN_K&%0cB{l%hjZ^mkj&LvPhAA0EiAGuEXniPqCbEJ0-ORYn+jz*$-Sp9Ecm}r%Tal3 z=_&S@-nYBvjo6-yG)xeSlPci=rSTui-|KY$GJ_ntCoJB_*3NGA_wU~u8$<)603INv zXXw6y`%6wv2K=b3tPFkqK$)Gfw)PM-fRo>k_;6@fsP@;2G+0powjH8tBg;*>7m*t||+Xr;1|`H4{3>WzN$E?gS6+E*7tDWV}*cBTan>G3=F zpdAvE1=g}svlEifHiHEYji|9B94g0wJEzZ_Y3#1^3kr(RH^uZ0niNqy4jFt3t81OI zjopOw*y|YZJwE5KIJn)wm`g6;_UXV%nf&%`3g89YR;Bw=O+%2E|HqPb?~y)%_|e!N z8~AsEU)el=bQ1H6>GsB2VLy)8ThQ@qRx* zKJ^5#F>ktP8U#lR1KK9G*+46+u&|5;N(itWP~)N5LA}Y*I<5=seP$zrE!1+Lu1w+6 z2`>#-r9O`OD9|TTvx5_wjVa{@;Rgggj{!ocfbw?KppdFflUTtbNs@0D{3`|N9Mrgw zu0E90(a|~XRBqh3vGY(4sv-OzN)rSdFj{S!Uk9pOIgb@_Ay;@$lZ@^lAy6n(3uIS; zE3NOIB4IOm%jZiZa_PLtMIZs(P=IF*s#UiEv!+

@HR`a|-OtBu*u2sDNey_uQP? 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�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

i&OEPQ>W+g>! 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 _YYANivOuO8T|#z!tC?+S+ovab{9nRjd8lYh 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

$0J4;&^#4!N=D(658kB00WRS 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

? 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%0D|n{ncRgiDU* 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 Zoe : createVat() Zoe -> Zcf : E(zcfRoot).executeContract Zcf -> Zcf : evalContractBundle() Zcf -> Zcf : makeZcf -Zcf -> contract : execute(zcf) +Zcf -> contract : start(zcf) contract -> Zcf : makeInvitation(...) Zcf -> Zoe : makeInvitation(...) note left diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index d447fb224d7..2bfd5dfd8b2 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -35,7 +35,7 @@ export function buildRootObject() { const getAmountMath = brand => issuerTable.get(brand).amountMath; const invitationHandleToHandler = makeWeakStore('invitationHandle'); - const seatToSeatAdmin = makeWeakStore('seat'); + const seatToZCFSeatAdmin = makeWeakStore('seat'); const issuers = Object.values(instanceRecord.issuerKeywordRecord); @@ -87,7 +87,7 @@ export function buildRootObject() { // Commit the staged allocations and inform Zoe of the // newAllocation. seatStagings.forEach(seatStaging => - seatToSeatAdmin.get(seatStaging.getSeat()).commit(seatStaging), + seatToZCFSeatAdmin.get(seatStaging.getSeat()).commit(seatStaging), ); }, saveIssuer: (issuerP, keyword) => { @@ -152,20 +152,20 @@ export function buildRootObject() { // can tell us about new seats. /** @type AddSeatObj */ const addSeatObj = { - addSeat: (invitationHandle, zoeSeat, seatData) => { - const { seatAdmin, seat } = makeSeatAdmin( + addSeat: (invitationHandle, zoeSeatAdmin, seatData) => { + const { zcfSeatAdmin, zcfSeat } = makeSeatAdmin( allSeatStagings, - zoeSeat, + zoeSeatAdmin, seatData, getAmountMath, ); - seatToSeatAdmin.init(seat, seatAdmin); + seatToZCFSeatAdmin.init(zcfSeat, zcfSeatAdmin); const offerHandler = invitationHandleToHandler.get(invitationHandle); - const offerResultP = E(offerHandler)(seat).catch(err => { - seat.exit(); + const offerResultP = E(offerHandler)(zcfSeat).catch(err => { + zcfSeat.exit(); throw err; }); - const exitObj = makeExitObj(seatData.proposal, zoeSeat); + const exitObj = makeExitObj(seatData.proposal, zoeSeatAdmin); /** @type AddSeatResult */ const addSeatResult = { offerResultP, exitObj }; return harden(addSeatResult); diff --git a/packages/zoe/src/contractFacet/exit.js b/packages/zoe/src/contractFacet/exit.js index 1c57b677946..741f7ebed31 100644 --- a/packages/zoe/src/contractFacet/exit.js +++ b/packages/zoe/src/contractFacet/exit.js @@ -2,7 +2,7 @@ import { assert, details, q } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; /** @type MakeExitObj */ -export const makeExitObj = (proposal, zoeSeat) => { +export const makeExitObj = (proposal, zoeSeatAdmin) => { const [exitKind] = Object.getOwnPropertyNames(proposal.exit); /** @type {ExitObj | undefined} */ @@ -13,7 +13,7 @@ export const makeExitObj = (proposal, zoeSeat) => { E(proposal.exit.afterDeadline.timer).setWakeup( proposal.exit.afterDeadline.deadline, harden({ - wake: () => E(zoeSeat).exit(), + wake: () => E(zoeSeatAdmin).exit(), }), ); } else if (exitKind === 'onDemand') { @@ -23,7 +23,7 @@ export const makeExitObj = (proposal, zoeSeat) => { // data) and presences (local proxies for objects that may have // methods). exitObj = { - exit: () => E(zoeSeat).exit(), + exit: () => E(zoeSeatAdmin).exit(), }; } else { // if exitKind is 'waived' the user has no ability to exit their seat diff --git a/packages/zoe/src/contractFacet/seat.js b/packages/zoe/src/contractFacet/seat.js index 22aec4016d4..e71adb29072 100644 --- a/packages/zoe/src/contractFacet/seat.js +++ b/packages/zoe/src/contractFacet/seat.js @@ -11,7 +11,7 @@ import '../internal-types'; /** @type MakeSeatAdmin */ export const makeSeatAdmin = ( allSeatStagings, - zoeSeat, + zoeSeatAdmin, seatData, getAmountMath, ) => { @@ -23,25 +23,25 @@ export const makeSeatAdmin = ( let exited = false; // seat is "active" /** @type ZCFSeatAdmin */ - const seatAdmin = harden({ + const zcfSeatAdmin = harden({ commit: seatStaging => { assert( allSeatStagings.has(seatStaging), details`The seatStaging ${seatStaging} was not recognized`, ); currentAllocation = seatStaging.getStagedAllocation(); - E(zoeSeat).replaceAllocation(currentAllocation); + E(zoeSeatAdmin).replaceAllocation(currentAllocation); }, }); /** @type {ZCFSeat} */ - const seat = harden({ + const zcfSeat = harden({ exit: () => { exited = true; - E(zoeSeat).exit(); + E(zoeSeatAdmin).exit(); }, kickOut: (msg = 'Kicked out of seat') => { - seat.exit(); + zcfSeat.exit(); assert.fail(msg); }, getNotifier: () => notifier, @@ -75,7 +75,7 @@ export const makeSeatAdmin = ( ); const seatStaging = { - getSeat: () => seat, + getSeat: () => zcfSeat, getStagedAllocation: () => allocation, }; allSeatStagings.add(seatStaging); @@ -83,5 +83,5 @@ export const makeSeatAdmin = ( }, }); - return harden({ seat, seatAdmin }); + return harden({ zcfSeat, zcfSeatAdmin }); }; diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index 71b901fa524..f818b06aa10 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -44,7 +44,7 @@ */ /** - * @typedef {Object} ZoeSeat + * @typedef {Object} ZoeSeatAdmin * @property {() => void} exit - exit seat * @property {(replacementAllocation: Allocation) => void} replaceAllocation - replace the * currentAllocation with this allocation @@ -67,11 +67,11 @@ * @callback MakeSeatAdmin * @param {Set} allSeatStagings - a set of valid * seatStagings where allocations have been checked for offerSafety - * @param {ZoeSeat} zoeSeat - a presence from Zoe such that ZCF can tell Zoe + * @param {ZoeSeatAdmin} zoeSeatAdmin - a presence from Zoe such that ZCF can tell Zoe * about seat events * @param {SeatData} seatData - pass-by-copy data to use to make the seat * @param {(brand: Brand) => AmountMath} getAmountMath - * @returns {{seatAdmin: ZCFSeatAdmin, seat: ZCFSeat}} + * @returns {{zcfSeatAdmin: ZCFSeatAdmin, zcfSeat: ZCFSeat}} */ /** @@ -82,9 +82,9 @@ /** * @typedef {Object} InstanceAdmin - * @property {(invitationHandle: InvitationHandle, seatAdmin: - * ZoeSeat, seatData: SeatData) => Promise} addSeatAdmin - * @property {(seatAdmin: ZoeSeat) => void} removeSeatAdmin + * @property {(invitationHandle: InvitationHandle, zoeSeatAdmin: + * ZoeSeatAdmin, seatData: SeatData) => Promise} addZoeSeatAdmin + * @property {(zoeSeatAdmin: ZoeSeatAdmin) => void} removeZoeSeatAdmin * @property {() => Instance} getInstance * @property {() => PublicFacet} getPublicFacet * @property {() => IssuerKeywordRecord} getIssuers @@ -94,7 +94,8 @@ /** * @typedef {Object} AddSeatObj - * @property {(invitationHandle, zoeSeat, seatData) => AddSeatResult} addSeat + * @property {(invitationHandle: InvitationHandle, zoeSeatAdmin: + * ZoeSeatAdmin, seatData: SeatData) => AddSeatResult} addSeat */ /** @@ -145,7 +146,7 @@ /** * @callback MakeExitObj * @param {ProposalRecord} proposal - * @param {ZoeSeat} zoeSeat + * @param {ZoeSeatAdmin} zoeSeatAdmin */ /** diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 18ea2d5773a..7ac1306ba05 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -118,7 +118,7 @@ * @param {Installation} installation * @param {IssuerKeywordRecord=} issuerKeywordRecord * @param {Object=} terms - * @returns {MakeInstanceResult} + * @returns {Promise} * Zoe is long-lived. We can use Zoe to create smart contract * instances by specifying a particular contract installation to use, diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index e805969fa9b..c019239f28a 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -84,7 +84,7 @@ function makeZoe(vatAdminSvc) { details`${installation} was not a valid installation`, ); - const seatAdmins = new Set(); + const zoeSeatAdmins = new Set(); const instance = harden({}); const keywords = cleanKeywords(uncleanIssuerKeywordRecord); @@ -121,7 +121,7 @@ function makeZoe(vatAdminSvc) { const zcfRoot = root; const exitAllSeats = () => - seatAdmins.forEach(seatAdmin => seatAdmin.exit()); + zoeSeatAdmins.forEach(zoeSeatAdmin => zoeSeatAdmin.exit()); E(adminNode) .done() @@ -191,11 +191,15 @@ function makeZoe(vatAdminSvc) { /** @type {InstanceAdmin} */ const instanceAdmin = { - addSeatAdmin: async (invitationHandle, seatAdmin, seatData) => { - seatAdmins.add(seatAdmin); - return E(addSeatObj).addSeat(invitationHandle, seatAdmin, seatData); + addZoeSeatAdmin: async (invitationHandle, zoeSeatAdmin, seatData) => { + zoeSeatAdmins.add(zoeSeatAdmin); + return E(addSeatObj).addSeat( + invitationHandle, + zoeSeatAdmin, + seatData, + ); }, - removeSeatAdmin: seatAdmin => seatAdmins.delete(seatAdmin), + removeZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.delete(zoeSeatAdmin), getPublicFacet: () => publicFacet, getTerms: () => instanceRecord.terms, getIssuers: () => instanceRecord.issuerKeywordRecord, @@ -206,7 +210,10 @@ function makeZoe(vatAdminSvc) { instanceToInstanceAdmin.init(instance, instanceAdmin); // Actually returned to the user. - return { creatorFacet: creatorFacetWInstance, creatorInvitation }; + return { + creatorFacet: creatorFacetWInstance, + creatorInvitation: await creatorInvitation, + }; }, offer: async ( invitation, @@ -253,8 +260,8 @@ function makeZoe(vatAdminSvc) { const instanceAdmin = instanceToInstanceAdmin.get(instance); - /** @type {ZoeSeat} */ - const seatAdmin = { + /** @type {ZoeSeatAdmin} */ + const zoeSeatAdmin = { replaceAllocation: replacementAllocation => { harden(replacementAllocation); // Merging happens in ZCF, so replacementAllocation can @@ -264,7 +271,7 @@ function makeZoe(vatAdminSvc) { }, exit: () => { updater.finish(undefined); - instanceAdmin.removeSeatAdmin(seatAdmin); + instanceAdmin.removeZoeSeatAdmin(zoeSeatAdmin); /** @type {PaymentPKeywordRecord} */ const payout = {}; @@ -278,7 +285,7 @@ function makeZoe(vatAdminSvc) { payoutPromiseKit.resolve(payout); }, }; - harden(seatAdmin); + harden(zoeSeatAdmin); /** @type {UserSeat} */ const userSeat = { @@ -295,7 +302,7 @@ function makeZoe(vatAdminSvc) { const seatData = harden({ proposal, initialAllocation, notifier }); instanceAdmin - .addSeatAdmin(invitationHandle, seatAdmin, seatData) + .addZoeSeatAdmin(invitationHandle, zoeSeatAdmin, seatData) .then(({ offerResultP, exitObj }) => { offerResultPromiseKit.resolve(offerResultP); exitObjPromiseKit.resolve(exitObj); From 8984c39a353410ce3a9ca392cd1627e6ec053abb Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Wed, 5 Aug 2020 17:16:10 -0700 Subject: [PATCH 03/31] refactor: move up init of instance, instanceAdmin --- packages/zoe/src/zoeService/zoe.js | 43 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index c019239f28a..a52adc690f5 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -170,6 +170,26 @@ function makeZoe(vatAdminSvc) { }; const bundle = installation.getBundle(); + const addSeatObjPromiseKit = makePromiseKit(); + const publicFacetPromiseKit = makePromiseKit(); + + /** @type {InstanceAdmin} */ + const instanceAdmin = { + addZoeSeatAdmin: async (invitationHandle, zoeSeatAdmin, seatData) => { + zoeSeatAdmins.add(zoeSeatAdmin); + return E( + /** @type Promise */ (addSeatObjPromiseKit.promise), + ).addSeat(invitationHandle, zoeSeatAdmin, seatData); + }, + removeZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.delete(zoeSeatAdmin), + getPublicFacet: () => publicFacetPromiseKit.promise, + getTerms: () => instanceRecord.terms, + getIssuers: () => instanceRecord.issuerKeywordRecord, + getBrands: () => instanceRecord.brandKeywordRecord, + getInstance: () => instance, + }; + + instanceToInstanceAdmin.init(instance, instanceAdmin); const { creatorFacet = {}, @@ -184,31 +204,14 @@ function makeZoe(vatAdminSvc) { harden({ ...instanceRecord }), ); + addSeatObjPromiseKit.resolve(addSeatObj); + publicFacetPromiseKit.resolve(publicFacet); + const creatorFacetWInstance = { ...creatorFacet, getInstance: () => instance, }; - /** @type {InstanceAdmin} */ - const instanceAdmin = { - addZoeSeatAdmin: async (invitationHandle, zoeSeatAdmin, seatData) => { - zoeSeatAdmins.add(zoeSeatAdmin); - return E(addSeatObj).addSeat( - invitationHandle, - zoeSeatAdmin, - seatData, - ); - }, - removeZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.delete(zoeSeatAdmin), - getPublicFacet: () => publicFacet, - getTerms: () => instanceRecord.terms, - getIssuers: () => instanceRecord.issuerKeywordRecord, - getBrands: () => instanceRecord.brandKeywordRecord, - getInstance: () => instance, - }; - - instanceToInstanceAdmin.init(instance, instanceAdmin); - // Actually returned to the user. return { creatorFacet: creatorFacetWInstance, From 017c85d01a5316257e375323c44bc64e596830ac Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Fri, 7 Aug 2020 09:50:04 -0700 Subject: [PATCH 04/31] fix: minting allocations in zcf (#1382) --- packages/ERTP/src/types.js | 2 +- packages/eventual-send/src/index.js | 3 +- .../zoe/src/contractFacet/contractFacet.js | 89 ++++++++++++++- packages/zoe/src/contracts/mintPayments.js | 107 ++++++++---------- packages/zoe/src/internal-types.js | 46 ++++++-- packages/zoe/src/issuerTable.js | 12 +- packages/zoe/src/objArrayConversion.js | 9 ++ packages/zoe/src/types.js | 52 ++++++++- packages/zoe/src/zoeService/zoe.js | 69 +++++++++-- packages/zoe/test/zoeTestHelpers.js | 2 +- 10 files changed, 293 insertions(+), 98 deletions(-) diff --git a/packages/ERTP/src/types.js b/packages/ERTP/src/types.js index e69e274d5e5..42ed02f7884 100644 --- a/packages/ERTP/src/types.js +++ b/packages/ERTP/src/types.js @@ -189,7 +189,7 @@ /** * @callback MakeIssuerKit * @param {string} allegedName - * @param {string} mathHelperName + * @param {MathHelpersName} mathHelperName * @returns {IssuerKit} * * The allegedName is useful for debugging and double-checking diff --git a/packages/eventual-send/src/index.js b/packages/eventual-send/src/index.js index 2b9f26f1e3d..ac2098cc433 100644 --- a/packages/eventual-send/src/index.js +++ b/packages/eventual-send/src/index.js @@ -74,7 +74,8 @@ export function makeHandledPromise(Promise) { * https://en.wikipedia.org/wiki/Disjoint-set_data_structure * * @param {*} target Any value. - * @returns {*} If the target was a HandledPromise, the most-resolved parent of it, otherwise the target. + * @returns {*} If the target was a HandledPromise, the most-resolved parent + * of it, otherwise the target. */ function shorten(target) { let p = target; diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 2bfd5dfd8b2..aad61d9a7c8 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -11,12 +11,14 @@ import { assert, details } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; import makeWeakStore from '@agoric/weak-store'; +import makeAmountMath from '@agoric/ertp/src/amountMath'; import { areRightsConserved } from './rightsConservation'; import { makeIssuerTable } from '../issuerTable'; import { assertKeywordName, getKeywords } from '../zoeService/cleanProposal'; import { evalContractBundle } from './evalContractCode'; import { makeSeatAdmin } from './seat'; import { makeExitObj } from './exit'; +import { objectMap } from '../objArrayConversion'; import '../../exported'; import '../internal-types'; @@ -35,6 +37,8 @@ export function buildRootObject() { const getAmountMath = brand => issuerTable.get(brand).amountMath; const invitationHandleToHandler = makeWeakStore('invitationHandle'); + + /** @type WeakStore */ const seatToZCFSeatAdmin = makeWeakStore('seat'); const issuers = Object.values(instanceRecord.issuerKeywordRecord); @@ -44,7 +48,83 @@ export function buildRootObject() { await getPromiseForIssuerRecords(issuers); - const allSeatStagings = new Set(); + const allSeatStagings = new WeakSet(); + + /** @type MakeZCFMint */ + const makeZCFMint = async (keyword, mathHelperName = 'nat') => { + const zoeMintP = E(zoeInstanceAdmin).makeZoeMint(keyword, mathHelperName); + const mintyIssuerRecord = await E(zoeMintP).getIssuerRecord(); + // AWAIT + const { brand: mintyBrand } = mintyIssuerRecord; + const mintyAmountMath = makeAmountMath(mintyBrand, mathHelperName); + // TODO some zcf-side registration of the new issuer + + /** @type ZCFMint */ + const zcfMint = harden({ + getIssuerRecord: () => { + return mintyIssuerRecord; + }, + mintGains: (gains, zcfSeat = undefined) => { + assert( + zcfSeat !== undefined, + details`On demand seat creation not yet implemented`, + ); + let totalToMint = mintyAmountMath.getEmpty(); + const oldAllocation = zcfSeat.getCurrentAllocation(); + const updates = objectMap(gains, ([seatKeyword, amountToAdd]) => { + totalToMint = mintyAmountMath.add(totalToMint, amountToAdd); + const oldAmount = oldAllocation[seatKeyword]; + // oldAmount being absent is equivalent to empty. + const newAmount = oldAmount + ? mintyAmountMath.add(oldAmount, amountToAdd) + : amountToAdd; + return [seatKeyword, newAmount]; + }); + const newAllocation = harden({ + ...oldAllocation, + ...updates, + }); + const zcfSeatAdmin = seatToZCFSeatAdmin.get(zcfSeat); + // verifies offer safety + const seatStaging = zcfSeat.stage(newAllocation); + // No effects above. Commit point. Must mint and commit atomically. + // (Not really. If we minted only, no one would ever get those + // invisibly-minted assets.) + E(zoeMintP).mintGains(totalToMint); + zcfSeatAdmin.commit(seatStaging); + return zcfSeat; + }, + burnLosses: (losses, zcfSeat) => { + let totalToBurn = mintyAmountMath.getEmpty(); + const oldAllocation = zcfSeat.getCurrentAllocation(); + const updates = objectMap( + losses, + ([seatKeyword, amountToSubtract]) => { + totalToBurn = mintyAmountMath.add(totalToBurn, amountToSubtract); + const oldAmount = oldAllocation[seatKeyword]; + const newAmount = mintyAmountMath.subtract( + oldAmount, + amountToSubtract, + ); + return [seatKeyword, newAmount]; + }, + ); + const newAllocation = harden({ + ...oldAllocation, + ...updates, + }); + const zcfSeatAdmin = seatToZCFSeatAdmin.get(zcfSeat); + // verifies offer safety + const seatStaging = zcfSeat.stage(newAllocation); + // No effects above. Commit point. Must commit and burn atomically. + // (Not really. If we committed only, no one would ever get the + // unburned assets.) + zcfSeatAdmin.commit(seatStaging); + E(zoeMintP).burnLosses(totalToBurn); + }, + }); + return zcfMint; + }; /** @type ContractFacet */ const zcf = { @@ -123,9 +203,8 @@ export function buildRootObject() { 'string', details`invitations must have a description string: ${description}`, ); - /** @type {InvitationHandle} */ - const invitationHandle = {}; - harden(invitationHandle); + + const invitationHandle = /** @type {InvitationHandle} */ (harden({})); invitationHandleToHandler.init(invitationHandle, offerHandler); /** @type {Promise>} */ const invitationP = E(zoeInstanceAdmin).makeInvitation( @@ -145,6 +224,7 @@ export function buildRootObject() { getBrandForIssuer: issuer => issuerTable.getIssuerRecordByIssuer(issuer).brand, getAmountMath, + makeZCFMint, }; harden(zcf); @@ -161,6 +241,7 @@ export function buildRootObject() { ); seatToZCFSeatAdmin.init(zcfSeat, zcfSeatAdmin); const offerHandler = invitationHandleToHandler.get(invitationHandle); + // @ts-ignore const offerResultP = E(offerHandler)(zcfSeat).catch(err => { zcfSeat.exit(); throw err; diff --git a/packages/zoe/src/contracts/mintPayments.js b/packages/zoe/src/contracts/mintPayments.js index 6fa675a4fd2..d2726419605 100644 --- a/packages/zoe/src/contracts/mintPayments.js +++ b/packages/zoe/src/contracts/mintPayments.js @@ -1,75 +1,62 @@ /* eslint-disable no-use-before-define */ // @ts-check -import makeIssuerKit from '@agoric/ertp'; -import { escrowAndAllocateTo } from '../contractSupport'; - import '../../exported'; /** * This is a very simple contract that creates a new issuer and mints payments * from it, in order to give an example of how that can be done. This contract - * sends new tokens to anyone who requests them. + * sends new tokens to anyone who has an invitation. * - * Offer safety is not enforced here: the expectation is that most contracts - * that want to do something similar would use the ability to mint new payments - * internally rather than sharing that ability widely as this one does. + * The expectation is that most contracts that want to do something similar + * would use the ability to mint new payments internally rather than sharing + * that ability widely as this one does. * - * makeInstance returns an invitation that, when exercised, provides 1000 of the - * new tokens. await E(publicAPI).makeInvite() returns an invitation that accepts an - * empty offer and provides 1000 tokens. + * To pay others in tokens, the creator of the instance can make + * invitations for them, which when used to make an offer, will payout + * the specified amount of tokens. * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const start = (zcf, _terms) => { - // Create the internal token mint for a fungible digital asset - const { issuer, mint, amountMath } = makeIssuerKit('tokens'); - - // We need to tell Zoe about this issuer and add a keyword for the - // issuer. Let's call this the 'Token' issuer. - return zcf.saveIssuer(issuer, 'Token').then(() => { - // We need to wait for the promise to resolve (meaning that Zoe - // has done the work of adding a new issuer). - const mintPayment = (extent = 1000) => seat => { - const amount = amountMath.make(extent); - const payment = mint.mintPayment(amount); - - // Let's use a helper function which escrows the payment with - // Zoe, and reallocates to the recipientHandle. - return escrowAndAllocateTo( - zcf, - seat, - { Token: amount }, - { Token: payment }, - ).then(() => { - // Exit the seat so the user gets a payout - seat.exit(); - - // Since the user is getting the payout through Zoe, we can - // return anything here. Let's return some helpful instructions. - return 'Offer completed. You should receive a payment from Zoe'; - }); - }; - - const creatorFacet = { - // The creator of the instance can send invitations to anyone - // they wish to. - makeInvitation: extent => - zcf.makeInvitation(mintPayment(extent), 'mint a payment'), - getTokenIssuer: () => issuer, - }; - - const publicFacet = { - // Make the token issuer public. Note that only the mint can - // make new digital assets. The issuer is ok to make public. - getTokenIssuer: () => issuer, - }; - - // Return the creatorFacet to the creator, so they can make - // invitations for others to get payments of tokens. Publish the - // publicFacet. - return harden({ creatorFacet, publicFacet }); - }); +const start = async (zcf, _terms) => { + // Create the internal token mint for a fungible digital asset. Note + // that 'Tokens' is both the keyword and the allegedName. + const zcfMint = await zcf.makeZCFMint('Tokens'); + // AWAIT + + // Now ZCF has saved the issuer, brand, and local amountMath so that they + // can be accessed synchronously. + const { amountMath, issuer } = zcfMint.getIssuerRecord(); + + const mintPayment = (extent = 1000) => seat => { + const amount = amountMath.make(extent); + // Synchronously mint and allocate amount to seat. + zcfMint.mintGains({ Token: amount }, seat); + // Exit the seat so that the user gets a payout. + seat.exit(); + // Since the user is getting the payout through Zoe, we can + // return anything here. Let's return some helpful instructions. + return 'Offer completed. You should receive a payment from Zoe'; + }; + + const creatorFacet = { + // The creator of the instance can send invitations to anyone + // they wish to. + makeInvitation: extent => + zcf.makeInvitation(mintPayment(extent), 'mint a payment'), + getTokenIssuer: () => issuer, + }; + + const publicFacet = { + // Make the token issuer public. Note that only the mint can + // make new digital assets. The issuer is ok to make public. + getTokenIssuer: () => issuer, + }; + + // Return the creatorFacet to the creator, so they can make + // invitations for others to get payments of tokens. Publish the + // publicFacet. + return harden({ creatorFacet, publicFacet }); }; harden(start); diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index f818b06aa10..889f7a9d293 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -46,8 +46,8 @@ /** * @typedef {Object} ZoeSeatAdmin * @property {() => void} exit - exit seat - * @property {(replacementAllocation: Allocation) => void} replaceAllocation - replace the - * currentAllocation with this allocation + * @property {(replacementAllocation: Allocation) => void} replaceAllocation + * - replace the currentAllocation with this allocation */ /** @@ -65,9 +65,10 @@ /** * Make the ZCF seat and seat admin * @callback MakeSeatAdmin - * @param {Set} allSeatStagings - a set of valid + * @param {WeakSet} allSeatStagings - a set of valid * seatStagings where allocations have been checked for offerSafety - * @param {ZoeSeatAdmin} zoeSeatAdmin - a presence from Zoe such that ZCF can tell Zoe + * @param {ZoeSeatAdmin} zoeSeatAdmin + * - a presence from Zoe such that ZCF can tell Zoe * about seat events * @param {SeatData} seatData - pass-by-copy data to use to make the seat * @param {(brand: Brand) => AmountMath} getAmountMath @@ -82,8 +83,10 @@ /** * @typedef {Object} InstanceAdmin - * @property {(invitationHandle: InvitationHandle, zoeSeatAdmin: - * ZoeSeatAdmin, seatData: SeatData) => Promise} addZoeSeatAdmin + * @property {(invitationHandle: InvitationHandle, + * zoeSeatAdmin: ZoeSeatAdmin, + * seatData: SeatData, + * ) => Promise} addZoeSeatAdmin * @property {(zoeSeatAdmin: ZoeSeatAdmin) => void} removeZoeSeatAdmin * @property {() => Instance} getInstance * @property {() => PublicFacet} getPublicFacet @@ -94,17 +97,36 @@ /** * @typedef {Object} AddSeatObj - * @property {(invitationHandle: InvitationHandle, zoeSeatAdmin: - * ZoeSeatAdmin, seatData: SeatData) => AddSeatResult} addSeat + * @property {(invitationHandle: InvitationHandle, + * zoeSeatAdmin: ZoeSeatAdmin, + * seatData: SeatData, + * ) => AddSeatResult} addSeat */ /** * @typedef {Object} ZoeInstanceAdmin - * @property {(invitationHandle: InvitationHandle, description: - * string, customProperties?: {}) => Payment<'ZoeInvitation'>} - * makeInvitation + * @property {(invitationHandle: InvitationHandle, + * description: string, + * customProperties?: {}, + * ) => Payment<'ZoeInvitation'>} makeInvitation * @property {() => void} shutdown - * @property {(issuerP: Issuer|Promise, keyword: Keyword) => void} saveIssuer + * @property {(issuerP: ERef, keyword: Keyword) => void} saveIssuer + * + * @property {MakeZoeMint} makeZoeMint + */ + +/** + * @callback MakeZoeMint + * @param {Keyword} keyword + * @param {MathHelpersName=} mathHelperName + * @returns {ZoeMint} + */ + +/** + * @typedef {Object} ZoeMint + * @property {() => IssuerRecord} getIssuerRecord + * @property {(totalToMint: Amount) => void} mintGains + * @property {(totalToBurn: Amount) => void} burnLosses */ /** diff --git a/packages/zoe/src/issuerTable.js b/packages/zoe/src/issuerTable.js index 09854739ab9..508c8d0d1b9 100644 --- a/packages/zoe/src/issuerTable.js +++ b/packages/zoe/src/issuerTable.js @@ -1,5 +1,6 @@ // @ts-check +import { assert } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; import makeWeakStore from '@agoric/weak-store'; @@ -8,7 +9,6 @@ import { makeTable, makeValidateProperties } from './table'; import '../exported'; import './internal-types'; -import { assert } from '@agoric/assert'; /** * @template K,V @@ -41,6 +41,12 @@ const makeIssuerTable = () => { /** @type {WeakStore,Brand>} */ const issuerToBrand = makeWeakStore('issuer'); + const registerIssuerRecord = issuerRecord => { + const { brand, issuer } = issuerRecord; + table.create(issuerRecord, brand); + issuerToBrand.init(issuer, brand); + }; + // We can't be sure we can build the table entry soon enough that the first // caller will get the actual data, so we start by saving a promise in the // inProgress table, and once we have the Issuer, build the record, fill in @@ -75,8 +81,7 @@ const makeIssuerTable = () => { issuer, amountMath, }; - table.create(issuerRecord, brand); - issuerToBrand.init(issuer, brand); + registerIssuerRecord(issuerRecord); issuersInProgress.delete(issuer); return table.get(brand); }, @@ -105,6 +110,7 @@ const makeIssuerTable = () => { } }); }, + registerIssuerRecord, // Synchronous, but throws if not present. getIssuerRecordByIssuer: issuer => table.get(issuerToBrand.get(issuer)), }); diff --git a/packages/zoe/src/objArrayConversion.js b/packages/zoe/src/objArrayConversion.js index 1bd8f99b35a..dcb7762e275 100644 --- a/packages/zoe/src/objArrayConversion.js +++ b/packages/zoe/src/objArrayConversion.js @@ -81,3 +81,12 @@ export const filterFillAmounts = (allocation, amountMathKeywordRecord) => { }); return filledAllocation; }; + +/** + * @template P, T, U + * @param {{[P]: T}} original + * @param {(pair: [P, T]) => [P, U]} mapPairFn + * @returns {{[P]: U}} + */ +export const objectMap = (original, mapPairFn) => + Object.fromEntries(Object.entries(original).map(mapPairFn)); diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 7ac1306ba05..3ca4fbcf663 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -8,8 +8,10 @@ /** * @template {string} H - the name of the handle - * @typedef {H & {}} Handle A type constructor for an opaque type identified by the H string. - * This uses an intersection type ('MyHandle' & {}) to tag the handle's type even though the + * @typedef {H & {}} Handle A type constructor for an opaque type identified by + * the H string. + * This uses an intersection type ('MyHandle' & {}) to tag the handle's type even + * though the * actual value is just an empty object. */ @@ -114,12 +116,12 @@ */ /** - * @callback MakeInstance + * @callback MakeInstance * @param {Installation} installation * @param {IssuerKeywordRecord=} issuerKeywordRecord * @param {Object=} terms * @returns {Promise} - + * * Zoe is long-lived. We can use Zoe to create smart contract * instances by specifying a particular contract installation to use, * as well as the `issuerKeywordRecord` and `terms` of the contract. @@ -137,7 +139,10 @@ /** * @typedef {Partial} Proposal - * @typedef {{give:AmountKeywordRecord,want:AmountKeywordRecord,exit:ExitRule}} ProposalRecord + * @typedef {{give: AmountKeywordRecord, + * want: AmountKeywordRecord, + * exit: ExitRule + * }} ProposalRecord * * @typedef {Record} AmountKeywordRecord * @@ -185,6 +190,43 @@ * @property {() => InstanceRecord } getInstanceRecord * @property {(issuer: Issuer) => Brand} getBrandForIssuer * @property {GetAmountMath} getAmountMath + * + * @property {MakeZCFMint} makeZCFMint + */ + +/** + * @callback MakeZCFMint + * @param {Keyword} keyword + * @param {MathHelpersName=} mathHelperName + * @returns {ZCFMint} + */ + +/** + * @typedef {Object} ZCFMint + * + * @property {() => IssuerRecord} getIssuerRecord + * @property {(gains: AmountKeywordRecord, + * zcfSeat: ZCFSeat=, + * ) => ZCFSeat} mintGains + * All the amounts in gains must be of this ZCFMint's brand. + * The gains' keywords are in the namespace of that seat. + * Add the gains to that seat's allocation. + * The resulting state must be offer safe. (Currently, increasing assets can + * never violate offer safety anyway.) + * + * Mint that amount of assets into the pooled purse. + * If a seat is provided, it is returned. Otherwise a new seat is + * returned. TODO This creation-on-demand is not yet implemented. + * + * @property {(losses: AmountKeywordRecord, + * zcfSeat: ZCFSeat, + * ) => void} burnLosses + * All the amounts in losses must be of this ZCFMint's brand. + * The losses' keywords are in the namespace of that seat. + * Subtract losses from that seat's allocation. + * The resulting state must be offer safe. + * + * Burn that amount of assets from the pooled purse. */ /** diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index a52adc690f5..14c09096f45 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -31,8 +31,8 @@ function makeZoe(vatAdminSvc) { // Zoe state shared among functions const issuerTable = makeIssuerTable(); - /** @type {Set} */ - const installations = new Set(); + /** @type {WeakSet} */ + const installations = new WeakSet(); /** @type {WeakStore} */ const instanceToInstanceAdmin = makeWeakStore('instance'); @@ -130,6 +130,59 @@ function makeZoe(vatAdminSvc) { () => exitAllSeats(), ); + const registerIssuerByKeyword = (keyword, issuer, brand) => { + instanceRecord.issuerKeywordRecord = { + ...instanceRecord.issuerKeywordRecord, + [keyword]: issuer, + }; + instanceRecord.brandKeywordRecord = { + ...instanceRecord.brandKeywordRecord, + [keyword]: brand, + }; + }; + + /** @type MakeZoeMint */ + const makeZoeMint = (keyword, mathHelperName = 'nat') => { + assert( + !(keyword in instanceRecord.issuerKeywordRecord), + details`Keyword ${keyword} already registered`, + ); + + // Local indicates one that zoe itself makes from vetted code, + // and so can be assumed correct and fresh by zoe. + const { + mint: localMint, + issuer: localIssuer, + amountMath: localAmountMath, + brand: localBrand, + } = makeIssuerKit(keyword, mathHelperName); + const localIssuerRecord = harden({ + brand: localBrand, + issuer: localIssuer, + amountMath: localAmountMath, + }); + issuerTable.registerIssuerRecord(localIssuerRecord); + registerIssuerByKeyword(keyword, localIssuer, localBrand); + const localPooledPurse = localIssuer.makeEmptyPurse(); + brandToPurse.init(localBrand, localPooledPurse); + + /** @type ZoeMint */ + const zoeMint = harden({ + getIssuerRecord: () => { + return localIssuerRecord; + }, + mintGains: totalToMint => { + const payment = localMint.mintPayment(totalToMint); + localPooledPurse.deposit(payment, totalToMint); + }, + burnLosses: totalToBurn => { + const payment = localPooledPurse.withdraw(totalToBurn); + localIssuer.burn(payment, totalToBurn); + }, + }); + return zoeMint; + }; + /** @type {ZoeInstanceAdmin} */ const zoeInstanceAdminForZcf = { makeInvitation: (invitationHandle, description, customProperties) => { @@ -146,19 +199,12 @@ function makeZoe(vatAdminSvc) { ); return invitationMint.mintPayment(invitationAmount); }, - // checks of keyword done on zcf side + // checks if keyword done on zcf side saveIssuer: (issuerP, keyword) => issuerTable .getPromiseForIssuerRecord(issuerP) .then(({ issuer, brand }) => { - instanceRecord.issuerKeywordRecord = { - ...instanceRecord.issuerKeywordRecord, - [keyword]: issuer, - }; - instanceRecord.brandKeywordRecord = { - ...instanceRecord.brandKeywordRecord, - [keyword]: brand, - }; + registerIssuerByKeyword(keyword, issuer, brand); if (!brandToPurse.has(brand)) { brandToPurse.init(brand, E(issuer).makeEmptyPurse()); } @@ -167,6 +213,7 @@ function makeZoe(vatAdminSvc) { exitAllSeats(); adminNode.terminate(); }, + makeZoeMint, }; const bundle = installation.getBundle(); diff --git a/packages/zoe/test/zoeTestHelpers.js b/packages/zoe/test/zoeTestHelpers.js index 30520174340..42545f53691 100644 --- a/packages/zoe/test/zoeTestHelpers.js +++ b/packages/zoe/test/zoeTestHelpers.js @@ -1,5 +1,5 @@ import bundleSource from '@agoric/bundle-source'; -import {E} from "@agoric/eventual-send"; +import { E } from '@agoric/eventual-send'; export const assertPayout = (t, payout, purse, amount) => { payout.then(payment => { From 2d69d83c0375f8beb67e20f2c68d29b964243ecb Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Fri, 7 Aug 2020 14:40:45 -0700 Subject: [PATCH 05/31] refactor: tests for sellItems and mintAndSellNFT pass - need to be reworked for new synchronous mint still --- .../zoe/src/contractFacet/contractFacet.js | 4 +- packages/zoe/src/contractFacet/seat.js | 22 +- packages/zoe/src/contractSupport/index.js | 1 + packages/zoe/src/contracts/mintAndSellNFT.js | 42 +-- packages/zoe/src/contracts/sellItems.js | 151 +++++----- packages/zoe/src/internal-types.js | 1 + packages/zoe/src/zoeService/zoe.js | 9 + .../unitTests/contracts/test-sellTickets.js | 269 +++++++++--------- 8 files changed, 249 insertions(+), 250 deletions(-) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index aad61d9a7c8..7f7f74aee70 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -243,7 +243,9 @@ export function buildRootObject() { const offerHandler = invitationHandleToHandler.get(invitationHandle); // @ts-ignore const offerResultP = E(offerHandler)(zcfSeat).catch(err => { - zcfSeat.exit(); + if (!zcfSeat.hasExited()) { + zcfSeat.exit(); + } throw err; }); const exitObj = makeExitObj(seatData.proposal, zoeSeatAdmin); diff --git a/packages/zoe/src/contractFacet/seat.js b/packages/zoe/src/contractFacet/seat.js index e71adb29072..acea74df054 100644 --- a/packages/zoe/src/contractFacet/seat.js +++ b/packages/zoe/src/contractFacet/seat.js @@ -34,27 +34,42 @@ export const makeSeatAdmin = ( }, }); + const assertExitedFalse = () => assert(!exited, `seat has been exited`); + /** @type {ZCFSeat} */ const zcfSeat = harden({ exit: () => { + assertExitedFalse(); exited = true; E(zoeSeatAdmin).exit(); }, kickOut: (msg = 'Kicked out of seat') => { + assertExitedFalse(); zcfSeat.exit(); assert.fail(msg); }, - getNotifier: () => notifier, + getNotifier: () => { + assertExitedFalse(); + return notifier; + }, hasExited: () => exited, - getProposal: () => proposal, + getProposal: () => { + assertExitedFalse(); + return proposal; + }, getAmountAllocated: (keyword, brand) => { + assertExitedFalse(); if (currentAllocation[keyword] !== undefined) { return currentAllocation[keyword]; } return getAmountMath(brand).getEmpty(); }, - getCurrentAllocation: () => currentAllocation, + getCurrentAllocation: () => { + assertExitedFalse(); + return currentAllocation; + }, isOfferSafe: newAllocation => { + assertExitedFalse(); const reallocation = harden({ ...currentAllocation, ...newAllocation, @@ -63,6 +78,7 @@ export const makeSeatAdmin = ( return isOfferSafe(getAmountMath, proposal, reallocation); }, stage: newAllocation => { + assertExitedFalse(); // Check offer safety. const allocation = harden({ ...currentAllocation, diff --git a/packages/zoe/src/contractSupport/index.js b/packages/zoe/src/contractSupport/index.js index b75aeacef58..8a1f6b7be3d 100644 --- a/packages/zoe/src/contractSupport/index.js +++ b/packages/zoe/src/contractSupport/index.js @@ -19,4 +19,5 @@ export { assertIssuerKeywords, satisfies, escrowAndAllocateTo, + assertNatMathHelpers, } from './zoeHelpers'; diff --git a/packages/zoe/src/contracts/mintAndSellNFT.js b/packages/zoe/src/contracts/mintAndSellNFT.js index 147fae2cc26..2f78abc83ba 100644 --- a/packages/zoe/src/contracts/mintAndSellNFT.js +++ b/packages/zoe/src/contracts/mintAndSellNFT.js @@ -23,12 +23,9 @@ import '../../exported'; * allows selling the tickets that were produced. You can reuse the ticket maker * to mint more tickets (e.g. for a separate show.) * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { - const { terms } = zcf.getInstanceRecord(); - const { tokenName = 'token' } = terms; - +const start = (zcf, { tokenName = 'token' }) => { // Create the internal token mint const { issuer, mint, amountMath: tokenAmountMath } = makeIssuerKit( tokenName, @@ -41,7 +38,7 @@ const makeContract = zcf => { customValueProperties, count, moneyIssuer, - sellItemsInstallationHandle, + sellItemsInstallation, pricePerItem, }) => { const tokenAmount = tokenAmountMath.make( @@ -80,36 +77,23 @@ const makeContract = zcf => { pricePerItem, }); return E(zoeService) - .makeInstance( - sellItemsInstallationHandle, - issuerKeywordRecord, - sellItemsTerms, - ) - .then(({ invite, instanceRecord: { handle: instanceHandle } }) => { + .makeInstance(sellItemsInstallation, issuerKeywordRecord, sellItemsTerms) + .then(({ creatorInvitation, creatorFacet }) => { return E(zoeService) - .offer(invite, proposal, paymentKeywordRecord) - .then(offerResult => { + .offer(creatorInvitation, proposal, paymentKeywordRecord) + .then(sellItemsCreatorSeat => { return harden({ - ...offerResult, - sellItemsInstanceHandle: instanceHandle, + sellItemsCreatorSeat, + sellItemsCreatorFacet: creatorFacet, }); }); }); }; - const mintTokensHook = _offerHandle => { - // outcome is an object with a sellTokens method - return harden({ sellTokens }); - }; - - zcf.initPublicAPI( - harden({ - getTokenIssuer: () => issuer, - }), - ); + const creatorFacet = harden({ sellTokens, getIssuer: () => issuer }); - return zcf.makeInvitation(mintTokensHook, 'mint tokens'); + return harden({ creatorFacet }); }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/sellItems.js b/packages/zoe/src/contracts/sellItems.js index 3d6acbe7fe3..149c7304a91 100644 --- a/packages/zoe/src/contracts/sellItems.js +++ b/packages/zoe/src/contracts/sellItems.js @@ -2,7 +2,13 @@ // @ts-check import { assert, details } from '@agoric/assert'; -import { makeZoeHelpers, defaultAcceptanceMsg } from '../contractSupport'; +import { + assertIssuerKeywords, + assertNatMathHelpers, + trade, + defaultAcceptanceMsg, + assertProposalKeywords, +} from '../contractSupport'; import '../../exported'; @@ -21,60 +27,45 @@ import '../../exported'; * available to sell, and the money should be pricePerItem times the number of * items requested. * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { +const start = (zcf, { pricePerItem }) => { const allKeywords = ['Items', 'Money']; - const { - assertKeywords, - rejectOffer, - checkHook, - assertNatMathHelpers, - trade, - } = makeZoeHelpers(zcf); - assertKeywords(harden(allKeywords)); - - const { pricePerItem } = zcf.getInstanceRecord().terms; - assertNatMathHelpers(pricePerItem.brand); - let sellerOfferHandle; - - const sellerOfferHook = offerHandle => { - sellerOfferHandle = offerHandle; + assertIssuerKeywords(zcf, harden(allKeywords)); + assertNatMathHelpers(zcf, pricePerItem.brand); + + let sellerSeat; + + const sell = seat => { + sellerSeat = seat; return defaultAcceptanceMsg; }; - const buyerOfferHook = buyerOfferHandle => { - const { brandKeywordRecord } = zcf.getInstanceRecord(); - const [sellerAllocation, buyerAllocation] = zcf.getCurrentAllocations( - [sellerOfferHandle, buyerOfferHandle], - [brandKeywordRecord, brandKeywordRecord], - ); - const currentItemsForSale = sellerAllocation.Items; - const providedMoney = buyerAllocation.Money; + const buy = buyerSeat => { + const currentItemsForSale = sellerSeat.getAmountAllocated('Items'); + const providedMoney = buyerSeat.getAmountAllocated('Money'); + + const { + want: { Items: wantedItems }, + } = buyerSeat.getProposal(); - const { proposal } = zcf.getOffer(buyerOfferHandle); - const wantedItems = proposal.want.Items; - const numItemsWanted = wantedItems.value.length; - const totalCostValue = pricePerItem.value * numItemsWanted; const moneyAmountMaths = zcf.getAmountMath(pricePerItem.brand); const itemsAmountMath = zcf.getAmountMath(wantedItems.brand); - const totalCost = moneyAmountMaths.make(totalCostValue); - // Check that the wanted items are still for sale. if (!itemsAmountMath.isGTE(currentItemsForSale, wantedItems)) { - return rejectOffer( - buyerOfferHandle, - `Some of the wanted items were not available for sale`, - ); + const rejectMsg = `Some of the wanted items were not available for sale`; + throw buyerSeat.kickOut(rejectMsg); } + const totalCost = moneyAmountMaths.make( + pricePerItem.value * wantedItems.value.length, + ); + // Check that the money provided to pay for the items is greater than the totalCost. if (!moneyAmountMaths.isGTE(providedMoney, totalCost)) { - return rejectOffer( - buyerOfferHandle, - `More money (${totalCost}) is required to buy these items`, - ); + const rejectMsg = `More money (${totalCost}) is required to buy these items`; + throw buyerSeat.kickOut(rejectMsg); } // Reallocate. We are able to trade by only defining the gains @@ -82,46 +73,54 @@ const makeContract = zcf => { // the same, so the gains for one offer are the losses for the // other. trade( - { offerHandle: sellerOfferHandle, gains: { Money: providedMoney } }, - { offerHandle: buyerOfferHandle, gains: { Items: wantedItems } }, + zcf, + { seat: sellerSeat, gains: { Money: providedMoney } }, + { seat: buyerSeat, gains: { Items: wantedItems } }, ); - // Complete the buyer offer. - zcf.complete([buyerOfferHandle]); + // exit the buyer seat + buyerSeat.exit(); return defaultAcceptanceMsg; }; - const buyerExpected = harden({ - want: { Items: null }, - give: { Money: null }, - }); - - zcf.initPublicAPI( - harden({ - makeBuyerInvite: () => { - const itemsAmount = zcf.getCurrentAllocation(sellerOfferHandle).Items; - const itemsAmountMath = zcf.getAmountMath(itemsAmount.brand); - assert( - sellerOfferHandle && !itemsAmountMath.isEmpty(itemsAmount), - details`no items are for sale`, - ); - return zcf.makeInvitation( - checkHook(buyerOfferHook, buyerExpected), - 'buyer', - ); - }, - getAvailableItems: () => { - if (!sellerOfferHandle) { - throw new Error(`no items have been escrowed`); - } - return zcf.getCurrentAllocation(sellerOfferHandle).Items; - }, - getItemsIssuer: () => zcf.getInstanceRecord().issuerKeywordRecord.Items, - }), - ); - - return zcf.makeInvitation(sellerOfferHook, 'seller'); + const getAvailableItems = () => { + assert(sellerSeat && !sellerSeat.hasExited(), `no items are for sale`); + return sellerSeat.getAmountAllocated('Items'); + }; + + const getItemsIssuer = () => + zcf.getInstanceRecord().issuerKeywordRecord.Items; + + const publicFacet = { + getAvailableItems, + getItemsIssuer, + }; + + const creatorFacet = { + makeBuyerInvite: () => { + const itemsAmount = sellerSeat.getAmountAllocated('Items'); + const itemsAmountMath = zcf.getAmountMath(itemsAmount.brand); + assert( + sellerSeat && !itemsAmountMath.isEmpty(itemsAmount), + details`no items are for sale`, + ); + const buyerExpected = harden({ + want: { Items: null }, + give: { Money: null }, + }); + return zcf.makeInvitation( + assertProposalKeywords(buy, buyerExpected), + 'buyer', + ); + }, + getAvailableItems, + getItemsIssuer, + }; + + const creatorInvitation = zcf.makeInvitation(sell, 'seller'); + + return harden({ creatorFacet, creatorInvitation, publicFacet }); }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index 889f7a9d293..b1081abcd20 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -87,6 +87,7 @@ * zoeSeatAdmin: ZoeSeatAdmin, * seatData: SeatData, * ) => Promise} addZoeSeatAdmin + * @property {(zoeSeatAdmin: ZoeSeatAdmin) => boolean} hasZoeSeatAdmin * @property {(zoeSeatAdmin: ZoeSeatAdmin) => void} removeZoeSeatAdmin * @property {() => Instance} getInstance * @property {() => PublicFacet} getPublicFacet diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index 14c09096f45..17c6e8a4501 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -228,6 +228,7 @@ function makeZoe(vatAdminSvc) { /** @type Promise */ (addSeatObjPromiseKit.promise), ).addSeat(invitationHandle, zoeSeatAdmin, seatData); }, + hasZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.has(zoeSeatAdmin), removeZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.delete(zoeSeatAdmin), getPublicFacet: () => publicFacetPromiseKit.promise, getTerms: () => instanceRecord.terms, @@ -313,6 +314,10 @@ function makeZoe(vatAdminSvc) { /** @type {ZoeSeatAdmin} */ const zoeSeatAdmin = { replaceAllocation: replacementAllocation => { + assert( + instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), + `Cannot replace allocation. Seat has already exited`, + ); harden(replacementAllocation); // Merging happens in ZCF, so replacementAllocation can // replace the old allocation entirely. @@ -320,6 +325,10 @@ function makeZoe(vatAdminSvc) { currentAllocation = replacementAllocation; }, exit: () => { + assert( + instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), + `Cannot exit seat. Seat has already exited`, + ); updater.finish(undefined); instanceAdmin.removeZoeSeatAdmin(zoeSeatAdmin); diff --git a/packages/zoe/test/unitTests/contracts/test-sellTickets.js b/packages/zoe/test/unitTests/contracts/test-sellTickets.js index 6df83099b3a..83b8d047374 100644 --- a/packages/zoe/test/unitTests/contracts/test-sellTickets.js +++ b/packages/zoe/test/unitTests/contracts/test-sellTickets.js @@ -21,24 +21,19 @@ test(`mint and sell tickets for multiple shows`, async t => { const zoe = makeZoe(fakeVatAdmin); const mintAndSellNFTBundle = await bundleSource(mintAndSellNFTRoot); - const mintAndSellNFTInstallationHandle = await E(zoe).install( - mintAndSellNFTBundle, - ); + const mintAndSellNFTInstallation = await E(zoe).install(mintAndSellNFTBundle); const sellItemsBundle = await bundleSource(sellItemsRoot); - const sellItemsInstallationHandle = await E(zoe).install(sellItemsBundle); + const sellItemsInstallation = await E(zoe).install(sellItemsBundle); const { issuer: moolaIssuer, amountMath: moolaAmountMath } = makeIssuerKit( 'moola', ); - const { - instanceRecord: { publicAPI }, - invite, - } = await E(zoe).makeInstance(mintAndSellNFTInstallationHandle); - const { outcome } = await E(zoe).offer(invite); - const ticketMaker = await outcome; - const { outcome: escrowTicketsOutcome, sellItemsInstanceHandle } = await E( + const { creatorFacet: ticketMaker } = await E(zoe).makeInstance( + mintAndSellNFTInstallation, + ); + const { sellItemsCreatorSeat, sellItemsCreatorFacet } = await E( ticketMaker, ).sellTokens({ customValueProperties: { @@ -47,21 +42,20 @@ test(`mint and sell tickets for multiple shows`, async t => { }, count: 3, moneyIssuer: moolaIssuer, - sellItemsInstallationHandle, + sellItemsInstallation, pricePerItem: moolaAmountMath.make(20), }); t.equals( - await escrowTicketsOutcome, + await sellItemsCreatorSeat.getOfferResult(), defaultAcceptanceMsg, `escrowTicketsOutcome is default acceptance message`, ); - const ticketIssuerP = E(publicAPI).getTokenIssuer(); + const ticketIssuerP = E(ticketMaker).getIssuer(); const ticketBrand = await E(ticketIssuerP).getBrand(); - const { publicAPI: ticketSalesPublicAPI } = await E(zoe).getInstanceRecord( - sellItemsInstanceHandle, - ); - const ticketsForSale = await E(ticketSalesPublicAPI).getAvailableItems(); + const sellItemsInstance = await E(sellItemsCreatorFacet).getInstance(); + const ticketSalesPublicFacet = await E(zoe).getPublicFacet(sellItemsInstance); + const ticketsForSale = await E(ticketSalesPublicFacet).getAvailableItems(); t.deepEquals( ticketsForSale, { @@ -87,7 +81,7 @@ test(`mint and sell tickets for multiple shows`, async t => { `the tickets are up for sale`, ); - const { sellItemsInstanceHandle: sellItemsInstanceHandle2 } = await E( + const { sellItemsCreatorFacet: sellItemsCreatorFacet2 } = await E( ticketMaker, ).sellTokens({ customValueProperties: { @@ -96,13 +90,12 @@ test(`mint and sell tickets for multiple shows`, async t => { }, count: 2, moneyIssuer: moolaIssuer, - sellItemsInstallationHandle, + sellItemsInstallation, pricePerItem: moolaAmountMath.make(20), }); - const { publicAPI: salesPublicAPI2 } = await E(zoe).getInstanceRecord( - sellItemsInstanceHandle2, - ); - const ticketsForSale2 = await E(salesPublicAPI2).getAvailableItems(); + const sellItemsInstance2 = sellItemsCreatorFacet2.getInstance(); + const sellItemsPublicFacet2 = await E(zoe).getPublicFacet(sellItemsInstance2); + const ticketsForSale2 = await E(sellItemsPublicFacet2).getAvailableItems(); t.deepEquals( ticketsForSale2, { @@ -155,81 +148,68 @@ test(`mint and sell opera tickets`, async t => { const zoe = makeZoe(fakeVatAdmin); const mintAndSellNFTBundle = await bundleSource(mintAndSellNFTRoot); - const mintAndSellNFTInstallationHandle = await E(zoe).install( - mintAndSellNFTBundle, - ); + const mintAndSellNFTInstallation = await E(zoe).install(mintAndSellNFTBundle); const sellItemsBundle = await bundleSource(sellItemsRoot); - const sellItemsInstallationHandle = await E(zoe).install(sellItemsBundle); + const sellItemsInstallation = await E(zoe).install(sellItemsBundle); // === Initial Opera de Bordeaux part === // create an instance of the venue contract const mintTickets = async () => { - const { - instanceRecord: { publicAPI }, - invite, - } = await E(zoe).makeInstance(mintAndSellNFTInstallationHandle); - - const ticketIssuer = await E(publicAPI).getTokenIssuer(); - const { outcome } = await E(zoe).offer(invite); - const ticketSeller = await outcome; + const { creatorFacet: ticketSeller } = await E(zoe).makeInstance( + mintAndSellNFTInstallation, + ); - // completeObj exists because of a current limitation in @agoric/marshal : https://github.com/Agoric/agoric-sdk/issues/818 - const { - sellItemsInstanceHandle: ticketSalesInstanceHandle, - payout, - completeObj, - } = await E(ticketSeller).sellTokens({ + const { sellItemsCreatorSeat, sellItemsCreatorFacet } = await E( + ticketSeller, + ).sellTokens({ customValueProperties: { show: 'Steven Universe, the Opera', start: 'Wed, March 25th 2020 at 8pm', }, count: 3, moneyIssuer: moolaIssuer, - sellItemsInstallationHandle, + sellItemsInstallation, pricePerItem: moola(22), }); - const { publicAPI: ticketSalesPublicAPI } = await E(zoe).getInstanceRecord( - ticketSalesInstanceHandle, + const ticketSalesInstance = await E(sellItemsCreatorFacet).getInstance(); + const ticketSalesPublicFacet = await E(zoe).getPublicFacet( + ticketSalesInstance, ); - const ticketsForSale = await E(ticketSalesPublicAPI).getAvailableItems(); + const ticketsForSale = await E(ticketSalesPublicFacet).getAvailableItems(); t.equal(ticketsForSale.value.length, 3, `3 tickets for sale`); return harden({ - ticketIssuer, - ticketSalesInstanceHandle, - ticketSalesPublicAPI, - payoutP: payout, - completeObj, + sellItemsCreatorSeat, + sellItemsCreatorFacet, }); }; // === Alice part === - // Alice is given the instanceHandle of the ticket sales instance + // Alice is given an invitation for the ticket sales instance // and she has 100 moola - const aliceBuysTicket1 = async ( - ticketSalesInstanceHandle, - moola100Payment, - ) => { - const { publicAPI: ticketSalesPublicAPI, terms } = await E( - zoe, - ).getInstanceRecord(ticketSalesInstanceHandle); - const ticketIssuer = await E(ticketSalesPublicAPI).getItemsIssuer(); + const aliceBuysTicket1 = async (invitation, moola100Payment) => { + const invitationIssuer = E(zoe).getInvitationIssuer(); + const { + value: [{ instance }], + } = await E(invitationIssuer).getAmountOf(invitation); + const ticketSalesPublicFacet = await E(zoe).getPublicFacet(instance); + const terms = await E(zoe).getTerms(instance); + const ticketIssuer = await E(ticketSalesPublicFacet).getItemsIssuer(); const ticketAmountMath = await E(ticketIssuer).getAmountMath(); const alicePurse = await E(moolaIssuer).makeEmptyPurse(); await E(alicePurse).deposit(moola100Payment); - // Alice makes an invite for herself - const aliceInvite = await E(ticketSalesPublicAPI).makeBuyerInvite(); - t.deepEquals(terms.pricePerItem, moola(22), `pricePerItem is 22 moola`); - const availableTickets = await E(ticketSalesPublicAPI).getAvailableItems(); + const availableTickets = await E( + ticketSalesPublicFacet, + ).getAvailableItems(); t.equal( availableTickets.value.length, @@ -265,20 +245,20 @@ test(`mint and sell opera tickets`, async t => { const alicePaymentKeywordRecord = harden({ Money: alicePaymentForTicket }); - const { payout: payoutP } = await E(zoe).offer( - aliceInvite, + const seat = await E(zoe).offer( + invitation, aliceProposal, alicePaymentKeywordRecord, ); - const alicePayout = await payoutP; + const aliceTickets = seat.getPayout('Items'); const aliceBoughtTicketAmount = await E(ticketIssuer).getAmountOf( - alicePayout.Items, + aliceTickets, ); t.equal( aliceBoughtTicketAmount.value[0].show, 'Steven Universe, the Opera', - 'Alice should have receieved the ticket for the correct show', + 'Alice should have received the ticket for the correct show', ); t.equal( aliceBoughtTicketAmount.value[0].number, @@ -290,24 +270,22 @@ test(`mint and sell opera tickets`, async t => { // === Joker part === // Joker starts with 100 moolas // Joker attempts to buy ticket 1 (and should fail) - const jokerBuysTicket1 = async ( - ticketSalesInstanceHandle, - moola100Payment, - ) => { - const { publicAPI: ticketSalesPublicAPI } = await E(zoe).getInstanceRecord( - ticketSalesInstanceHandle, + const jokerBuysTicket1 = async (untrustedInvitation, moola100Payment) => { + const invitationIssuer = E(zoe).getInvitationIssuer(); + const invitation = await E(invitationIssuer).claim(untrustedInvitation); + const { + value: [{ instance: ticketSalesInstance }], + } = await E(invitationIssuer).getAmountOf(invitation); + const ticketSalesPublicFacet = await E(zoe).getPublicFacet( + ticketSalesInstance, ); - const ticketIssuer = await E(ticketSalesPublicAPI).getItemsIssuer(); + const ticketIssuer = await E(ticketSalesPublicFacet).getItemsIssuer(); const ticketAmountMath = await E(ticketIssuer).getAmountMath(); const jokerPurse = await E(moolaIssuer).makeEmptyPurse(); await E(jokerPurse).deposit(moola100Payment); - const jokerInvite = await E(ticketSalesPublicAPI).makeBuyerInvite(); - - const { - terms: { pricePerItem }, - } = await E(zoe).getInstanceRecord(ticketSalesInstanceHandle); + const { pricePerItem } = await E(zoe).getTerms(ticketSalesInstance); // Joker does NOT check available tickets and tries to buy the ticket // number 1(already bought by Alice, but he doesn't know) @@ -328,25 +306,29 @@ test(`mint and sell opera tickets`, async t => { const jokerPaymentForTicket = jokerPurse.withdraw(pricePerItem); - const { outcome, payout: payoutP } = await zoe.offer( - jokerInvite, + const seat = await zoe.offer( + invitation, jokerProposal, harden({ Money: jokerPaymentForTicket, }), ); + console.log( + 'EXPECTED ERROR: Some of the wanted items were not available for sale >>>', + ); t.rejects( - outcome, + seat.getOfferResult(), /Some of the wanted items were not available for sale/, 'ticket 1 is no longer available', ); - const payout = await payoutP; const jokerTicketPayoutAmount = await ticketIssuer.getAmountOf( - payout.Items, + seat.getPayout('Items'), + ); + const jokerMoneyPayoutAmount = await moolaIssuer.getAmountOf( + seat.getPayout('Money'), ); - const jokerMoneyPayoutAmount = await moolaIssuer.getAmountOf(payout.Money); t.ok( ticketAmountMath.isEmpty(jokerTicketPayoutAmount), @@ -361,20 +343,23 @@ test(`mint and sell opera tickets`, async t => { // Joker attempts to buy ticket 2 for 1 moola (and should fail) const jokerTriesToBuyTicket2 = async ( - ticketSalesInstanceHandle, + untrustedInvitation, moola100Payment, ) => { - const { publicAPI: ticketSalesPublicAPI } = await E(zoe).getInstanceRecord( - ticketSalesInstanceHandle, + const invitationIssuer = E(zoe).getInvitationIssuer(); + const invitation = await E(invitationIssuer).claim(untrustedInvitation); + const { + value: [{ instance: ticketSalesInstance }], + } = await E(invitationIssuer).getAmountOf(invitation); + const ticketSalesPublicFacet = await E(zoe).getPublicFacet( + ticketSalesInstance, ); - const ticketIssuer = await E(ticketSalesPublicAPI).getItemsIssuer(); + const ticketIssuer = await E(ticketSalesPublicFacet).getItemsIssuer(); const ticketAmountMath = await E(ticketIssuer).getAmountMath(); const jokerPurse = await E(moolaIssuer).makeEmptyPurse(); await E(jokerPurse).deposit(moola100Payment); - const jokerInvite = await E(ticketSalesPublicAPI).makeBuyerInvite(); - const ticket2Amount = ticketAmountMath.make( harden([ { @@ -395,24 +380,29 @@ test(`mint and sell opera tickets`, async t => { insufficientAmount, ); - const { outcome, payout: payoutP } = await zoe.offer( - jokerInvite, + const seat = await zoe.offer( + invitation, jokerProposal, harden({ Money: jokerInsufficientPaymentForTicket, }), ); + console.log( + 'EXPECTED ERROR: More money is required to buy these items >>> ', + ); t.rejects( - outcome, + seat.getOfferResult(), /More money.*is required to buy these items/, 'outcome from Joker should throw when trying to buy a ticket for 1 moola', ); - const payout = await payoutP; + const jokerTicketPayoutAmount = await ticketIssuer.getAmountOf( - payout.Items, + seat.getPayout('Items'), + ); + const jokerMoneyPayoutAmount = await moolaIssuer.getAmountOf( + seat.getPayout('Money'), ); - const jokerMoneyPayoutAmount = await moolaIssuer.getAmountOf(payout.Money); t.ok( ticketAmountMath.isEmpty(jokerTicketPayoutAmount), @@ -425,22 +415,26 @@ test(`mint and sell opera tickets`, async t => { ); }; - const bobBuysTicket2And3 = async ( - ticketSalesInstanceHandle, - moola100Payment, - ) => { - const { publicAPI: ticketSalesPublicAPI, terms } = await E( - zoe, - ).getInstanceRecord(ticketSalesInstanceHandle); - const ticketIssuer = await E(ticketSalesPublicAPI).getItemsIssuer(); + const bobBuysTicket2And3 = async (untrustedInvitation, moola100Payment) => { + const invitationIssuer = E(zoe).getInvitationIssuer(); + const invitation = await E(invitationIssuer).claim(untrustedInvitation); + const { + value: [{ instance: ticketSalesInstance }], + } = await E(invitationIssuer).getAmountOf(invitation); + const ticketSalesPublicFacet = await E(zoe).getPublicFacet( + ticketSalesInstance, + ); + const terms = await E(zoe).getTerms(ticketSalesInstance); + const ticketIssuer = await E(ticketSalesPublicFacet).getItemsIssuer(); const ticketAmountMath = await E(ticketIssuer).getAmountMath(); const bobPurse = await E(moolaIssuer).makeEmptyPurse(); await E(bobPurse).deposit(moola100Payment); - const bobInvite = await E(ticketSalesPublicAPI).makeBuyerInvite(); - - const availableTickets = await E(ticketSalesPublicAPI).getAvailableItems(); + /** @type {Amount} */ + const availableTickets = await E( + ticketSalesPublicFacet, + ).getAvailableItems(); // Bob sees the currently available tickets t.equal( @@ -480,13 +474,15 @@ test(`mint and sell opera tickets`, async t => { Money: bobPaymentForTicket, }); - const { payout: payoutP } = await E(zoe).offer( - bobInvite, + const seat = await E(zoe).offer( + invitation, bobProposal, paymentKeywordRecord, ); - const payout = await payoutP; - const bobTicketAmount = await E(ticketIssuer).getAmountOf(payout.Items); + + const bobTicketAmount = await E(ticketIssuer).getAmountOf( + seat.getPayout('Items'), + ); t.equal( bobTicketAmount.value.length, 2, @@ -503,13 +499,12 @@ test(`mint and sell opera tickets`, async t => { }; // === Final Opera part === - const ticketSellerClosesContract = async ({ - ticketIssuer, - ticketSalesPublicAPI, - payoutP, - completeObj, - }) => { - const availableTickets = await E(ticketSalesPublicAPI).getAvailableItems(); + const ticketSellerClosesContract = async ( + sellItemsCreatorSeat, + sellItemsCreatorFacet, + ) => { + const availableTickets = await E(sellItemsCreatorFacet).getAvailableItems(); + const ticketIssuer = await E(sellItemsCreatorFacet).getItemsIssuer(); const ticketAmountMath = await E(ticketIssuer).getAmountMath(); t.ok( ticketAmountMath.isEmpty(availableTickets), @@ -518,10 +513,9 @@ test(`mint and sell opera tickets`, async t => { const operaPurse = moolaIssuer.makeEmptyPurse(); - await E(completeObj).complete(); + await E(sellItemsCreatorSeat).exit(); - const payout = await payoutP; - const moneyPayment = await payout.Money; + const moneyPayment = await E(sellItemsCreatorSeat).getPayout('Money'); await E(operaPurse).deposit(moneyPayment); const currentPurseBalance = await E(operaPurse).getCurrentAmount(); @@ -532,34 +526,27 @@ test(`mint and sell opera tickets`, async t => { ); }; - const { - ticketIssuer, - ticketSalesInstanceHandle, - ticketSalesPublicAPI, - payoutP, - completeObj, - } = await mintTickets(); + const { sellItemsCreatorSeat, sellItemsCreatorFacet } = await mintTickets(); + const ticketSalesInvitation1 = E(sellItemsCreatorFacet).makeBuyerInvite(); await aliceBuysTicket1( - ticketSalesInstanceHandle, + ticketSalesInvitation1, moolaMint.mintPayment(moola(100)), ); + const ticketSalesInvitation2 = E(sellItemsCreatorFacet).makeBuyerInvite(); await jokerBuysTicket1( - ticketSalesInstanceHandle, + ticketSalesInvitation2, moolaMint.mintPayment(moola(100)), ); + const ticketSalesInvitation3 = E(sellItemsCreatorFacet).makeBuyerInvite(); await jokerTriesToBuyTicket2( - ticketSalesInstanceHandle, + ticketSalesInvitation3, moolaMint.mintPayment(moola(100)), ); + const ticketSalesInvitation4 = E(sellItemsCreatorFacet).makeBuyerInvite(); await bobBuysTicket2And3( - ticketSalesInstanceHandle, + ticketSalesInvitation4, moolaMint.mintPayment(moola(100)), ); - await ticketSellerClosesContract({ - ticketIssuer, - ticketSalesPublicAPI, - payoutP, - completeObj, - }); + await ticketSellerClosesContract(sellItemsCreatorSeat, sellItemsCreatorFacet); t.end(); }); From f0dc9db9925cd75cfdbac4bd45e79acd1ad18133 Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Fri, 7 Aug 2020 15:14:05 -0700 Subject: [PATCH 06/31] refactor: fix jsdoc to recognize makeZCFMint as property --- packages/zoe/src/types.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 3ca4fbcf663..5356feae584 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -190,7 +190,6 @@ * @property {() => InstanceRecord } getInstanceRecord * @property {(issuer: Issuer) => Brand} getBrandForIssuer * @property {GetAmountMath} getAmountMath - * * @property {MakeZCFMint} makeZCFMint */ From 75acfffd01f1f6e9ed1ca4db7b3b2a108db18414 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Fri, 7 Aug 2020 16:58:34 -0700 Subject: [PATCH 07/31] fix: register the new minty issuer on zcf side (#1392) --- .../zoe/src/contractFacet/contractFacet.js | 76 +++++++++++-------- packages/zoe/src/internal-types.js | 8 +- packages/zoe/src/types.js | 11 +-- packages/zoe/src/zoeService/zoe.js | 11 +-- 4 files changed, 60 insertions(+), 46 deletions(-) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 7f7f74aee70..03ccc0f6b13 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -30,9 +30,8 @@ export function buildRootObject() { zoeService, invitationIssuer, zoeInstanceAdmin, - hardenedInstanceRecord, + instanceRecord, ) => { - const instanceRecord = { ...hardenedInstanceRecord }; const issuerTable = makeIssuerTable(); const getAmountMath = brand => issuerTable.get(brand).amountMath; @@ -50,14 +49,41 @@ export function buildRootObject() { const allSeatStagings = new WeakSet(); + const registerIssuerRecord = (keyword, issuerRecord) => { + assertKeywordName(keyword); + assert( + !getKeywords(instanceRecord.issuerKeywordRecord).includes(keyword), + details`keyword ${keyword} must be unique`, + ); + instanceRecord = { + ...instanceRecord, + issuerKeywordRecord: { + ...instanceRecord.issuerKeywordRecord, + [keyword]: issuerRecord.issuer, + }, + brandKeywordRecord: { + ...instanceRecord.brandKeywordRecord, + [keyword]: issuerRecord.brand, + }, + }; + + return issuerRecord; + }; + /** @type MakeZCFMint */ const makeZCFMint = async (keyword, mathHelperName = 'nat') => { + assert( + !(keyword in instanceRecord.issuerKeywordRecord), + details`Keyword ${keyword} already registered`, + ); + const zoeMintP = E(zoeInstanceAdmin).makeZoeMint(keyword, mathHelperName); const mintyIssuerRecord = await E(zoeMintP).getIssuerRecord(); // AWAIT const { brand: mintyBrand } = mintyIssuerRecord; const mintyAmountMath = makeAmountMath(mintyBrand, mathHelperName); - // TODO some zcf-side registration of the new issuer + registerIssuerRecord(keyword, mintyIssuerRecord); + issuerTable.registerIssuerRecord(mintyIssuerRecord); /** @type ZCFMint */ const zcfMint = harden({ @@ -87,10 +113,13 @@ export function buildRootObject() { const zcfSeatAdmin = seatToZCFSeatAdmin.get(zcfSeat); // verifies offer safety const seatStaging = zcfSeat.stage(newAllocation); - // No effects above. Commit point. Must mint and commit atomically. - // (Not really. If we minted only, no one would ever get those - // invisibly-minted assets.) - E(zoeMintP).mintGains(totalToMint); + // No effects above. Commit point. The following two steps + // *should* be committed atomically. + // But unlike https://github.com/Agoric/agoric-sdk/issues/1391 + // it is not a disater if they are not. + // If we minted only, no one would ever get those + // invisibly-minted assets. + E(zoeMintP).mintAndEscrow(totalToMint); zcfSeatAdmin.commit(seatStaging); return zcfSeat; }, @@ -116,11 +145,14 @@ export function buildRootObject() { const zcfSeatAdmin = seatToZCFSeatAdmin.get(zcfSeat); // verifies offer safety const seatStaging = zcfSeat.stage(newAllocation); - // No effects above. Commit point. Must commit and burn atomically. - // (Not really. If we committed only, no one would ever get the - // unburned assets.) + // No effects above. Commit point. The following two steps + // *should* be committed atomically. + // But unlike https://github.com/Agoric/agoric-sdk/issues/1391 + // it is not a disater if they are not. + // If we only commit the staging, no one would ever get the + // unburned assets. zcfSeatAdmin.commit(seatStaging); - E(zoeMintP).burnLosses(totalToBurn); + E(zoeMintP).withdrawAndBurn(totalToBurn); }, }); return zcfMint; @@ -176,25 +208,7 @@ export function buildRootObject() { .then(() => { return issuerTable .getPromiseForIssuerRecord(issuerP) - .then(issuerRecord => { - assertKeywordName(keyword); - assert( - !getKeywords(instanceRecord.issuerKeywordRecord).includes( - keyword, - ), - details`keyword ${keyword} must be unique`, - ); - instanceRecord.issuerKeywordRecord = { - ...instanceRecord.issuerKeywordRecord, - [keyword]: issuerRecord.issuer, - }; - instanceRecord.brandKeywordRecord = { - ...instanceRecord.brandKeywordRecord, - [keyword]: issuerRecord.brand, - }; - - return issuerRecord; - }); + .then(registerIssuerRecord); }); }, makeInvitation: (offerHandler, description, customProperties = {}) => { @@ -220,7 +234,7 @@ export function buildRootObject() { // The methods below are pure and have no side-effects // getZoeService: () => zoeService, getInvitationIssuer: () => invitationIssuer, - getInstanceRecord: () => harden({ ...instanceRecord }), + getInstanceRecord: () => instanceRecord, getBrandForIssuer: issuer => issuerTable.getIssuerRecordByIssuer(issuer).brand, getAmountMath, diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index b1081abcd20..fd793d11e79 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -126,8 +126,12 @@ /** * @typedef {Object} ZoeMint * @property {() => IssuerRecord} getIssuerRecord - * @property {(totalToMint: Amount) => void} mintGains - * @property {(totalToBurn: Amount) => void} burnLosses + * @property {(totalToMint: Amount) => void} mintAndEscrow + * @property {(totalToBurn: Amount) => void} withdrawAndBurn + * Note that the burning is asynchronous, and so may not have happened by + * the time withdrawAndBurn returns. We rely on our other bookkeeping so that + * these assets are assumed burned elsewhere, so no one will try to access + * them even before they are actually burned. */ /** diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 5356feae584..5e90b342add 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -10,14 +10,14 @@ * @template {string} H - the name of the handle * @typedef {H & {}} Handle A type constructor for an opaque type identified by * the H string. - * This uses an intersection type ('MyHandle' & {}) to tag the handle's type even - * though the - * actual value is just an empty object. + * This uses an intersection type ('MyHandle' & {}) to tag the handle's type + * even though the actual value is just an empty object. */ /** * @typedef {string} Keyword - * @typedef {Handle<'InvitationHandle'>} InvitationHandle - an opaque handle for an invite + * @typedef {Handle<'InvitationHandle'>} InvitationHandle + * - an opaque handle for an invite * @typedef {Record} IssuerKeywordRecord * @typedef {Record} BrandKeywordRecord * @typedef {Record} PaymentKeywordRecord @@ -334,7 +334,8 @@ * * @typedef {object} InstanceRecord * @property {Object} terms - contract parameters - * @property {IssuerKeywordRecord} issuerKeywordRecord - record with keywords keys, issuer values + * @property {IssuerKeywordRecord} issuerKeywordRecord + * - record with keywords keys, issuer values * @property {BrandKeywordRecord} brandKeywordRecord - record with * keywords keys, brand values * diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index 17c6e8a4501..e73bdb12666 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -143,11 +143,6 @@ function makeZoe(vatAdminSvc) { /** @type MakeZoeMint */ const makeZoeMint = (keyword, mathHelperName = 'nat') => { - assert( - !(keyword in instanceRecord.issuerKeywordRecord), - details`Keyword ${keyword} already registered`, - ); - // Local indicates one that zoe itself makes from vetted code, // and so can be assumed correct and fresh by zoe. const { @@ -171,11 +166,11 @@ function makeZoe(vatAdminSvc) { getIssuerRecord: () => { return localIssuerRecord; }, - mintGains: totalToMint => { + mintAndEscrow: totalToMint => { const payment = localMint.mintPayment(totalToMint); localPooledPurse.deposit(payment, totalToMint); }, - burnLosses: totalToBurn => { + withdrawAndBurn: totalToBurn => { const payment = localPooledPurse.withdraw(totalToBurn); localIssuer.burn(payment, totalToBurn); }, @@ -199,7 +194,7 @@ function makeZoe(vatAdminSvc) { ); return invitationMint.mintPayment(invitationAmount); }, - // checks if keyword done on zcf side + // checks of keyword done on zcf side saveIssuer: (issuerP, keyword) => issuerTable .getPromiseForIssuerRecord(issuerP) From 48e6119c060ca48ad62928168eb938ac8c5a3257 Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Fri, 7 Aug 2020 17:20:43 -0700 Subject: [PATCH 08/31] refactor: redirect main in package.json --- packages/zoe/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zoe/package.json b/packages/zoe/package.json index d17c0a941c6..e26bfa1b84c 100644 --- a/packages/zoe/package.json +++ b/packages/zoe/package.json @@ -2,7 +2,7 @@ "name": "@agoric/zoe", "version": "0.7.0", "description": "Zoe: the Smart Contract Framework for Offer Enforcement", - "main": "src/zoe.js", + "main": "src/zoeService/zoe.js", "engines": { "node": ">=11.0" }, From 4585c4545a9913ad327ae666747e4bf8be658dfd Mon Sep 17 00:00:00 2001 From: Dean Tribble Date: Fri, 7 Aug 2020 20:32:30 -0700 Subject: [PATCH 09/31] refactor: apply zoe renames - `getInviteIssuer` to `getInvitationIssuer` - `inviteDesc` to `description` --- packages/cosmic-swingset/test/test-home.js | 4 +-- .../dapp-svelte-wallet/api/src/lib-wallet.js | 2 +- .../api/test/test-lib-wallet.js | 30 +++++++++---------- packages/spawner/src/contractHost.chainmail | 2 +- packages/spawner/src/contractHost.js | 2 +- .../swingsetTests/contractHost/bootstrap.js | 2 +- .../swingsetTests/contractHost/vat-alice.js | 2 +- .../swingsetTests/contractHost/vat-fred.js | 2 +- .../demo/exchangeBenchmark/exchanger.js | 2 +- .../swingset-runner/demo/zoeTests/helpers.js | 2 +- .../swingset-runner/demo/zoeTests/vat-bob.js | 6 ++-- .../demo/zoeTests/vat-carol.js | 2 +- .../swingset-runner/demo/zoeTests/vat-dave.js | 4 +-- .../brokenContracts/vat-alice.js | 2 +- packages/zoe/test/swingsetTests/helpers.js | 2 +- .../swingsetTests/zoe-metering/bootstrap.js | 2 +- .../zoe/test/swingsetTests/zoe/vat-bob.js | 6 ++-- .../zoe/test/swingsetTests/zoe/vat-carol.js | 2 +- .../zoe/test/swingsetTests/zoe/vat-dave.js | 4 +-- .../contracts/test-automaticRefund.js | 4 +-- .../test/unitTests/contracts/test-autoswap.js | 4 +-- .../unitTests/contracts/test-coveredCall.js | 20 ++++++------- .../contracts/test-multipoolAutoswap.js | 4 +-- .../unitTests/contracts/test-publicAuction.js | 2 +- 24 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/cosmic-swingset/test/test-home.js b/packages/cosmic-swingset/test/test-home.js index 6e9652cf630..57219c94ff6 100644 --- a/packages/cosmic-swingset/test/test-home.js +++ b/packages/cosmic-swingset/test/test-home.js @@ -79,7 +79,7 @@ test('home.wallet - receive zoe invite', async t => { // Check that the wallet knows about the Zoe invite issuer and starts out // with a default Zoe invite issuer purse. - const zoeInviteIssuer = await E(zoe).getInviteIssuer(); + const zoeInviteIssuer = await E(zoe).getInvitationIssuer(); const issuers = await E(wallet).getIssuers(); const issuersMap = new Map(issuers); t.deepEquals( @@ -108,7 +108,7 @@ test('home.wallet - receive zoe invite', async t => { // The invite was successfully received in the user's wallet. const invitePurseBalance = await E(invitePurse).getCurrentAmount(); t.equals( - invitePurseBalance.value[0].inviteDesc, + invitePurseBalance.value[0].description, 'getRefund', `invite successfully deposited`, ); diff --git a/packages/dapp-svelte-wallet/api/src/lib-wallet.js b/packages/dapp-svelte-wallet/api/src/lib-wallet.js index 2af8d088384..9667773bd0a 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/src/lib-wallet.js @@ -1243,7 +1243,7 @@ export async function makeWallet({ // Make Zoe invite purse const ZOE_INVITE_BRAND_PETNAME = 'zoe invite'; const ZOE_INVITE_PURSE_PETNAME = 'Default Zoe invite purse'; - const inviteIssuerP = E(zoe).getInviteIssuer(); + const inviteIssuerP = E(zoe).getInvitationIssuer(); const addZoeIssuer = issuerP => wallet.addIssuer(ZOE_INVITE_BRAND_PETNAME, issuerP); const makeInvitePurse = () => diff --git a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js index d351a3967cd..33041466244 100644 --- a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js @@ -96,7 +96,7 @@ test('lib-wallet issuer and purse methods', async t => { inboxStateChangeLog, pursesStateChangeLog, } = await setupTest(); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); t.deepEquals( wallet.getIssuers(), [['zoe invite', inviteIssuer]], @@ -230,7 +230,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t // that we withdraw an invite for the offer during the // `addOffer` call, so any invites associated with an offer are no // longer in the purse. - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); const zoeInvitePurse = await E(wallet).getPurse('Default Zoe invite purse'); const { value: [{ handle: inviteHandle, installationHandle }], @@ -274,7 +274,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t currentAmount.value, [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: inviteHandle2, instanceHandle, installationHandle, @@ -295,7 +295,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t pursePetname: 'Default Zoe invite purse', value: [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: {}, instanceHandle: {}, installationHandle: {}, @@ -303,7 +303,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t ], currentAmountSlots: { body: - '{"brand":{"@qclass":"slot","index":0},"value":[{"inviteDesc":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', + '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', slots: [ { kind: 'brand', petname: 'zoe invite' }, { kind: 'unnamed', petname: 'unnamed-1' }, @@ -315,7 +315,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t brand: { kind: 'brand', petname: 'zoe invite' }, value: [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: { kind: 'unnamed', petname: 'unnamed-1' }, instanceHandle: { kind: 'unnamed', petname: 'unnamed-2' }, installationHandle: { kind: 'unnamed', petname: 'unnamed-3' }, @@ -376,7 +376,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t pursePetname: 'Default Zoe invite purse', value: [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: {}, instanceHandle: {}, installationHandle: {}, @@ -384,7 +384,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t ], currentAmountSlots: { body: - '{"brand":{"@qclass":"slot","index":0},"value":[{"inviteDesc":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', + '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', slots: [ { kind: 'brand', petname: 'zoe invite' }, { kind: 'unnamed', petname: 'unnamed-4' }, @@ -396,7 +396,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t brand: { kind: 'brand', petname: 'zoe invite' }, value: [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: { kind: 'unnamed', petname: 'unnamed-4' }, instanceHandle: { kind: 'instance', petname: 'automaticRefund' }, installationHandle: { @@ -457,7 +457,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t currentAmount2.value, [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: inviteHandle2, instanceHandle, installationHandle, @@ -479,7 +479,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t pursePetname: 'Default Zoe invite purse', value: [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: inviteHandle2, instanceHandle, installationHandle, @@ -487,7 +487,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t ], currentAmountSlots: { body: - '{"brand":{"@qclass":"slot","index":0},"value":[{"inviteDesc":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', + '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', slots: [ { kind: 'brand', petname: 'zoe invite' }, { kind: 'unnamed', petname: 'unnamed-4' }, @@ -499,7 +499,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t brand: { kind: 'brand', petname: 'zoe invite' }, value: [ { - inviteDesc: 'getRefund', + description: 'getRefund', handle: { kind: 'unnamed', petname: 'unnamed-4' }, instanceHandle: { kind: 'instance', petname: 'automaticRefund' }, installationHandle: { @@ -556,7 +556,7 @@ test('lib-wallet offer methods', async t => { moolaBundle.mint.mintPayment(moolaBundle.amountMath.make(100)), ); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); const { value: [{ handle: inviteHandle, installationHandle }], } = await E(inviteIssuer).getAmountOf(invite); @@ -833,7 +833,7 @@ test('lib-wallet addOffer for autoswap swap', async t => { await addLiqOutcome; const invite = await E(publicAPI).makeSwapInvite(); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); const { value: [{ handle: inviteHandle }], } = await E(inviteIssuer).getAmountOf(invite); diff --git a/packages/spawner/src/contractHost.chainmail b/packages/spawner/src/contractHost.chainmail index c359c2cdd30..f64171da65e 100644 --- a/packages/spawner/src/contractHost.chainmail +++ b/packages/spawner/src/contractHost.chainmail @@ -32,7 +32,7 @@ interface ContractHost { * The issuer allows holders of seat invitations to get exclusive access to a * Seat. */ - getInviteIssuer() -> (Issuer); + getInvitationIssuer() -> (Issuer); } /** diff --git a/packages/spawner/src/contractHost.js b/packages/spawner/src/contractHost.js index b043522dd95..eb7c86e1780 100644 --- a/packages/spawner/src/contractHost.js +++ b/packages/spawner/src/contractHost.js @@ -75,7 +75,7 @@ function makeContractHost(vatPowers, additionalEndowments = {}) { /** The contract host is designed to have a long-lived credible identity. */ const contractHost = harden({ - getInviteIssuer() { + getInvitationIssuer() { return inviteIssuer; }, // contractBundle is a record containing source code for the functions diff --git a/packages/spawner/test/swingsetTests/contractHost/bootstrap.js b/packages/spawner/test/swingsetTests/contractHost/bootstrap.js index 2013593355a..1b290bcbcbb 100644 --- a/packages/spawner/test/swingsetTests/contractHost/bootstrap.js +++ b/packages/spawner/test/swingsetTests/contractHost/bootstrap.js @@ -78,7 +78,7 @@ export function buildRootObject(vatPowers, vatParameters) { const fooInviteP = E(installationP).spawn('foo terms'); - const inviteIssuerP = E(host).getInviteIssuer(); + const inviteIssuerP = E(host).getInvitationIssuer(); return Promise.resolve( showPaymentBalance('foo', inviteIssuerP, fooInviteP), ).then(_ => { diff --git a/packages/spawner/test/swingsetTests/contractHost/vat-alice.js b/packages/spawner/test/swingsetTests/contractHost/vat-alice.js index f5f97a0161c..a377c28e122 100644 --- a/packages/spawner/test/swingsetTests/contractHost/vat-alice.js +++ b/packages/spawner/test/swingsetTests/contractHost/vat-alice.js @@ -42,7 +42,7 @@ function makeAliceMaker(host, log) { myOptFinPurseP = undefined, optFredP = undefined, ) { - const inviteIssuerP = E(host).getInviteIssuer(); + const inviteIssuerP = E(host).getInvitationIssuer(); const moneyMath = await getLocalAmountMath(moneyIssuerP); const stockMath = await getLocalAmountMath(stockIssuerP); diff --git a/packages/spawner/test/swingsetTests/contractHost/vat-fred.js b/packages/spawner/test/swingsetTests/contractHost/vat-fred.js index e23f69030ab..297c8c51c10 100644 --- a/packages/spawner/test/swingsetTests/contractHost/vat-fred.js +++ b/packages/spawner/test/swingsetTests/contractHost/vat-fred.js @@ -31,7 +31,7 @@ function makeFredMaker(host, log) { myStockPaymentP, myFinPaymentP, ) { - const inviteIssuerP = E(host).getInviteIssuer(); + const inviteIssuerP = E(host).getInvitationIssuer(); const myMoneyPurseP = E(moneyIssuerP).makeEmptyPurse(); const myStockPurseP = E(stockIssuerP).makeEmptyPurse(); const myFinPurseP = E(finIssuerP).makeEmptyPurse(); diff --git a/packages/swingset-runner/demo/exchangeBenchmark/exchanger.js b/packages/swingset-runner/demo/exchangeBenchmark/exchanger.js index 636c315c57e..98f85bb3655 100755 --- a/packages/swingset-runner/demo/exchangeBenchmark/exchanger.js +++ b/packages/swingset-runner/demo/exchangeBenchmark/exchanger.js @@ -19,7 +19,7 @@ async function build(name, zoe, issuers, payments, installations) { Price: simoleanIssuer, Asset: moolaIssuer, }); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); const { simpleExchange } = installations; async function preReport() { diff --git a/packages/swingset-runner/demo/zoeTests/helpers.js b/packages/swingset-runner/demo/zoeTests/helpers.js index 6007e571688..519e4c159d2 100644 --- a/packages/swingset-runner/demo/zoeTests/helpers.js +++ b/packages/swingset-runner/demo/zoeTests/helpers.js @@ -20,7 +20,7 @@ export const getLocalAmountMath = issuer => export const setupIssuers = async (zoe, issuers) => { const purses = issuers.map(issuer => E(issuer).makeEmptyPurse()); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; const moolaAmountMath = await getLocalAmountMath(moolaIssuer); diff --git a/packages/swingset-runner/demo/zoeTests/vat-bob.js b/packages/swingset-runner/demo/zoeTests/vat-bob.js index de8f8df9094..826a4d88d9b 100644 --- a/packages/swingset-runner/demo/zoeTests/vat-bob.js +++ b/packages/swingset-runner/demo/zoeTests/vat-bob.js @@ -20,7 +20,7 @@ const build = async (zoe, issuers, payments, installations, timer) => { const [moolaPurseP, simoleanPurseP, bucksPurseP] = purses; const [moolaPayment, simoleanPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); return harden({ doAutomaticRefund: async inviteP => { @@ -101,7 +101,7 @@ const build = async (zoe, issuers, payments, installations, timer) => { details`wrong installation`, ); assert( - optionValue[0].inviteDesc === 'exerciseOption', + optionValue[0].description === 'exerciseOption', details`wrong invite`, ); assert(moolaAmountMath.isEqual(optionValue[0].underlyingAsset, moola(3))); @@ -162,7 +162,7 @@ const build = async (zoe, issuers, payments, installations, timer) => { details`wrong installation`, ); assert( - optionValue[0].inviteDesc === 'exerciseOption', + optionValue[0].description === 'exerciseOption', details`wrong invite`, ); assert( diff --git a/packages/swingset-runner/demo/zoeTests/vat-carol.js b/packages/swingset-runner/demo/zoeTests/vat-carol.js index bb05272c7f2..5ee811b0c02 100644 --- a/packages/swingset-runner/demo/zoeTests/vat-carol.js +++ b/packages/swingset-runner/demo/zoeTests/vat-carol.js @@ -13,7 +13,7 @@ const build = async (zoe, issuers, payments, installations) => { const [moolaPurseP, simoleanPurseP] = purses; const [_moolaPayment, simoleanPayment] = payments; const [moolaIssuer, simoleanIssuer] = issuers; - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); return harden({ doPublicAuction: async inviteP => { diff --git a/packages/swingset-runner/demo/zoeTests/vat-dave.js b/packages/swingset-runner/demo/zoeTests/vat-dave.js index 2930eec1ca0..ea9966b4b91 100644 --- a/packages/swingset-runner/demo/zoeTests/vat-dave.js +++ b/packages/swingset-runner/demo/zoeTests/vat-dave.js @@ -20,7 +20,7 @@ const build = async (zoe, issuers, payments, installations, timer) => { const [moolaPurseP, simoleanPurseP, bucksPurseP] = purses; const [_moolaPayment, simoleanPayment, bucksPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); return harden({ doPublicAuction: async inviteP => { @@ -129,7 +129,7 @@ const build = async (zoe, issuers, payments, installations, timer) => { ); const optionValue = optionAmounts.value; assert( - optionValue[0].inviteDesc === 'exerciseOption', + optionValue[0].description === 'exerciseOption', details`wrong invite`, ); assert( diff --git a/packages/zoe/test/swingsetTests/brokenContracts/vat-alice.js b/packages/zoe/test/swingsetTests/brokenContracts/vat-alice.js index 89bb4da3491..8506ae3041f 100644 --- a/packages/zoe/test/swingsetTests/brokenContracts/vat-alice.js +++ b/packages/zoe/test/swingsetTests/brokenContracts/vat-alice.js @@ -271,7 +271,7 @@ const build = async (log, zoe, issuers, payments, installations) => { aliceSwapPayments, ); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); let swapInviteTwo; swapOutcome.then( o => { diff --git a/packages/zoe/test/swingsetTests/helpers.js b/packages/zoe/test/swingsetTests/helpers.js index a2a673b10d0..3bcaa672131 100644 --- a/packages/zoe/test/swingsetTests/helpers.js +++ b/packages/zoe/test/swingsetTests/helpers.js @@ -18,7 +18,7 @@ export const getLocalAmountMath = issuer => export const setupIssuers = async (zoe, issuers) => { const purses = issuers.map(issuer => E(issuer).makeEmptyPurse()); - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; const moolaAmountMath = await getLocalAmountMath(moolaIssuer); diff --git a/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js b/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js index e644438ebb3..c41722cc9de 100644 --- a/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js +++ b/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js @@ -31,7 +31,7 @@ export function buildRootObject(vatPowers, vatParameters) { try { const installId = await installations[testName](); log(`instantiating ${testName}`); - const inviteIssuer = E(zoe).getInviteIssuer(); + const inviteIssuer = E(zoe).getInvitationIssuer(); const issuerKeywordRecord = harden({ Keyword1: inviteIssuer }); const { instanceRecord: { publicAPI }, diff --git a/packages/zoe/test/swingsetTests/zoe/vat-bob.js b/packages/zoe/test/swingsetTests/zoe/vat-bob.js index 78d9e06f3c7..8baf62ef0c6 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-bob.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-bob.js @@ -15,7 +15,7 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { const [moolaPurseP, simoleanPurseP, bucksPurseP] = purses; const [moolaPayment, simoleanPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); return harden({ doAutomaticRefund: async inviteP => { @@ -94,7 +94,7 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { details`wrong installation`, ); assert( - optionValue[0].inviteDesc === 'exerciseOption', + optionValue[0].description === 'exerciseOption', details`wrong invite`, ); assert(moolaAmountMath.isEqual(optionValue[0].underlyingAsset, moola(3))); @@ -155,7 +155,7 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { details`wrong installation`, ); assert( - optionValue[0].inviteDesc === 'exerciseOption', + optionValue[0].description === 'exerciseOption', details`wrong invite`, ); assert( diff --git a/packages/zoe/test/swingsetTests/zoe/vat-carol.js b/packages/zoe/test/swingsetTests/zoe/vat-carol.js index 60d08771393..c5efaacc6c5 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-carol.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-carol.js @@ -8,7 +8,7 @@ const build = async (log, zoe, issuers, payments, installations) => { const [moolaPurseP, simoleanPurseP] = purses; const [_moolaPayment, simoleanPayment] = payments; const [moolaIssuer, simoleanIssuer] = issuers; - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); return harden({ doPublicAuction: async inviteP => { diff --git a/packages/zoe/test/swingsetTests/zoe/vat-dave.js b/packages/zoe/test/swingsetTests/zoe/vat-dave.js index 80e4ab07c52..27ea8a22137 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-dave.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-dave.js @@ -15,7 +15,7 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { const [moolaPurseP, simoleanPurseP, bucksPurseP] = purses; const [_moolaPayment, simoleanPayment, bucksPayment] = payments; const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; - const inviteIssuer = await E(zoe).getInviteIssuer(); + const inviteIssuer = await E(zoe).getInvitationIssuer(); return harden({ doPublicAuction: async inviteP => { @@ -124,7 +124,7 @@ const build = async (log, zoe, issuers, payments, installations, timer) => { ); const optionValue = optionAmounts.value; assert( - optionValue[0].inviteDesc === 'exerciseOption', + optionValue[0].description === 'exerciseOption', details`wrong invite`, ); assert( diff --git a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js index 012096dd215..51ef70cc0d6 100644 --- a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js +++ b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js @@ -112,7 +112,7 @@ test('zoe with automaticRefund', async t => { // Setup zoe and mints const { moolaR, simoleanR, moola, simoleans } = setup(); const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); @@ -279,7 +279,7 @@ test('multiple instances of automaticRefund for the same Zoe', async t => { ContributionA: moolaR.issuer, ContributionB: simoleanR.issuer, }); - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); const { invite: aliceInvite1 } = await zoe.makeInstance( installationHandle, issuerKeywordRecord, diff --git a/packages/zoe/test/unitTests/contracts/test-autoswap.js b/packages/zoe/test/unitTests/contracts/test-autoswap.js index 981a03fefc4..606418fd85f 100644 --- a/packages/zoe/test/unitTests/contracts/test-autoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-autoswap.js @@ -24,7 +24,7 @@ test('autoSwap with valid offers', async t => { simoleans, } = setup(); const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(10)); @@ -255,7 +255,7 @@ test('autoSwap - test fee', async t => { simoleans, } = setup(); const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(10000)); diff --git a/packages/zoe/test/unitTests/contracts/test-coveredCall.js b/packages/zoe/test/unitTests/contracts/test-coveredCall.js index eb9823addfa..8337e585314 100644 --- a/packages/zoe/test/unitTests/contracts/test-coveredCall.js +++ b/packages/zoe/test/unitTests/contracts/test-coveredCall.js @@ -245,7 +245,7 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a // contract instance that he expects as well as that Alice has // already escrowed. - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); const bobExclOption = await inviteIssuer.claim(optionP); const { value: [optionValue], @@ -254,7 +254,7 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a optionValue.instanceHandle, ); t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.inviteDesc, 'exerciseOption'); + t.equal(optionValue.description, 'exerciseOption'); t.ok(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); t.ok(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); t.equal(optionValue.expirationDate, 1); @@ -399,7 +399,7 @@ test('zoe - coveredCall with swap for invite', async t => { // party in the covered call: Did the covered call use the // expected covered call installation (code)? Does it use the issuers // that he expects (moola and simoleans)? - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); const inviteAmountMath = inviteIssuer.getAmountMath(); const bobExclOption = await inviteIssuer.claim(optionP); const optionAmount = await inviteIssuer.getAmountOf(bobExclOption); @@ -408,7 +408,7 @@ test('zoe - coveredCall with swap for invite', async t => { optionDesc.instanceHandle, ); t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionDesc.inviteDesc, 'exerciseOption'); + t.equal(optionDesc.description, 'exerciseOption'); t.ok(moolaR.amountMath.isEqual(optionDesc.underlyingAsset, moola(3))); t.ok(simoleanR.amountMath.isEqual(optionDesc.strikePrice, simoleans(7))); t.equal(optionDesc.expirationDate, 100); @@ -661,7 +661,7 @@ test('zoe - coveredCall with coveredCall for invite', async t => { // party in the covered call: Did the covered call use the // expected covered call installation (code)? Does it use the issuers // that he expects (moola and simoleans)? - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); const inviteAmountMath = inviteIssuer.getAmountMath(); const bobExclOption = await inviteIssuer.claim(optionP); const { @@ -671,7 +671,7 @@ test('zoe - coveredCall with coveredCall for invite', async t => { optionValue.instanceHandle, ); t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.inviteDesc, 'exerciseOption'); + t.equal(optionValue.description, 'exerciseOption'); t.ok(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); t.ok(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); t.equal(optionValue.expirationDate, 100); @@ -725,14 +725,14 @@ test('zoe - coveredCall with coveredCall for invite', async t => { installationHandle: daveOptionInstallationHandle, } = zoe.getInstanceRecord(daveOptionValue.instanceHandle); t.equal(daveOptionInstallationHandle, coveredCallInstallationHandle); - t.equal(daveOptionValue.inviteDesc, 'exerciseOption'); + t.equal(daveOptionValue.description, 'exerciseOption'); t.ok(bucksR.amountMath.isEqual(daveOptionValue.strikePrice, bucks(1))); t.equal(daveOptionValue.expirationDate, 100); t.deepEqual(daveOptionValue.timerAuthority, timer); // What about the underlying asset (the other option)? t.equal( - daveOptionValue.underlyingAsset.value[0].inviteDesc, + daveOptionValue.underlyingAsset.value[0].description, 'exerciseOption', ); t.equal(daveOptionValue.underlyingAsset.value[0].expirationDate, 100); @@ -928,7 +928,7 @@ test('zoe - coveredCall non-fungible', async t => { // contract instance that he expects as well as that Alice has // already escrowed. - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); const bobExclOption = await inviteIssuer.claim(optionP); const { value: [optionValue], @@ -937,7 +937,7 @@ test('zoe - coveredCall non-fungible', async t => { optionValue.instanceHandle, ); t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.inviteDesc, 'exerciseOption'); + t.equal(optionValue.description, 'exerciseOption'); t.ok( amountMaths .get('cc') diff --git a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js index ada073357b6..ff30d1de922 100644 --- a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js @@ -20,7 +20,7 @@ test('multipoolAutoSwap with valid offers', async t => { try { const { moolaR, simoleanR, moola, simoleans } = setup(); const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); // Set up central token const centralR = makeIssuerKit('central'); @@ -62,7 +62,7 @@ test('multipoolAutoSwap with valid offers', async t => { inviteAmountMath.make( harden([ { - inviteDesc: 'multipool autoswap add liquidity', + description: 'multipool autoswap add liquidity', instanceHandle: aliceInviteAmount.value[0].instanceHandle, installationHandle, handle: aliceInviteAmount.value[0].handle, diff --git a/packages/zoe/test/unitTests/contracts/test-publicAuction.js b/packages/zoe/test/unitTests/contracts/test-publicAuction.js index 4e38572a779..d20e12a2e02 100644 --- a/packages/zoe/test/unitTests/contracts/test-publicAuction.js +++ b/packages/zoe/test/unitTests/contracts/test-publicAuction.js @@ -372,7 +372,7 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { moola, } = setupMixed(); const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInviteIssuer(); + const inviteIssuer = zoe.getInvitationIssuer(); // Setup Alice const aliceCcPayment = ccMint.mintPayment(cryptoCats(harden(['Felix']))); From 40a4322e76d7fb0da43e679d5a4dda2f70905b7b Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sat, 8 Aug 2020 18:51:24 -0600 Subject: [PATCH 10/31] fix: solve some easy types --- packages/zoe/src/issuerTable.js | 8 +++++--- packages/zoe/src/types.js | 11 +++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/zoe/src/issuerTable.js b/packages/zoe/src/issuerTable.js index 508c8d0d1b9..d70ee18e699 100644 --- a/packages/zoe/src/issuerTable.js +++ b/packages/zoe/src/issuerTable.js @@ -30,9 +30,11 @@ const makeIssuerTable = () => { * misshapen objects rather than just a general check. * @type {Validator} */ - const validateSomewhat = makeValidateProperties( - harden(['brand', 'issuer', 'amountMath']), - ); + const validateSomewhat = makeValidateProperties([ + 'brand', + 'issuer', + 'amountMath', + ]); const makeCustomMethods = table => { /** @type {WeakStore,any>} */ diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 5e90b342add..5356feae584 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -10,14 +10,14 @@ * @template {string} H - the name of the handle * @typedef {H & {}} Handle A type constructor for an opaque type identified by * the H string. - * This uses an intersection type ('MyHandle' & {}) to tag the handle's type - * even though the actual value is just an empty object. + * This uses an intersection type ('MyHandle' & {}) to tag the handle's type even + * though the + * actual value is just an empty object. */ /** * @typedef {string} Keyword - * @typedef {Handle<'InvitationHandle'>} InvitationHandle - * - an opaque handle for an invite + * @typedef {Handle<'InvitationHandle'>} InvitationHandle - an opaque handle for an invite * @typedef {Record} IssuerKeywordRecord * @typedef {Record} BrandKeywordRecord * @typedef {Record} PaymentKeywordRecord @@ -334,8 +334,7 @@ * * @typedef {object} InstanceRecord * @property {Object} terms - contract parameters - * @property {IssuerKeywordRecord} issuerKeywordRecord - * - record with keywords keys, issuer values + * @property {IssuerKeywordRecord} issuerKeywordRecord - record with keywords keys, issuer values * @property {BrandKeywordRecord} brandKeywordRecord - record with * keywords keys, brand values * From b94086f51f24480bc837e91f6d71453ea765e144 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Sat, 8 Aug 2020 20:59:14 -0700 Subject: [PATCH 11/31] fix: use local amountMath (#1408) --- packages/zoe/src/contractFacet/contractFacet.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 03ccc0f6b13..114470da64d 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -78,10 +78,16 @@ export function buildRootObject() { ); const zoeMintP = E(zoeInstanceAdmin).makeZoeMint(keyword, mathHelperName); - const mintyIssuerRecord = await E(zoeMintP).getIssuerRecord(); + const { brand: mintyBrand, issuer: mintyIssuer } = await E( + zoeMintP, + ).getIssuerRecord(); // AWAIT - const { brand: mintyBrand } = mintyIssuerRecord; const mintyAmountMath = makeAmountMath(mintyBrand, mathHelperName); + const mintyIssuerRecord = harden({ + brand: mintyBrand, + issuer: mintyIssuer, + amountMath: mintyAmountMath, + }); registerIssuerRecord(keyword, mintyIssuerRecord); issuerTable.registerIssuerRecord(mintyIssuerRecord); From 56b4faacdfc82fc870fe86b48ff8403a7d9cffcd Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 8 Aug 2020 17:24:57 -0700 Subject: [PATCH 12/31] fix(swingset): check promise resolution table during comms.inbound refs #1400 --- packages/SwingSet/src/vats/comms/inbound.js | 28 ++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/SwingSet/src/vats/comms/inbound.js b/packages/SwingSet/src/vats/comms/inbound.js index 98579aba4cd..a0f7c70dd2a 100644 --- a/packages/SwingSet/src/vats/comms/inbound.js +++ b/packages/SwingSet/src/vats/comms/inbound.js @@ -9,6 +9,32 @@ import { markPromiseAsResolved, } from './state'; +function getInboundFor(state, remoteID, remoteTarget) { + const target = getInbound(state, remoteID, remoteTarget); + // That might point to o-NN or a promise. Check if the promise was resolved + // already. + if (state.promiseTable.has(target)) { + const p = state.promiseTable.get(target); + if (p.state === 'unresolved') { + return target; + } + if (p.state === 'resolved') { + if (p.resolution.type === 'object') { + return p.resolution.slot; + } + if (p.resolution.type === 'data') { + throw new Error(`todo: error for fulfilledToData`); + } + if (p.resolution.type === 'reject') { + throw new Error(`todo: error for rejected`); + } + throw new Error(`unknown res type ${p.resolution.type}`); + } + throw new Error(`unknown p.state ${p.state}`); + } + return target; +} + export function deliverFromRemote(syscall, state, remoteID, message) { const command = message.split(':', 1)[0]; @@ -21,7 +47,7 @@ export function deliverFromRemote(syscall, state, remoteID, message) { .split(':') .slice(1); // slots: [$target, $method, $result, $slots..] - const target = getInbound(state, remoteID, slots[0]); + const target = getInboundFor(state, remoteID, slots[0]); const method = slots[1]; const result = slots[2]; // 'rp-NN' or empty string const msgSlots = slots.slice(3).map(s => mapInbound(state, remoteID, s)); From 07cf2a29269abf47d4b2304c40a50cb8b134a627 Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Sat, 8 Aug 2020 21:34:17 -0700 Subject: [PATCH 13/31] refactor: multipoolAutoswap --- .../zoe/src/contractFacet/contractFacet.js | 13 +- packages/zoe/src/contractSupport/index.js | 1 + .../zoe/src/contracts/multipoolAutoswap.js | 588 ------------------ .../multipoolAutoswap/addLiquidity.js | 32 + .../multipoolAutoswap/getCurrentPrice.js | 47 ++ .../multipoolAutoswap/multipoolAutoswap.js | 92 +++ .../src/contracts/multipoolAutoswap/pool.js | 171 +++++ .../multipoolAutoswap/removeLiquidity.js | 33 + .../src/contracts/multipoolAutoswap/swap.js | 137 ++++ packages/zoe/src/types.js | 2 + .../contracts/test-multipoolAutoswap.js | 268 ++++---- 11 files changed, 642 insertions(+), 742 deletions(-) delete mode 100644 packages/zoe/src/contracts/multipoolAutoswap.js create mode 100644 packages/zoe/src/contracts/multipoolAutoswap/addLiquidity.js create mode 100644 packages/zoe/src/contracts/multipoolAutoswap/getCurrentPrice.js create mode 100644 packages/zoe/src/contracts/multipoolAutoswap/multipoolAutoswap.js create mode 100644 packages/zoe/src/contracts/multipoolAutoswap/pool.js create mode 100644 packages/zoe/src/contracts/multipoolAutoswap/removeLiquidity.js create mode 100644 packages/zoe/src/contracts/multipoolAutoswap/swap.js diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 114470da64d..3d2781623c2 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -208,13 +208,24 @@ export function buildRootObject() { seatToZCFSeatAdmin.get(seatStaging.getSeat()).commit(seatStaging), ); }, + assertUniqueKeyword: keyword => { + assertKeywordName(keyword); + assert( + !getKeywords(instanceRecord.issuerKeywordRecord).includes(keyword), + details`keyword ${keyword} must be unique`, + ); + }, saveIssuer: (issuerP, keyword) => { + // TODO: The checks of the keyword for uniqueness are + // duplicated. Assess how waiting on promises to resolve might + // affect those checks and see if one can be removed. + zcf.assertUniqueKeyword(keyword); return E(zoeInstanceAdmin) .saveIssuer(issuerP, keyword) .then(() => { return issuerTable .getPromiseForIssuerRecord(issuerP) - .then(registerIssuerRecord); + .then(record => registerIssuerRecord(keyword, record)); }); }, makeInvitation: (offerHandler, description, customProperties = {}) => { diff --git a/packages/zoe/src/contractSupport/index.js b/packages/zoe/src/contractSupport/index.js index 8a1f6b7be3d..6873832d11c 100644 --- a/packages/zoe/src/contractSupport/index.js +++ b/packages/zoe/src/contractSupport/index.js @@ -20,4 +20,5 @@ export { satisfies, escrowAndAllocateTo, assertNatMathHelpers, + makeEmptyOffer, } from './zoeHelpers'; diff --git a/packages/zoe/src/contracts/multipoolAutoswap.js b/packages/zoe/src/contracts/multipoolAutoswap.js deleted file mode 100644 index 6d91d9d7327..00000000000 --- a/packages/zoe/src/contracts/multipoolAutoswap.js +++ /dev/null @@ -1,588 +0,0 @@ -// @ts-check - -/* eslint-disable no-use-before-define */ -import makeIssuerKit from '@agoric/ertp'; -import { assert, details } from '@agoric/assert'; -import { E } from '@agoric/eventual-send'; - -import { makeTable, makeValidateProperties } from '../table'; -import { assertKeywordName } from '../zoeService/cleanProposal'; -import { - makeZoeHelpers, - getInputPrice, - calcLiqValueToMint, - calcValueToRemove, -} from '../contractSupport'; -import { filterObj } from '../objArrayConversion'; - -import '../../exported'; - -/** - * Autoswap is a rewrite of Uniswap. Please see the documentation for more - * https://agoric.com/documentation/zoe/guide/contracts/autoswap.html - * - * We expect that this contract will have tens to hundreds of issuers. - * Each liquidity pool is between the central token and a secondary - * token. Secondary tokens can be exchanged with each other, but only - * through the central token. For example, if X and Y are two token - * types and C is the central token, a swap giving X and wanting Y - * would first use the pool (X, C) then the pool (Y, C). There are no - * liquidity pools between two secondary tokens. - * - * There should only need to be one instance of this contract, so liquidity can - * be shared as much as possible. - * - * When the contract is instantiated, the central token is specified in the - * issuerKeywordRecord. The party that calls makeInstance gets an invitation - * that can be used to request an invitation to add liquidity. The same - * invitation is available by calling `await E(publicAPI).getLiquidityIssuer(brand)`. - * Separate invitations are available for adding and removing liquidity, and for - * making trades. Other API operations support monitoring prices and the sizes - * of pools. - * - * @param {ContractFacet} zcf - */ -const makeContract = zcf => { - // This contract must have a "central token" issuer in the terms. - const CENTRAL_TOKEN = 'CentralToken'; - - const getCentralTokenBrand = () => { - const { brandKeywordRecord } = zcf.getInstanceRecord(); - assert( - brandKeywordRecord.CentralToken !== undefined, - details`centralTokenBrand must be present`, - ); - return brandKeywordRecord.CentralToken; - }; - const centralTokenBrand = getCentralTokenBrand(); - - const { - trade, - rejectOffer, - makeEmptyOffer, - checkHook, - assertKeywords, - escrowAndAllocateTo, - assertNatMathHelpers, - } = makeZoeHelpers(zcf); - - // There must be one keyword at the start, which is equal to the - // value of CENTRAL_TOKEN - assertKeywords([CENTRAL_TOKEN]); - - // We need to be able to retrieve information about the liquidity - // pools by tokenBrand. Key: tokenBrand Columns: poolHandle, - // tokenIssuer, liquidityMint, liquidityIssuer, tokenKeyword, - // liquidityKeyword, liquidityTokenSupply - const liquidityTable = makeTable( - makeValidateProperties([ - 'poolHandle', - 'tokenIssuer', - 'tokenBrand', - 'liquidityMint', - 'liquidityIssuer', - 'liquidityBrand', - 'tokenKeyword', - 'liquidityKeyword', - 'liquidityTokenSupply', - ]), - 'tokenBrand', - ); - - /** - * Allows users to add new liquidity pools. `newTokenIssuer` and - * `newTokenKeyword` must not have been already used - * @param {Issuer} newTokenIssuer - * @param {Keyword} newTokenKeyword - */ - const addPool = async (newTokenIssuer, newTokenKeyword) => { - assertKeywordName(newTokenKeyword); - const { brandKeywordRecord } = zcf.getInstanceRecord(); - const keywords = Object.keys(brandKeywordRecord); - const brands = Object.values(brandKeywordRecord); - assert( - !keywords.includes(newTokenKeyword), - details`newTokenKeyword must be unique`, - ); - const newTokenBrand = await E(newTokenIssuer).getBrand(); - assert( - !brands.includes(newTokenBrand), - details`newTokenIssuer must not be already present`, - ); - const newLiquidityKeyword = `${newTokenKeyword}Liquidity`; - assert( - !keywords.includes(newLiquidityKeyword), - details`newLiquidityKeyword must be unique`, - ); - const { - mint: liquidityMint, - issuer: liquidityIssuer, - brand: liquidityBrand, - } = makeIssuerKit(newLiquidityKeyword); - - /** @type {Promise<[Omit, OfferHandle, Omit]>} */ - const promiseAll = Promise.all([ - zcf.addNewIssuer(newTokenIssuer, newTokenKeyword), - makeEmptyOffer(), - zcf.addNewIssuer(liquidityIssuer, newLiquidityKeyword), - ]); - return promiseAll.then(([newTokenIssuerRecord, poolHandle]) => { - // The final element of the above array is intentionally - // ignored, since we already have the liquidityIssuer and mint. - assertNatMathHelpers(newTokenIssuerRecord.brand); - liquidityTable.create( - harden({ - poolHandle, - tokenIssuer: newTokenIssuer, - tokenBrand: newTokenIssuerRecord.brand, - liquidityMint, - liquidityIssuer, - liquidityBrand, - tokenKeyword: newTokenKeyword, - liquidityKeyword: newLiquidityKeyword, - liquidityTokenSupply: 0, - }), - newTokenIssuerRecord.brand, - ); - return harden({ - tokenIssuer: newTokenIssuer, - tokenBrand: newTokenIssuerRecord.brand, - liquidityIssuer, - liquidityBrand, - tokenKeyword: newTokenKeyword, - liquidityKeyword: newLiquidityKeyword, - }); - }); - }; - - // The secondary token brand is used as the key of liquidityTable - // rows, and we use it to look up the pool allocation. We only - // return the keywords for the secondary token, the central token, - // and the associated liquidity token. - const getPoolAllocation = tokenBrand => { - const { poolHandle, tokenKeyword, liquidityKeyword } = liquidityTable.get( - tokenBrand, - ); - - const brandKeywordRecord = filterObj( - zcf.getInstanceRecord().brandKeywordRecord, - [tokenKeyword, CENTRAL_TOKEN, liquidityKeyword], - ); - - return zcf.getCurrentAllocation(poolHandle, brandKeywordRecord); - }; - - /** - * @param {Object} args - * @param {Brand} args.brandIn - * @param {Brand} args.brandOut - * @param {OfferHandle=} args.offerHandleToReject - * @returns {Brand} - */ - const findSecondaryTokenBrand = ({ - brandIn, - brandOut, - offerHandleToReject, - }) => { - if (liquidityTable.has(brandIn)) { - return brandIn; - } - if (liquidityTable.has(brandOut)) { - return brandOut; - } - // We couldn't find either so throw. Reject the offer if - // offerHandleToReject is defined. - const msg = `No secondary token was found`; - if (offerHandleToReject !== undefined) { - rejectOffer(offerHandleToReject, msg); - } - throw new Error(msg); - }; - - const getPoolKeyword = brandToMatch => { - if (brandToMatch === centralTokenBrand) { - return CENTRAL_TOKEN; - } - if (!liquidityTable.has(brandToMatch)) { - throw new Error('getPoolKeyword: brand not found'); - } - const { tokenKeyword } = liquidityTable.get(brandToMatch); - return tokenKeyword; - }; - - const getPoolAmount = (poolAllocation, desiredBrand) => { - const keyword = getPoolKeyword(desiredBrand); - return poolAllocation[keyword]; - }; - - const doGetCurrentPrice = ({ amountIn, brandOut }) => { - const brandIn = amountIn.brand; - const secondaryTokenBrand = findSecondaryTokenBrand({ - brandIn, - brandOut, - }); - const poolAllocation = getPoolAllocation(secondaryTokenBrand); - const outputValue = getInputPrice({ - inputValue: amountIn.value, - inputReserve: getPoolAmount(poolAllocation, brandIn).value, - outputReserve: getPoolAmount(poolAllocation, brandOut).value, - }); - return zcf.getAmountMath(brandOut).make(outputValue); - }; - - const rejectIfNotTokenBrand = (inviteHandle, brand) => { - if (!liquidityTable.has(brand)) { - rejectOffer(inviteHandle, `brand ${brand} was not recognized`); - } - }; - - const addLiquidityExpected = harden({ - give: { - CentralToken: null, - SecondaryToken: null, - }, - want: { Liquidity: null }, - }); - - const addLiquidityHook = offerHandle => { - // Get the brand of the secondary token so we can identify the liquidity pool. - const { - proposal: { - give: { - SecondaryToken: { brand: secondaryTokenBrand }, - }, - }, - } = zcf.getOffer(offerHandle); - - const { - tokenKeyword, - liquidityBrand, - liquidityTokenSupply, - liquidityMint, - poolHandle, - } = liquidityTable.get(secondaryTokenBrand); - - const userAllocation = zcf.getCurrentAllocation(offerHandle); - const poolAllocation = getPoolAllocation(secondaryTokenBrand); - - // Calculate how many liquidity tokens we should be minting. - const liquidityValueOut = calcLiqValueToMint( - harden({ - liqTokenSupply: liquidityTokenSupply, - inputValue: userAllocation.CentralToken.value, - inputReserve: poolAllocation.CentralToken.value, - }), - ); - - const liquidityAmountOut = zcf - .getAmountMath(liquidityBrand) - .make(liquidityValueOut); - - const liquidityPaymentP = liquidityMint.mintPayment(liquidityAmountOut); - - // We update the liquidityTokenSupply before the next turn - liquidityTable.update(secondaryTokenBrand, { - liquidityTokenSupply: liquidityTokenSupply + liquidityValueOut, - }); - - // The contract needs to escrow the liquidity payment with Zoe - // to eventually payout to the user - return escrowAndAllocateTo({ - amount: liquidityAmountOut, - payment: liquidityPaymentP, - keyword: 'Liquidity', - recipientHandle: offerHandle, - }).then(() => { - trade( - { - offerHandle: poolHandle, - gains: { - CentralToken: userAllocation.CentralToken, - [tokenKeyword]: userAllocation.SecondaryToken, - }, - }, - // We reallocated liquidity in the call to - // escrowAndAllocateTo. - { offerHandle, gains: {}, losses: userAllocation }, - ); - - zcf.complete(harden([offerHandle])); - return 'Added liquidity.'; - }); - }; - - const removeLiquidityExpected = harden({ - want: { - CentralToken: null, - SecondaryToken: null, - }, - give: { - Liquidity: null, - }, - }); - - const removeLiquidityHook = offerHandle => { - // Get the brand of the secondary token so we can identify the liquidity pool. - const { - proposal: { - want: { - SecondaryToken: { brand: secondaryTokenBrand }, - }, - }, - } = zcf.getOffer(offerHandle); - - const { - tokenKeyword, - liquidityKeyword, - liquidityTokenSupply, - poolHandle, - } = liquidityTable.get(secondaryTokenBrand); - - const userAllocation = zcf.getCurrentAllocation(offerHandle); - const poolAllocation = getPoolAllocation(secondaryTokenBrand); - const liquidityValueIn = userAllocation.Liquidity.value; - - const centralTokenAmountOut = zcf.getAmountMath(centralTokenBrand).make( - calcValueToRemove( - harden({ - liqTokenSupply: liquidityTokenSupply, - poolValue: poolAllocation[CENTRAL_TOKEN].value, - liquidityValueIn, - }), - ), - ); - - const tokenKeywordAmountOut = zcf.getAmountMath(secondaryTokenBrand).make( - calcValueToRemove( - harden({ - liqTokenSupply: liquidityTokenSupply, - poolValue: poolAllocation[tokenKeyword].value, - liquidityValueIn, - }), - ), - ); - - liquidityTable.update(secondaryTokenBrand, { - liquidityTokenSupply: liquidityTokenSupply - liquidityValueIn, - }); - - trade( - { - offerHandle: poolHandle, - gains: { [liquidityKeyword]: userAllocation.Liquidity }, - losses: { - CentralToken: centralTokenAmountOut, - [tokenKeyword]: tokenKeywordAmountOut, - }, - }, - { - offerHandle, - gains: { - CentralToken: centralTokenAmountOut, - SecondaryToken: tokenKeywordAmountOut, - }, - losses: { - Liquidity: userAllocation.Liquidity, - }, - }, - ); - - zcf.complete(harden([offerHandle])); - return 'Liquidity successfully removed.'; - }; - - const swapExpected = harden({ - give: { - In: null, - }, - want: { - Out: null, - }, - }); - - const swapHook = offerHandle => { - const { - give: { In: amountIn }, - want: { Out: wantedAmountOut }, - } = zcf.getOffer(offerHandle).proposal; - const brandIn = amountIn.brand; - const brandOut = wantedAmountOut.brand; - - // we could be swapping (1) secondary to secondary, (2) central - // to secondary, or (3) secondary to central. - - // 1) secondary to secondary - if (liquidityTable.has(brandIn) && liquidityTable.has(brandOut)) { - rejectIfNotTokenBrand(offerHandle, brandIn); - rejectIfNotTokenBrand(offerHandle, brandOut); - - const centralTokenAmount = doGetCurrentPrice( - harden({ - amountIn, - brandOut: centralTokenBrand, - }), - ); - const amountOut = doGetCurrentPrice( - harden({ - amountIn: centralTokenAmount, - brandOut, - }), - ); - - const brandInAmountMath = zcf.getAmountMath(brandIn); - const finalUserAmounts = harden({ - In: brandInAmountMath.getEmpty(), - Out: amountOut, - }); - - const { poolHandle: poolHandleA } = liquidityTable.get(brandIn); - const poolAllocationA = zcf.getCurrentAllocation(poolHandleA); - const poolKeywordBrandIn = getPoolKeyword(brandIn); - const centralTokenAmountMath = zcf.getAmountMath(centralTokenBrand); - const finalPoolAmountsA = { - [poolKeywordBrandIn]: brandInAmountMath.add( - poolAllocationA[poolKeywordBrandIn], - amountIn, - ), - CentralToken: centralTokenAmountMath.subtract( - poolAllocationA.CentralToken, - centralTokenAmount, - ), - }; - - const { poolHandle: poolHandleB } = liquidityTable.get(brandOut); - const poolAllocationB = zcf.getCurrentAllocation(poolHandleB); - const poolKeywordBrandOut = getPoolKeyword(brandOut); - const brandOutAmountMath = zcf.getAmountMath(brandOut); - const finalPoolAmountsB = { - CentralToken: centralTokenAmountMath.add( - poolAllocationB.CentralToken, - centralTokenAmount, - ), - [poolKeywordBrandOut]: brandOutAmountMath.subtract( - poolAllocationB[poolKeywordBrandOut], - amountOut, - ), - }; - - zcf.reallocate( - harden([poolHandleA, poolHandleB, offerHandle]), - harden([finalPoolAmountsA, finalPoolAmountsB, finalUserAmounts]), - ); - zcf.complete(harden([offerHandle])); - return `Swap successfully completed.`; - } - // 2) central to secondary and 3) secondary to central - const secondaryTokenBrand = findSecondaryTokenBrand({ - brandIn, - brandOut, - offerHandleToReject: offerHandle, - }); - const { poolHandle } = liquidityTable.get(secondaryTokenBrand); - - const amountOut = doGetCurrentPrice( - harden({ - amountIn, - brandOut, - }), - ); - trade( - { - offerHandle: poolHandle, - gains: { - [getPoolKeyword(brandIn)]: amountIn, - }, - losses: { - [getPoolKeyword(brandOut)]: amountOut, - }, - }, - { - offerHandle, - gains: { Out: amountOut }, - losses: { In: amountIn }, - }, - ); - zcf.complete(harden([offerHandle])); - return `Swap successfully completed.`; - }; - - /** - * `getCurrentPrice` calculates the result of a trade, given a certain - * amount of digital assets in. - * @param {Amount} amountIn - the amount of digital assets to be - * sent in - * @param {Brand} brandOut - the brand of the requested payment. - */ - const getCurrentPrice = (amountIn, brandOut) => { - const brandIn = amountIn.brand; - // brandIn could either be the central token brand, or one of - // the secondary token brands - - // SecondaryToken to SecondaryToken - if (brandIn !== centralTokenBrand && brandOut !== centralTokenBrand) { - assert( - liquidityTable.has(brandIn) && liquidityTable.has(brandOut), - details`amountIn brand ${brandIn} or brandOut ${brandOut} was not recognized`, - ); - - // We must do two consecutive `doGetCurrentPrice` calls: from - // the brandIn to the central token, then from the central - // token to the brandOut - const centralTokenAmount = doGetCurrentPrice( - harden({ - amountIn, - brandOut: centralTokenBrand, - }), - ); - return doGetCurrentPrice( - harden({ - amountIn: centralTokenAmount, - brandOut, - }), - ); - } - - // All other cases: secondaryToken to CentralToken or vice versa. - return doGetCurrentPrice( - harden({ - amountIn, - brandOut, - }), - ); - }; - - const getLiquidityIssuer = tokenBrand => - liquidityTable.get(tokenBrand).liquidityIssuer; - - const makeAddLiquidityInvite = () => - zcf.makeInvitation( - checkHook(addLiquidityHook, addLiquidityExpected), - 'multipool autoswap add liquidity', - ); - - const makeSwapInvite = () => - zcf.makeInvitation(checkHook(swapHook, swapExpected), 'autoswap swap'); - - const makeRemoveLiquidityInvite = () => - zcf.makeInvitation( - checkHook(removeLiquidityHook, removeLiquidityExpected), - 'autoswap remove liquidity', - ); - - zcf.initPublicAPI( - harden({ - addPool, - getPoolAllocation, - getLiquidityIssuer, - getCurrentPrice, - makeSwapInvite, - makeAddLiquidityInvite, - makeRemoveLiquidityInvite, - }), - ); - - return makeAddLiquidityInvite(); -}; - -harden(makeContract); -export { makeContract }; diff --git a/packages/zoe/src/contracts/multipoolAutoswap/addLiquidity.js b/packages/zoe/src/contracts/multipoolAutoswap/addLiquidity.js new file mode 100644 index 00000000000..8db7f7a4de9 --- /dev/null +++ b/packages/zoe/src/contracts/multipoolAutoswap/addLiquidity.js @@ -0,0 +1,32 @@ +import { assertProposalKeywords } from '../../contractSupport'; + +import '../../../exported'; + +/** + * @param {ContractFacet} zcf + * @param {(brand: Brand) => Pool} getPool + */ +export const makeMakeAddLiquidityInvitation = (zcf, getPool) => { + const addLiquidityExpected = harden({ + give: { + Central: null, + Secondary: null, + }, + want: { Liquidity: null }, + }); + + const addLiquidity = seat => { + // Get the brand of the secondary token so we can identify the liquidity pool. + const secondaryBrand = seat.getProposal().give.Secondary.brand; + const pool = getPool(secondaryBrand); + return pool.addLiquidity(seat); + }; + + const makeAddLiquidityInvitation = () => + zcf.makeInvitation( + assertProposalKeywords(addLiquidity, addLiquidityExpected), + 'multipool autoswap add liquidity', + ); + + return makeAddLiquidityInvitation; +}; diff --git a/packages/zoe/src/contracts/multipoolAutoswap/getCurrentPrice.js b/packages/zoe/src/contracts/multipoolAutoswap/getCurrentPrice.js new file mode 100644 index 00000000000..b87844256a9 --- /dev/null +++ b/packages/zoe/src/contracts/multipoolAutoswap/getCurrentPrice.js @@ -0,0 +1,47 @@ +/** + * + * @param {(brand: Brand) => boolean} isSecondary + * @param {(brand: Brand) => boolean} isCentral + * @param {(brand: Brand) => Pool} getPool + */ + +import '../../../exported'; + +export const makeGetCurrentPrice = (isSecondary, isCentral, getPool) => { + /** + * `getCurrentPrice` calculates the result of a trade, given a certain + * amount of digital assets in. + * @param {Amount} amountIn - the amount of digital assets to be + * sent in + * @param {Brand} brandOut - the brand of the requested payment. + */ + // eslint-disable-next-line consistent-return + const getCurrentPrice = (amountIn, brandOut) => { + // BrandIn could either be the central token brand, or one of + // the secondary token brands. + const { brand: brandIn, value: inputValue } = amountIn; + + if (isCentral(brandIn) && isSecondary(brandOut)) { + return getPool(brandOut).getCurrentPrice(true, inputValue); + } + + if (isSecondary(brandIn) && isCentral(brandOut)) { + return getPool(brandIn).getCurrentPrice(false, inputValue); + } + + if (isSecondary(brandIn) && isSecondary(brandOut)) { + // We must do two consecutive `getCurrentPrice` calls: from + // the brandIn to the central token, then from the central + // token to the brandOut. + const centralTokenAmount = getPool(brandIn).getCurrentPrice( + false, + inputValue, + ); + return getPool(brandOut).getCurrentPrice(true, centralTokenAmount.value); + } + + throw new Error(`brands were not recognized`); + }; + + return getCurrentPrice; +}; diff --git a/packages/zoe/src/contracts/multipoolAutoswap/multipoolAutoswap.js b/packages/zoe/src/contracts/multipoolAutoswap/multipoolAutoswap.js new file mode 100644 index 00000000000..8c4c16a466d --- /dev/null +++ b/packages/zoe/src/contracts/multipoolAutoswap/multipoolAutoswap.js @@ -0,0 +1,92 @@ +// @ts-check + +import { assert, details } from '@agoric/assert'; +import makeWeakStore from '@agoric/weak-store'; + +import { assertIssuerKeywords } from '../../contractSupport'; +import { makeAddPool } from './pool'; +import { makeGetCurrentPrice } from './getCurrentPrice'; +import { makeMakeSwapInvitation } from './swap'; +import { makeMakeAddLiquidityInvitation } from './addLiquidity'; +import { makeMakeRemoveLiquidityInvitation } from './removeLiquidity'; + +import '../../../exported'; + +/** + * Autoswap is a rewrite of Uniswap. Please see the documentation for more + * https://agoric.com/documentation/zoe/guide/contracts/autoswap.html + * + * We expect that this contract will have tens to hundreds of issuers. + * Each liquidity pool is between the central token and a secondary + * token. Secondary tokens can be exchanged with each other, but only + * through the central token. For example, if X and Y are two token + * types and C is the central token, a swap giving X and wanting Y + * would first use the pool (X, C) then the pool (Y, C). There are no + * liquidity pools between two secondary tokens. + * + * There should only need to be one instance of this contract, so liquidity can + * be shared as much as possible. + * + * When the contract is instantiated, the central token is specified in the + * issuerKeywordRecord. The party that calls `E(zoe).startInstance` gets an invitation + * that can be used to request an invitation to add liquidity. The same + * invitation is available by calling `await E(publicAPI).getLiquidityIssuer(brand)`. + * Separate invitations are available for adding and removing liquidity, and for + * making trades. Other API operations support monitoring prices and the sizes + * of pools. + * + * @type {ContractStartFn} + */ +const start = (zcf, _terms) => { + // This contract must have a "Central" keyword and issuer in the + // IssuerKeywordRecord. + assertIssuerKeywords(zcf, ['Central']); + const centralBrand = zcf.getInstanceRecord().brandKeywordRecord.Central; + assert(centralBrand !== undefined, details`centralBrand must be present`); + + const secondaryBrandToPool = makeWeakStore(); + const getPool = secondaryBrandToPool.get; + const initPool = secondaryBrandToPool.init; + const isSecondary = secondaryBrandToPool.has; + const isCentral = brand => brand === centralBrand; + + const getLiquidityIssuer = brand => getPool(brand).getLiquidityIssuer(); + const addPool = makeAddPool(zcf, isSecondary, initPool, centralBrand); + const getPoolAllocation = brand => { + debugger; + return getPool(brand) + .getPoolSeat() + .getCurrentAllocation(); + }; + const getCurrentPrice = makeGetCurrentPrice(isSecondary, isCentral, getPool); + const makeSwapInvitation = makeMakeSwapInvitation( + zcf, + isSecondary, + isCentral, + getPool, + ); + const makeAddLiquidityInvitation = makeMakeAddLiquidityInvitation( + zcf, + getPool, + ); + + const makeRemoveLiquidityInvitation = makeMakeRemoveLiquidityInvitation( + zcf, + getPool, + ); + + const publicFacet = { + addPool, + getPoolAllocation, + getLiquidityIssuer, + getCurrentPrice, + makeSwapInvitation, + makeAddLiquidityInvitation, + makeRemoveLiquidityInvitation, + }; + + return harden({ publicFacet }); +}; + +harden(start); +export { start }; diff --git a/packages/zoe/src/contracts/multipoolAutoswap/pool.js b/packages/zoe/src/contracts/multipoolAutoswap/pool.js new file mode 100644 index 00000000000..112b8a80ced --- /dev/null +++ b/packages/zoe/src/contracts/multipoolAutoswap/pool.js @@ -0,0 +1,171 @@ +import { E } from '@agoric/eventual-send'; +import { assert, details } from '@agoric/assert'; + +import { + getInputPrice, + calcLiqValueToMint, + calcValueToRemove, + assertNatMathHelpers, + makeEmptyOffer, + trade, +} from '../../contractSupport'; + +import '../../../exported'; + +/** + * @param {ContractFacet} zcf + * @param {(brand: Brand) => boolean} isSecondary + * @param {(brand: Brand, pool: Pool) => void} initPool + * @param {Brand} centralBrand + */ +export const makeAddPool = (zcf, isSecondary, initPool, centralBrand) => { + const makePool = (liquidityZcfMint, poolSeat, secondaryBrand) => { + let liqTokenSupply = 0; + const { + brand: liquidityBrand, + amountMath: liquidityAmountMath, + } = liquidityZcfMint.getIssuerRecord(); + + /** + * @typedef {Object} Pool + * @property {(centralToSecondary: boolean, inputValue: Value) => Amount } getCurrentPrice + * @property {(seat: ZCFSeat) => string} addLiquidity + * @property {(seat: ZCFSeat) => string} removeLiquidity + * @property {() => ZCFSeat} getPoolSeat + * @property {() => AmountMath} getAmountMath + * @property {() => Amount} getSecondaryAmount + * @property {() => Amount} getCentralAmount + */ + + /** @type {Pool} */ + const pool = { + getPoolSeat: () => poolSeat, + getAmountMath: () => zcf.getAmountMath(secondaryBrand), + getCentralAmountMath: () => zcf.getAmountMath(centralBrand), + getCentralAmount: () => + poolSeat.getAmountAllocated('Central', centralBrand), + getSecondaryAmount: () => + poolSeat.getAmountAllocated('Secondary', secondaryBrand), + getCurrentPrice: (centralToSecondary, inputValue) => { + if (centralToSecondary) { + const outputValue = getInputPrice({ + inputValue, + inputReserve: pool.getCentralAmount().value, + outputReserve: pool.getSecondaryAmount().value, + }); + return pool.getAmountMath().make(outputValue); + } + const outputValue = getInputPrice({ + inputValue, + inputReserve: pool.getSecondaryAmount().value, + outputReserve: pool.getCentralAmount().value, + }); + return pool.getCentralAmountMath().make(outputValue); + }, + addLiquidity: userSeat => { + // Calculate how many liquidity tokens we should be minting. + const liquidityValueOut = calcLiqValueToMint( + harden({ + liqTokenSupply, + inputValue: userSeat.getAmountAllocated('Central').value, + inputReserve: pool.getCentralAmount().value, + }), + ); + + const liquidityAmountOut = liquidityAmountMath.make(liquidityValueOut); + liquidityZcfMint.mintGains({ Liquidity: liquidityAmountOut }, poolSeat); + liqTokenSupply += liquidityValueOut; + + trade( + zcf, + { + seat: poolSeat, + gains: userSeat.getCurrentAllocation(), + }, + { + seat: userSeat, + gains: { Liquidity: liquidityAmountOut }, + }, + ); + userSeat.exit(); + return 'Added liquidity.'; + }, + removeLiquidity: userSeat => { + const liquidityIn = userSeat.getAmountAllocated( + 'Liquidity', + liquidityBrand, + ); + const liquidityValueIn = liquidityIn.value; + const centralTokenAmountOut = pool.getCentralAmountMath().make( + calcValueToRemove( + harden({ + liqTokenSupply, + poolValue: pool.getCentralAmount().value, + liquidityValueIn, + }), + ), + ); + + const tokenKeywordAmountOut = pool.getAmountMath().make( + calcValueToRemove( + harden({ + liqTokenSupply, + poolValue: pool.getSecondaryAmount().value, + liquidityValueIn, + }), + ), + ); + + liqTokenSupply -= liquidityValueIn; + + trade( + zcf, + { + seat: poolSeat, + gains: { Liquidity: liquidityIn }, + }, + { + seat: userSeat, + gains: { + Central: centralTokenAmountOut, + Secondary: tokenKeywordAmountOut, + }, + }, + ); + + userSeat.exit(); + return 'Liquidity successfully removed.'; + }, + }; + return pool; + }; + + /** + * Allows users to add new liquidity pools. `secondaryIssuer` and + * its keyword must not have been already used + * @param {Issuer} secondaryIssuer + * @param {Keyword} keyword - will be used in the + * issuerKeywordRecord for the contract, but not used otherwise + */ + const addPool = async (secondaryIssuer, keyword) => { + zcf.assertUniqueKeyword(keyword); + const liquidityKeyword = `${keyword}Liquidity`; + zcf.assertUniqueKeyword(liquidityKeyword); + const secondaryBrand = await E(secondaryIssuer).getBrand(); + assert( + !isSecondary(secondaryBrand), + details`issuer ${secondaryIssuer} already has a pool`, + ); + + await zcf.saveIssuer(secondaryIssuer, keyword); + assertNatMathHelpers(zcf, secondaryBrand); + const liquidityZCFMint = await zcf.makeZCFMint(liquidityKeyword); + const { zcfSeat: poolSeatP } = await makeEmptyOffer(zcf); + const poolSeat = await poolSeatP; + const pool = makePool(liquidityZCFMint, poolSeat, secondaryBrand); + initPool(secondaryBrand, pool); + return liquidityZCFMint.getIssuerRecord().issuer; + }; + + return addPool; +}; diff --git a/packages/zoe/src/contracts/multipoolAutoswap/removeLiquidity.js b/packages/zoe/src/contracts/multipoolAutoswap/removeLiquidity.js new file mode 100644 index 00000000000..16af1b6d650 --- /dev/null +++ b/packages/zoe/src/contracts/multipoolAutoswap/removeLiquidity.js @@ -0,0 +1,33 @@ +import { assertProposalKeywords } from '../../contractSupport'; + +import '../../../exported'; + +/** + * @param {ContractFacet} zcf + * @param {(brand: Brand) => Pool} getPool + */ +export const makeMakeRemoveLiquidityInvitation = (zcf, getPool) => { + const removeLiquidityExpected = harden({ + want: { + Central: null, + Secondary: null, + }, + give: { + Liquidity: null, + }, + }); + + const removeLiquidity = seat => { + // Get the brand of the secondary token so we can identify the liquidity pool. + const secondaryBrand = seat.getProposal().want.Secondary.brand; + const pool = getPool(secondaryBrand); + return pool.removeLiquidity(seat); + }; + + const makeRemoveLiquidityInvitation = () => + zcf.makeInvitation( + assertProposalKeywords(removeLiquidity, removeLiquidityExpected), + 'autoswap remove liquidity', + ); + return makeRemoveLiquidityInvitation; +}; diff --git a/packages/zoe/src/contracts/multipoolAutoswap/swap.js b/packages/zoe/src/contracts/multipoolAutoswap/swap.js new file mode 100644 index 00000000000..4e7f2b51676 --- /dev/null +++ b/packages/zoe/src/contracts/multipoolAutoswap/swap.js @@ -0,0 +1,137 @@ +import { assertProposalKeywords, trade } from '../../contractSupport'; + +import '../../../exported'; + +/** + * + * @param {ContractFacet} zcf + * @param {(brand: Brand) => boolean} isSecondary + * @param {(brand: Brand) => boolean} isCentral + * @param {(brand: Brand) => Pool} getPool + */ +export const makeMakeSwapInvitation = ( + zcf, + isSecondary, + isCentral, + getPool, +) => { + const swapExpected = harden({ + give: { In: null }, + want: { Out: null }, + }); + + const swap = seat => { + debugger; + const { + give: { In: amountIn }, + want: { Out: wantedAmountOut }, + } = seat.getProposal(); + const { brand: brandIn, value: inputValue } = amountIn; + const brandOut = wantedAmountOut.brand; + + // we could be swapping (1) central to secondary, (2) secondary to + // central, or (3) secondary to secondary + + if (isCentral(brandIn) && isSecondary(brandOut)) { + const pool = getPool(brandOut); + const amountOut = pool.getCurrentPrice(true, inputValue); + trade( + zcf, + { + seat: pool.getPoolSeat(), + gains: { Central: amountIn }, + losses: { Secondary: amountOut }, + }, + { + seat, + gains: { Out: amountOut }, + losses: { In: amountIn }, + }, + ); + seat.exit(); + return `Swap successfully completed.`; + } + + if (isSecondary(brandIn) && isCentral(brandOut)) { + const pool = getPool(brandIn); + const amountOut = pool.getCurrentPrice(false, inputValue); + trade( + zcf, + { + seat: pool.getPoolSeat(), + gains: { Secondary: amountIn }, + losses: { Central: amountOut }, + }, + { + seat, + gains: { Out: amountOut }, + losses: { In: amountIn }, + }, + ); + seat.exit(); + return `Swap successfully completed.`; + } + + if (isSecondary(brandIn) && isSecondary(brandOut)) { + // We must do two consecutive `getCurrentPrice` calls: from + // the brandIn to the central token, then from the central + // token to the brandOut. + + const brandInPool = getPool(brandIn); + const brandOutPool = getPool(brandOut); + + const centralTokenAmount = brandInPool.getCurrentPrice(false, inputValue); + const amountOut = brandOutPool.getCurrentPrice( + true, + centralTokenAmount.value, + ); + + const seatStaging = seat.stage( + harden({ + In: brandInPool.getAmountMath().getEmpty(), + Out: amountOut, + }), + ); + + const centralTokenAmountMath = brandInPool.getCentralAmountMath(); + const brandInAmountMath = brandInPool.getAmountMath(); + const brandOutAmountMath = brandOutPool.getAmountMath(); + + const poolBrandInStaging = brandInPool.getPoolSeat().stage({ + Secondary: brandInAmountMath.add( + brandInPool.getSecondaryAmount(), + amountIn, + ), + Central: centralTokenAmountMath.subtract( + brandInPool.getCentralAmount(), + centralTokenAmount, + ), + }); + + const poolBrandOutStaging = brandOutPool.getPoolSeat().stage({ + Central: centralTokenAmountMath.add( + brandOutPool.getCentralAmount(), + centralTokenAmount, + ), + Secondary: brandOutAmountMath.subtract( + brandOutPool.getSecondaryAmount(), + amountOut, + ), + }); + + zcf.reallocate(poolBrandInStaging, poolBrandOutStaging, seatStaging); + seat.exit(); + return `Swap successfully completed.`; + } + + throw new Error(`brands were not recognized`); + }; + + const makeSwapInvitation = () => + zcf.makeInvitation( + assertProposalKeywords(swap, swapExpected), + 'autoswap swap', + ); + + return makeSwapInvitation; +}; diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 5356feae584..80ad553911d 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -181,6 +181,8 @@ * synchronously from within the contract, and usually is referred to in code as * zcf. * @property {Reallocate} reallocate - reallocate amounts among seats + * @property {(keyword: Keyword) => void} assertUniqueKeyword - check + * whether a keyword is valid and unique and could be added in `saveIssuer` * @property {SaveIssuer} saveIssuer - save an issuer to ZCF and Zoe * and get the amountMath and brand synchronously accessible after saving * @property {MakeInvitation} makeInvitation diff --git a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js index ff30d1de922..ff13b4e98f2 100644 --- a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js @@ -13,10 +13,10 @@ import fakeVatAdmin from './fakeVatAdmin'; import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; -const multipoolAutoswapRoot = `${__dirname}/../../../src/contracts/multipoolAutoswap`; +const multipoolAutoswapRoot = `${__dirname}/../../../src/contracts/multipoolAutoswap/multipoolAutoswap`; test('multipoolAutoSwap with valid offers', async t => { - t.plan(35); + t.plan(33); try { const { moolaR, simoleanR, moola, simoleans } = setup(); const zoe = makeZoe(fakeVatAdmin); @@ -29,9 +29,7 @@ test('multipoolAutoSwap with valid offers', async t => { // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(100)); // Let's assume that central tokens are worth 2x as much as moola - const aliceCentralTokenPayment = centralR.mint.mintPayment( - centralTokens(50), - ); + const aliceCentralPayment = centralR.mint.mintPayment(centralTokens(50)); const aliceSimoleanPayment = simoleanR.mint.mintPayment(simoleans(398)); // Setup Bob @@ -43,11 +41,16 @@ test('multipoolAutoSwap with valid offers', async t => { // Pack the contract. const bundle = await bundleSource(multipoolAutoswapRoot); - const installationHandle = await zoe.install(bundle); - const { invite: aliceInvite } = await zoe.makeInstance( - installationHandle, - harden({ CentralToken: centralR.issuer }), + const installation = await zoe.install(bundle); + const { creatorFacet } = await zoe.makeInstance( + installation, + harden({ Central: centralR.issuer }), ); + const instance = await E(creatorFacet).getInstance(); + const publicFacet = await E(zoe).getPublicFacet(instance); + const aliceAddLiquidityInvitation = E( + publicFacet, + ).makeAddLiquidityInvitation(); const makeAmountMathFromIssuer = issuer => Promise.all([ @@ -56,15 +59,17 @@ test('multipoolAutoSwap with valid offers', async t => { ]).then(([brand, mathName]) => makeAmountMath(brand, mathName)); const inviteAmountMath = await makeAmountMathFromIssuer(inviteIssuer); - const aliceInviteAmount = await inviteIssuer.getAmountOf(aliceInvite); + const aliceInviteAmount = await inviteIssuer.getAmountOf( + aliceAddLiquidityInvitation, + ); t.deepEquals( aliceInviteAmount, inviteAmountMath.make( harden([ { description: 'multipool autoswap add liquidity', - instanceHandle: aliceInviteAmount.value[0].instanceHandle, - installationHandle, + instance, + installation, handle: aliceInviteAmount.value[0].handle, }, ]), @@ -72,49 +77,29 @@ test('multipoolAutoSwap with valid offers', async t => { `invite value is as expected`, ); - const { publicAPI, handle: instanceHandle } = zoe.getInstanceRecord( - aliceInviteAmount.value[0].instanceHandle, - ); - - const addMoolaPoolResult = await E(publicAPI).addPool( + const moolaLiquidityIssuer = await E(publicFacet).addPool( moolaR.issuer, 'Moola', ); - t.equals( - addMoolaPoolResult.tokenIssuer, - moolaR.issuer, - `adding pool for moola`, - ); - const moolaLiquidityIssuer = await E(publicAPI).getLiquidityIssuer( - moolaR.brand, - ); const moolaLiquidityAmountMath = await makeAmountMathFromIssuer( moolaLiquidityIssuer, ); const moolaLiquidity = moolaLiquidityAmountMath.make; - const addSimoleansPoolResult = await E(publicAPI).addPool( + const simoleanLiquidityIssuer = await E(publicFacet).addPool( simoleanR.issuer, 'Simoleans', ); - t.equals( - addSimoleansPoolResult.tokenIssuer, - simoleanR.issuer, - `adding pool for simoleans`, - ); - const simoleanLiquidityIssuer = await E(publicAPI).getLiquidityIssuer( - simoleanR.brand, - ); const simoleanLiquidityAmountMath = await makeAmountMathFromIssuer( simoleanLiquidityIssuer, ); const simoleanLiquidity = simoleanLiquidityAmountMath.make; - const { issuerKeywordRecord } = zoe.getInstanceRecord(instanceHandle); + const issuerKeywordRecord = zoe.getIssuers(instance); t.deepEquals( issuerKeywordRecord, harden({ - CentralToken: centralR.issuer, + Central: centralR.issuer, Moola: moolaR.issuer, MoolaLiquidity: moolaLiquidityIssuer, Simoleans: simoleanR.issuer, @@ -123,22 +108,14 @@ test('multipoolAutoSwap with valid offers', async t => { `There are keywords for central token and two additional tokens and liquidity`, ); t.deepEquals( - await E(publicAPI).getPoolAllocation(moolaR.brand), - harden({ - Moola: moolaR.amountMath.getEmpty(), - CentralToken: centralR.amountMath.getEmpty(), - MoolaLiquidity: moolaLiquidityAmountMath.getEmpty(), - }), - `The poolAmounts object should only have keywords for Moola, CentralToken, and MoolaLiquidity. Values should be empty`, + await E(publicFacet).getPoolAllocation(moolaR.brand), + {}, + `The poolAllocation object values for moola should be empty`, ); t.deepEquals( - await E(publicAPI).getPoolAllocation(simoleanR.brand), - harden({ - Simoleans: simoleanR.amountMath.getEmpty(), - CentralToken: centralR.amountMath.getEmpty(), - SimoleansLiquidity: simoleanLiquidityAmountMath.getEmpty(), - }), - `The poolAmounts object should only have keywords for Simoleans, CentralToken, and SimoleansLiquidity. Values should be empty`, + await E(publicFacet).getPoolAllocation(simoleanR.brand), + {}, + `The poolAllocation object values for simoleans should be empty`, ); // Alice adds liquidity @@ -146,64 +123,61 @@ test('multipoolAutoSwap with valid offers', async t => { // aka 2 moola = 1 central token const aliceProposal = harden({ want: { Liquidity: moolaLiquidity(50) }, - give: { SecondaryToken: moola(100), CentralToken: centralTokens(50) }, + give: { Secondary: moola(100), Central: centralTokens(50) }, }); const alicePayments = { - SecondaryToken: aliceMoolaPayment, - CentralToken: aliceCentralTokenPayment, + Secondary: aliceMoolaPayment, + Central: aliceCentralPayment, }; - const { - outcome: liquidityOkP, - payout: aliceAddLiquidityPayoutP, - } = await zoe.offer(aliceInvite, aliceProposal, alicePayments); + const addLiquiditySeat = await zoe.offer( + aliceAddLiquidityInvitation, + aliceProposal, + alicePayments, + ); t.equals( - await liquidityOkP, + await E(addLiquiditySeat).getOfferResult(), 'Added liquidity.', `Alice added moola and central liquidity`, ); - const liquidityPayments = await aliceAddLiquidityPayoutP; - const liquidityPayout = await liquidityPayments.Liquidity; + const liquidityPayout = await addLiquiditySeat.getPayout('Liquidity'); t.deepEquals( await moolaLiquidityIssuer.getAmountOf(liquidityPayout), moolaLiquidity(50), ); t.deepEquals( - await E(publicAPI).getPoolAllocation(moolaR.brand), + await E(publicFacet).getPoolAllocation(moolaR.brand), harden({ - Moola: moola(100), - CentralToken: centralTokens(50), - MoolaLiquidity: moolaLiquidity(0), + Secondary: moola(100), + Central: centralTokens(50), + Liquidity: moolaLiquidity(0), }), `The poolAmounts record should contain the new liquidity`, ); // Bob creates a swap invite for himself - const bobSwapInvite1 = await E(publicAPI).makeSwapInvite(); + const bobSwapInvite1 = await E(publicFacet).makeSwapInvitation(); const { value: [bobInviteValue], } = await inviteIssuer.getAmountOf(bobSwapInvite1); - const { - publicAPI: bobPublicAPI, - installationHandle: bobInstallationId, - } = zoe.getInstanceRecord(bobInviteValue.instanceHandle); + const bobPublicFacet = zoe.getPublicFacet(bobInviteValue.instance); t.equals( - bobInstallationId, - installationHandle, - `installationHandle is as expected`, + bobInviteValue.installation, + installation, + `installation is as expected`, ); // Bob looks up the price of 17 moola in central tokens - const priceInCentralTokens = await E(bobPublicAPI).getCurrentPrice( + const priceInCentrals = await E(bobPublicFacet).getCurrentPrice( moola(17), centralR.brand, ); t.deepEquals( - priceInCentralTokens, + priceInCentrals, centralTokens(7), `price in central tokens of 17 moola is as expected`, ); @@ -215,18 +189,16 @@ test('multipoolAutoSwap with valid offers', async t => { const bobMoolaForCentralPayments = harden({ In: bobMoolaPayment }); // Bob swaps - const { outcome: offerOkP, payout: bobPayoutP } = await zoe.offer( + const bobSeat = await zoe.offer( bobSwapInvite1, bobMoolaForCentralProposal, bobMoolaForCentralPayments, ); - t.equal(await offerOkP, 'Swap successfully completed.'); + t.equal(await E(bobSeat).getOfferResult(), 'Swap successfully completed.'); - const bobPayout = await bobPayoutP; - - const bobMoolaPayout1 = await bobPayout.In; - const bobCentralTokenPayout1 = await bobPayout.Out; + const bobMoolaPayout1 = await bobSeat.getPayout('In'); + const bobCentralPayout1 = await bobSeat.getPayout('Out'); t.deepEqual( await moolaR.issuer.getAmountOf(bobMoolaPayout1), @@ -234,25 +206,25 @@ test('multipoolAutoSwap with valid offers', async t => { `bob gets no moola back`, ); t.deepEqual( - await centralR.issuer.getAmountOf(bobCentralTokenPayout1), + await centralR.issuer.getAmountOf(bobCentralPayout1), centralTokens(7), `bob gets the same price as when he called the getCurrentPrice method`, ); t.deepEquals( - await E(bobPublicAPI).getPoolAllocation(moolaR.brand), + await E(bobPublicFacet).getPoolAllocation(moolaR.brand), { - Moola: moola(117), - CentralToken: centralTokens(43), - MoolaLiquidity: moolaLiquidity(0), + Secondary: moola(117), + Central: centralTokens(43), + Liquidity: moolaLiquidity(0), }, `pool allocation added the moola and subtracted the central tokens`, ); - const bobCentralTokenPurse = await E(centralR.issuer).makeEmptyPurse(); - await E(bobCentralTokenPurse).deposit(bobCentralTokenPayout1); + const bobCentralPurse = await E(centralR.issuer).makeEmptyPurse(); + await E(bobCentralPurse).deposit(bobCentralPayout1); // Bob looks up the price of 7 central tokens in moola - const moolaAmounts = await E(bobPublicAPI).getCurrentPrice( + const moolaAmounts = await E(bobPublicFacet).getCurrentPrice( centralTokens(7), moolaR.brand, ); @@ -263,33 +235,29 @@ test('multipoolAutoSwap with valid offers', async t => { ); // Bob makes another offer and swaps - const bobSwapInvite2 = await E(bobPublicAPI).makeSwapInvite(); + const bobSwapInvite2 = await E(bobPublicFacet).makeSwapInvitation(); const bobCentralForMoolaProposal = harden({ want: { Out: moola(16) }, give: { In: centralTokens(7) }, }); const centralForMoolaPayments = harden({ - In: await E(bobCentralTokenPurse).withdraw(centralTokens(7)), + In: await E(bobCentralPurse).withdraw(centralTokens(7)), }); - const { - outcome: centralForMoolaOkP, - payout: bobCentralForMoolaPayoutP, - } = await zoe.offer( + const bobSeat2 = await zoe.offer( bobSwapInvite2, bobCentralForMoolaProposal, centralForMoolaPayments, ); t.equal( - await centralForMoolaOkP, + await bobSeat2.getOfferResult(), 'Swap successfully completed.', `second swap successful`, ); - const bobCentralForMoolaPayout = await bobCentralForMoolaPayoutP; - const bobMoolaPayout2 = await bobCentralForMoolaPayout.Out; - const bobCentralPayout2 = await bobCentralForMoolaPayout.In; + const bobMoolaPayout2 = await bobSeat2.getPayout('Out'); + const bobCentralPayout2 = await bobSeat2.getPayout('In'); t.deepEqual( await moolaR.issuer.getAmountOf(bobMoolaPayout2), @@ -302,11 +270,11 @@ test('multipoolAutoSwap with valid offers', async t => { `bob gets no central tokens back`, ); t.deepEqual( - await E(bobPublicAPI).getPoolAllocation(moolaR.brand), + await E(bobPublicFacet).getPoolAllocation(moolaR.brand), { - Moola: moola(101), - CentralToken: centralTokens(50), - MoolaLiquidity: moolaLiquidity(0), + Secondary: moola(101), + Central: centralTokens(50), + Liquidity: moolaLiquidity(0), }, `fee added to liquidity pool`, ); @@ -316,37 +284,33 @@ test('multipoolAutoSwap with valid offers', async t => { // the liquidity adding // const aliceSimCentralLiquidityInvite = await E( - publicAPI, - ).makeAddLiquidityInvite(); + publicFacet, + ).makeAddLiquidityInvitation(); const aliceSimCentralProposal = harden({ want: { Liquidity: simoleanLiquidity(43) }, - give: { SecondaryToken: simoleans(398), CentralToken: centralTokens(43) }, + give: { Secondary: simoleans(398), Central: centralTokens(43) }, }); - const aliceCentralTokenPayment2 = await centralR.mint.mintPayment( + const aliceCentralPayment2 = await centralR.mint.mintPayment( centralTokens(43), ); const aliceSimCentralPayments = { - SecondaryToken: aliceSimoleanPayment, - CentralToken: aliceCentralTokenPayment2, + Secondary: aliceSimoleanPayment, + Central: aliceCentralPayment2, }; - const { - outcome: simCentralLiquidityOkP, - payout: aliceSimCentralPayoutP, - } = await zoe.offer( + const aliceSeat2 = await zoe.offer( aliceSimCentralLiquidityInvite, aliceSimCentralProposal, aliceSimCentralPayments, ); t.equals( - await simCentralLiquidityOkP, + await aliceSeat2.getOfferResult(), 'Added liquidity.', `Alice added simoleans and central liquidity`, ); - const simCentralPayments = await aliceSimCentralPayoutP; - const simoleanLiquidityPayout = await simCentralPayments.Liquidity; + const simoleanLiquidityPayout = await aliceSeat2.getPayout('Liquidity'); t.deepEquals( await simoleanLiquidityIssuer.getAmountOf(simoleanLiquidityPayout), @@ -354,11 +318,11 @@ test('multipoolAutoSwap with valid offers', async t => { `simoleanLiquidity minted was equal to the amount of central tokens added to pool`, ); t.deepEquals( - await E(publicAPI).getPoolAllocation(simoleanR.brand), + await E(publicFacet).getPoolAllocation(simoleanR.brand), harden({ - Simoleans: simoleans(398), - CentralToken: centralTokens(43), - SimoleansLiquidity: simoleanLiquidity(0), + Secondary: simoleans(398), + Central: centralTokens(43), + Liquidity: simoleanLiquidity(0), }), `The poolAmounts record should contain the new liquidity`, ); @@ -370,7 +334,7 @@ test('multipoolAutoSwap with valid offers', async t => { // Bob checks the price. Let's say he gives 74 simoleans, and he // wants to know how many moola he would get back. - const priceInMoola = await E(bobPublicAPI).getCurrentPrice( + const priceInMoola = await E(bobPublicFacet).getCurrentPrice( simoleans(74), moolaR.brand, ); @@ -381,7 +345,7 @@ test('multipoolAutoSwap with valid offers', async t => { ); // This is the same as making two synchronous exchanges - const priceInCentral = await E(bobPublicAPI).getCurrentPrice( + const priceInCentral = await E(bobPublicFacet).getCurrentPrice( simoleans(74), centralR.brand, ); @@ -391,7 +355,7 @@ test('multipoolAutoSwap with valid offers', async t => { `price is as expected for secondary token to central`, ); - const centralPriceInMoola = await E(bobPublicAPI).getCurrentPrice( + const centralPriceInMoola = await E(bobPublicFacet).getCurrentPrice( centralTokens(6), moolaR.brand, ); @@ -401,7 +365,7 @@ test('multipoolAutoSwap with valid offers', async t => { `price is as expected for secondary token to secondary token`, ); - const bobThirdInvite = await E(bobPublicAPI).makeSwapInvite(); + const bobThirdInvite = await E(bobPublicFacet).makeSwapInvitation(); const bobSimsForMoolaProposal = harden({ want: { Out: moola(10) }, give: { In: simoleans(74) }, @@ -410,15 +374,14 @@ test('multipoolAutoSwap with valid offers', async t => { In: bobSimoleanPayment, }); - const { payout: bobSimsForMoolaPayoutP } = await zoe.offer( + const bobSeat3 = await zoe.offer( bobThirdInvite, bobSimsForMoolaProposal, simsForMoolaPayments, ); - const bobSimsForMoolaPayout = await bobSimsForMoolaPayoutP; - const bobSimsPayout3 = await bobSimsForMoolaPayout.In; - const bobMoolaPayout3 = await bobSimsForMoolaPayout.Out; + const bobSimsPayout3 = await bobSeat3.getPayout('In'); + const bobMoolaPayout3 = await bobSeat3.getPayout('Out'); t.deepEqual( await moolaR.issuer.getAmountOf(bobMoolaPayout3), @@ -432,24 +395,24 @@ test('multipoolAutoSwap with valid offers', async t => { ); t.deepEqual( - await E(bobPublicAPI).getPoolAllocation(simoleanR.brand), + await E(bobPublicFacet).getPoolAllocation(simoleanR.brand), harden({ // 398 + 74 - Simoleans: simoleans(472), + Secondary: simoleans(472), // 43 - 6 - CentralToken: centralTokens(37), - SimoleansLiquidity: simoleanLiquidity(0), + Central: centralTokens(37), + Liquidity: simoleanLiquidity(0), }), `the simolean liquidity pool gains simoleans and loses central tokens`, ); t.deepEqual( - await E(bobPublicAPI).getPoolAllocation(moolaR.brand), + await E(bobPublicFacet).getPoolAllocation(moolaR.brand), harden({ // 101 - 10 - Moola: moola(91), + Secondary: moola(91), // 50 + 6 - CentralToken: centralTokens(56), - MoolaLiquidity: moolaLiquidity(0), + Central: centralTokens(56), + Liquidity: moolaLiquidity(0), }), `the moola liquidity pool loses moola and gains central tokens`, ); @@ -457,28 +420,27 @@ test('multipoolAutoSwap with valid offers', async t => { // Alice removes her liquidity // She's not picky... const aliceRemoveLiquidityInvite = await E( - publicAPI, - ).makeRemoveLiquidityInvite(); + publicFacet, + ).makeRemoveLiquidityInvitation(); const aliceRemoveLiquidityProposal = harden({ give: { Liquidity: moolaLiquidity(50) }, - want: { SecondaryToken: moola(91), CentralToken: centralTokens(56) }, + want: { Secondary: moola(91), Central: centralTokens(56) }, }); - const { - outcome: removeLiquidityResultP, - payout: aliceRemoveLiquidityPayoutP, - } = await zoe.offer( + const aliceSeat3 = await zoe.offer( aliceRemoveLiquidityInvite, aliceRemoveLiquidityProposal, harden({ Liquidity: liquidityPayout }), ); - t.equals(await removeLiquidityResultP, 'Liquidity successfully removed.'); + t.equals( + await aliceSeat3.getOfferResult(), + 'Liquidity successfully removed.', + ); - const aliceRemoveLiquidityPayout = await aliceRemoveLiquidityPayoutP; - const aliceMoolaPayout = await aliceRemoveLiquidityPayout.SecondaryToken; - const aliceCentralTokenPayout = await aliceRemoveLiquidityPayout.CentralToken; - const aliceMoolaLiquidityPayout = await aliceRemoveLiquidityPayout.Liquidity; + const aliceMoolaPayout = await aliceSeat3.getPayout('Secondary'); + const aliceCentralPayout = await aliceSeat3.getPayout('Central'); + const aliceLiquidityPayout = await aliceSeat3.getPayout('Liquidity'); t.deepEquals( await moolaR.issuer.getAmountOf(aliceMoolaPayout), @@ -486,21 +448,21 @@ test('multipoolAutoSwap with valid offers', async t => { `alice gets all the moola in the pool`, ); t.deepEquals( - await centralR.issuer.getAmountOf(aliceCentralTokenPayout), + await centralR.issuer.getAmountOf(aliceCentralPayout), centralTokens(56), `alice gets all the central tokens in the pool`, ); t.deepEquals( - await moolaLiquidityIssuer.getAmountOf(aliceMoolaLiquidityPayout), + await moolaLiquidityIssuer.getAmountOf(aliceLiquidityPayout), moolaLiquidity(0), `alice gets no liquidity tokens`, ); t.deepEqual( - await E(bobPublicAPI).getPoolAllocation(moolaR.brand), + await E(bobPublicFacet).getPoolAllocation(moolaR.brand), harden({ - Moola: moola(0), - CentralToken: centralTokens(0), - MoolaLiquidity: moolaLiquidity(50), + Secondary: moola(0), + Central: centralTokens(0), + Liquidity: moolaLiquidity(50), }), `liquidity is empty`, ); From 06de2bd72ab217a7a3d52063bcfb3045bdd8ef5f Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sun, 9 Aug 2020 12:39:08 -0600 Subject: [PATCH 14/31] refactor: remove BrandName parameter from ERTP types --- packages/ERTP/src/amountMath.js | 11 +++-- packages/ERTP/src/issuer.js | 9 ++--- packages/ERTP/src/types.js | 40 ++++++------------- .../zoe/src/contractFacet/contractFacet.js | 2 +- packages/zoe/src/contractFacet/offerSafety.js | 12 ++---- .../src/contractFacet/rightsConservation.js | 21 +++++----- .../zoe/src/contractSupport/zoeHelpers.js | 1 - packages/zoe/src/internal-types.js | 6 +-- packages/zoe/src/issuerTable.js | 9 +++-- packages/zoe/src/types.js | 22 +++++----- packages/zoe/src/zoeService/zoe.js | 5 ++- 11 files changed, 59 insertions(+), 79 deletions(-) diff --git a/packages/ERTP/src/amountMath.js b/packages/ERTP/src/amountMath.js index 8e2c288896d..a110bcca854 100644 --- a/packages/ERTP/src/amountMath.js +++ b/packages/ERTP/src/amountMath.js @@ -54,10 +54,9 @@ import './types'; * amounts, we can get the brand and find the issuer which matches the * brand. The issuer and the brand mutually validate each other. * - * @template {string} T - * @param {Brand} brand + * @param {Brand} brand * @param {MathHelpersName} mathHelpersName - * @returns {AmountMath} + * @returns {AmountMath} */ function makeAmountMath(brand, mathHelpersName) { mustBeComparable(brand); @@ -79,7 +78,7 @@ function makeAmountMath(brand, mathHelpersName) { /** * Make an amount from a value by adding the brand. * @param {Value} allegedValue - * @returns {Amount} + * @returns {Amount} */ make: allegedValue => { const value = helpers.doCoerce(allegedValue); @@ -90,8 +89,8 @@ function makeAmountMath(brand, mathHelpersName) { /** * Make sure this amount is valid and return it if so, throwing if invalid. - * @param {Amount} allegedAmount - * @returns {Amount} or throws if invalid + * @param {Amount} allegedAmount + * @returns {Amount} or throws if invalid */ coerce: allegedAmount => { // If the cache already has the allegedAmount, that diff --git a/packages/ERTP/src/issuer.js b/packages/ERTP/src/issuer.js index 158d32bb279..c1ec1f88bb1 100644 --- a/packages/ERTP/src/issuer.js +++ b/packages/ERTP/src/issuer.js @@ -11,7 +11,6 @@ import makeAmountMath from './amountMath'; import './types'; /** - * @template {string} T * @param {string} allegedName * @param {MathHelpersName} [mathHelpersName='nat'] * @returns {IssuerKit} @@ -55,10 +54,10 @@ function makeIssuerKit(allegedName, mathHelpersName = 'nat') { }; /** - * @returns {Purse} + * @returns {Purse} */ const makePurse = () => { - /** @type {Purse} */ + /** @type {Purse} */ const purse = harden({ deposit: (srcPayment, optAmount = undefined) => { if (isPromise(srcPayment)) { @@ -170,7 +169,7 @@ function makeIssuerKit(allegedName, mathHelpersName = 'nat') { return harden(newPayments); }; - /** @type {Issuer} */ + /** @type {Issuer} */ const issuer = harden({ getBrand: () => brand, getAllegedName: () => allegedName, @@ -274,7 +273,7 @@ function makeIssuerKit(allegedName, mathHelpersName = 'nat') { }, }); - /** @type {Mint} */ + /** @type {Mint} */ const mint = harden({ getIssuer: () => issuer, mintPayment: newAmount => { diff --git a/packages/ERTP/src/types.js b/packages/ERTP/src/types.js index 42ed02f7884..0d4b0cc809b 100644 --- a/packages/ERTP/src/types.js +++ b/packages/ERTP/src/types.js @@ -7,9 +7,7 @@ */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} Amount + * @typedef {Object} 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 @@ -38,9 +36,7 @@ */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} AmountMath + * @typedef {Object} AmountMath * Logic for manipulating amounts. * * Amounts are the canonical description of tradable goods. They are manipulated @@ -97,9 +93,7 @@ */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} Brand + * @typedef {Object} 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 @@ -119,9 +113,7 @@ */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} Issuer + * @typedef {Object} 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 @@ -190,7 +182,7 @@ * @callback MakeIssuerKit * @param {string} allegedName * @param {MathHelpersName} mathHelperName - * @returns {IssuerKit} + * @returns {IssuerKit} * * The allegedName is useful for debugging and double-checking * assumptions, but should not be trusted. @@ -199,19 +191,17 @@ * from the mathHelpers library. For example, natMathHelpers, the * default, is used for basic fungible tokens. * - * @typedef {Object} IssuerKit + * @typedef {Object} IssuerKit * The return value of makeIssuerKit * - * @property {Mint} mint - * @property {Issuer} issuer - * @property {AmountMath} amountMath - * @property {Brand} brand + * @property {Mint} mint + * @property {Issuer} issuer + * @property {AmountMath} amountMath + * @property {Brand} brand */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} Mint + * @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. * @@ -231,9 +221,7 @@ */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} Purse + * @typedef {Object} 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 @@ -263,9 +251,7 @@ */ /** - * @template {string} BrandName - A string representing the associated - * brand - * @typedef {Object} Payment + * @typedef {Object} 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 diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 3d2781623c2..48bb2393d1f 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -237,7 +237,7 @@ export function buildRootObject() { const invitationHandle = /** @type {InvitationHandle} */ (harden({})); invitationHandleToHandler.init(invitationHandle, offerHandler); - /** @type {Promise>} */ + /** @type {Promise} */ const invitationP = E(zoeInstanceAdmin).makeInvitation( invitationHandle, description, diff --git a/packages/zoe/src/contractFacet/offerSafety.js b/packages/zoe/src/contractFacet/offerSafety.js index 6cae5332d82..ffa965768a9 100644 --- a/packages/zoe/src/contractFacet/offerSafety.js +++ b/packages/zoe/src/contractFacet/offerSafety.js @@ -1,11 +1,10 @@ // @ts-check /** - * @template {string} T * Helper to perform satisfiesWant and satisfiesGive. Is * allocationAmount greater than or equal to requiredAmount for every * keyword of giveOrWant? - * @param {(brand: Brand) => AmountMath} getAmountMath + * @param {(brand: Brand) => AmountMath} getAmountMath * @param {ProposalRecord["give"] | ProposalRecord["want"]} giveOrWant * @param {AmountKeywordRecord} allocation */ @@ -26,8 +25,7 @@ const satisfiesInternal = (getAmountMath, giveOrWant = {}, allocation) => { /** * For this allocation to satisfy what the user wanted, their * allocated amounts must be greater than or equal to proposal.want. - * @template {string} T - * @param {(brand: Brand) => AmountMath} getAmountMath - a + * @param {(brand: Brand) => AmountMath} getAmountMath - a * function that takes a brand and returns the appropriate amountMath. * The function must have an amountMath for every brand in * proposal.want. @@ -48,8 +46,7 @@ const satisfiesWant = (getAmountMath, proposal, allocation) => * For this allocation to count as a full refund, the allocated * amounts must be greater than or equal to what was originally * offered (proposal.give). - * @template {string} T - * @param {(brand: Brand) => AmountMath} getAmountMath - a + * @param {(brand: Brand) => AmountMath} getAmountMath - a * function that takes a brand and returns the appropriate amountMath. * The function must have an amountMath for every brand in * proposal.give. @@ -73,8 +70,7 @@ const satisfiesGive = (getAmountMath, proposal, allocation) => * `proposal.give` (giving a refund) or whether we fully satisfy * `proposal.want`. Both can be fully satisfied. * - * @template {string} T - * @param {(brand: Brand) => AmountMath} getAmountMath - a + * @param {(brand: Brand) => AmountMath} getAmountMath - a * function that takes a brand and returns the appropriate amountMath. * The function must have an amountMath for every brand in * proposal.want and proposal.give. diff --git a/packages/zoe/src/contractFacet/rightsConservation.js b/packages/zoe/src/contractFacet/rightsConservation.js index e118ab5f8a8..20676afe311 100644 --- a/packages/zoe/src/contractFacet/rightsConservation.js +++ b/packages/zoe/src/contractFacet/rightsConservation.js @@ -7,13 +7,12 @@ import '../../exported'; import '../internal-types'; /** - * @template {string} T * Iterate over the amounts and sum, storing the sums in a * map by brand. - * @param {(brand: Brand) => AmountMath} getAmountMath - a function + * @param {(brand: Brand) => AmountMath} getAmountMath - a function * to get amountMath given a brand. - * @param {Amount[]} amounts - an array of amounts - * @returns {Store, Amount>} sumsByBrand - a map of Brand keys and + * @param {Amount[]} amounts - an array of amounts + * @returns {Store} sumsByBrand - a map of Brand keys and * Amount values. The amounts are the sums. */ const sumByBrand = (getAmountMath, amounts) => { @@ -31,12 +30,11 @@ const sumByBrand = (getAmountMath, amounts) => { }; /** - * @template {string} T * Do the left sums by brand equal the right sums by brand? - * @param {(brand: Brand) => AmountMath} getAmountMath - a function + * @param {(brand: Brand) => AmountMath} getAmountMath - a function * to get amountMath given a brand. - * @param {Store, Amount>} leftSumsByBrand - a map of brands to sums - * @param {Store, Amount>} rightSumsByBrand - a map of brands to sums + * @param {Store} leftSumsByBrand - a map of brands to sums + * @param {Store} rightSumsByBrand - a map of brands to sums * indexed by issuer */ const isEqualPerBrand = (getAmountMath, leftSumsByBrand, rightSumsByBrand) => { @@ -58,14 +56,13 @@ const isEqualPerBrand = (getAmountMath, leftSumsByBrand, rightSumsByBrand) => { }; /** - * @template {string} T * `areRightsConserved` checks that the total amount per brand is * equal to the total amount per brand in the proposed reallocation - * @param {(brand: Brand) => AmountMath} getAmountMath - a function + * @param {(brand: Brand) => AmountMath} getAmountMath - a function * to get amountMath given a brand. - * @param {Amount[]} previousAmounts - an array of the amounts before the + * @param {Amount[]} previousAmounts - an array of the amounts before the * proposed reallocation - * @param {Amount[]} newAmounts - an array of the amounts in the + * @param {Amount[]} newAmounts - an array of the amounts in the * proposed reallocation * * @returns {boolean} isEqualPerBrand diff --git a/packages/zoe/src/contractSupport/zoeHelpers.js b/packages/zoe/src/contractSupport/zoeHelpers.js index 31186e70812..01cde0629c1 100644 --- a/packages/zoe/src/contractSupport/zoeHelpers.js +++ b/packages/zoe/src/contractSupport/zoeHelpers.js @@ -321,7 +321,6 @@ export const makeEmptyOffer = zcf => { * Escrow payments with Zoe and reallocate the amount of each * payment to a recipient. * - * @template {string} T * @param {ContractFacet} zcf * @param {ZCFSeat} recipientSeat * @param {AmountKeywordRecord} giveAmountKeywordRecord - the keywords diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index fd793d11e79..e2a8fae4032 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -109,7 +109,7 @@ * @property {(invitationHandle: InvitationHandle, * description: string, * customProperties?: {}, - * ) => Payment<'ZoeInvitation'>} makeInvitation + * ) => Payment} makeInvitation * @property {() => void} shutdown * @property {(issuerP: ERef, keyword: Keyword) => void} saveIssuer * @@ -152,7 +152,7 @@ * @callback ExecuteContract * @param {SourceBundle} bundle * @param {ZoeService} zoeService - * @param {Issuer<'ZoeInvitation'>} invitationIssuer + * @param {Issuer} invitationIssuer * @param {ZoeInstanceAdmin} zoeInstanceAdmin * @param {InstanceRecord} instanceRecord * @returns {ExecuteContractResult} @@ -163,7 +163,7 @@ * @callback MakeMakeInstanceFn * @param {VatAdminSvc} vatAdminSvc, * @param {GetPromiseForIssuerRecord} getPromiseForIssuerRecord, - * @param {IssuerKit<'ZoeInvitation'>} invitationKit, + * @param {IssuerKit} invitationKit, * @param {HasInstallation} hasInstallation, * @param {ZoeService} zoeService, * @param {AddInstance} addInstance, diff --git a/packages/zoe/src/issuerTable.js b/packages/zoe/src/issuerTable.js index d70ee18e699..54c83ef7de3 100644 --- a/packages/zoe/src/issuerTable.js +++ b/packages/zoe/src/issuerTable.js @@ -36,11 +36,12 @@ const makeIssuerTable = () => { 'amountMath', ]); + /** @param {Table} table */ const makeCustomMethods = table => { - /** @type {WeakStore,any>} */ + /** @type {WeakStore>} */ const issuersInProgress = makeWeakStore('issuer'); - /** @type {WeakStore,Brand>} */ + /** @type {WeakStore} */ const issuerToBrand = makeWeakStore('issuer'); const registerIssuerRecord = issuerRecord => { @@ -54,7 +55,7 @@ const makeIssuerTable = () => { // inProgress table, and once we have the Issuer, build the record, fill in // the table, and resolve the promise. /** - * @param {Issuer} issuer + * @param {Issuer} issuer */ function buildTableEntryAndPlaceHolder(issuer) { // remote calls which immediately return a promise @@ -64,7 +65,7 @@ const makeIssuerTable = () => { /** * @type {[ - * PromiseLike>, + * PromiseLike, * PromiseLike<'nat' | 'set' | 'strSet'>, * PromiseLike * ]} diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 80ad553911d..21ce35f24a1 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -40,7 +40,7 @@ * within its vat. The contract and ZCF never have direct access to * the users' payments or the Zoe purses. * - * @property {() => Issuer<'ZoeInvitation'>} getInvitationIssuer + * @property {() => Issuer} getInvitationIssuer * * Zoe has a single `invitationIssuer` for the entirety of its * lifetime. By having a reference to Zoe, a user can get the @@ -52,10 +52,10 @@ * @property {Install} install * @property {MakeInstance} makeInstance * @property {Offer} offer - * @property {(Instance) => Object} getPublicFacet - * @property {(Instance) => IssuerKeywordRecord} getIssuers - * @property {(Instance) => BrandKeywordRecord} getBrands - * @property {(Instance) => Object} getTerms + * @property {(instance: Instance) => Object} getPublicFacet + * @property {(instance: Instance) => IssuerKeywordRecord} getIssuers + * @property {(instance: Instance) => BrandKeywordRecord} getBrands + * @property {(instance: Instance) => Object} getTerms */ /** @@ -112,7 +112,7 @@ /** * @typedef {Object} MakeInstanceResult * @property {CreatorFacetWInstance & Record} creatorFacet - * @property {Payment<'ZoeInvitation'>} creatorInvitation + * @property {Payment} creatorInvitation */ /** @@ -188,7 +188,7 @@ * @property {MakeInvitation} makeInvitation * @property {Shutdown} shutdown * @property {() => ZoeService} getZoeService - * @property {() => Issuer<'ZoeInvitation'>} getInvitationIssuer + * @property {() => Issuer} getInvitationIssuer * @property {() => InstanceRecord } getInstanceRecord * @property {(issuer: Issuer) => Brand} getBrandForIssuer * @property {GetAmountMath} getAmountMath @@ -391,18 +391,18 @@ */ /** - * @typedef {Payment<'ZoeInvitation'>} Invitation + * @typedef {Payment} Invitation */ /** - * @typedef {{}} Instance + * @typedef {Handle<'InstanceHandle'>} Instance */ /** * @template T * @callback GetAmountMath - * @param {Brand} brand - * @returns {AmountMath} + * @param {Brand} brand + * @returns {AmountMath} */ /** diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index e73bdb12666..e016a5bd608 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -18,6 +18,7 @@ import { makeIssuerTable } from '../issuerTable'; import zcfContractBundle from '../../bundles/bundle-contractFacet'; import { arrayToObj } from '../objArrayConversion'; import { cleanKeywords, cleanProposal } from './cleanProposal'; +import { makeHandle } from '../table'; /** * Create an instance of Zoe. @@ -40,6 +41,7 @@ function makeZoe(vatAdminSvc) { /** @type {GetAmountMath} */ const getAmountMath = brand => issuerTable.get(brand).amountMath; + /** @type {WeakStore>} */ const brandToPurse = makeWeakStore('brand'); /** @@ -77,6 +79,7 @@ function makeZoe(vatAdminSvc) { amountMath: invitationAmountMath, } = invitationKit; + /** @param {Issuer[]} issuers */ const getPromiseForIssuerRecords = issuers => Promise.all(issuers.map(issuerTable.getPromiseForIssuerRecord)); assert( @@ -85,7 +88,7 @@ function makeZoe(vatAdminSvc) { ); const zoeSeatAdmins = new Set(); - const instance = harden({}); + const instance = makeHandle('InstanceHandle'); const keywords = cleanKeywords(uncleanIssuerKeywordRecord); From 5cb4e507f8b1249c651ab63f63a4f39a225b8fca Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Fri, 7 Aug 2020 11:33:05 -0700 Subject: [PATCH 15/31] feat: add the ability for a contract to get a synchronous seat zcf.addEmptySeat() would return a zcfSeat that the contract could use to hold allocations. untested --- .../zoe/src/contractFacet/contractFacet.js | 43 +++++++++++++++++++ packages/zoe/src/zoeService/zoe.js | 30 +++++++++++++ 2 files changed, 73 insertions(+) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 48bb2393d1f..54f2e45cadd 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -12,6 +12,8 @@ import { E } from '@agoric/eventual-send'; import makeWeakStore from '@agoric/weak-store'; import makeAmountMath from '@agoric/ertp/src/amountMath'; +import { makeNotifierKit } from '@agoric/notifier'; +import { makePromiseKit } from '@agoric/promise-kit'; import { areRightsConserved } from './rightsConservation'; import { makeIssuerTable } from '../issuerTable'; import { assertKeywordName, getKeywords } from '../zoeService/cleanProposal'; @@ -164,6 +166,20 @@ export function buildRootObject() { return zcfMint; }; + function updateNotiferFrom(updater, notifier, nextCount = undefined) { + notifier.getUpdateSince(nextCount).then( + ({ value, updateCount }) => { + if (updateCount) { + updater.updateState(value); + updateNotiferFrom(updater, notifier, updateCount); + } else { + updater.finish(value); + } + }, + e => updater.fail(e), + ); + } + /** @type ContractFacet */ const zcf = { reallocate: (/** @type SeatStaging[] */ ...seatStagings) => { @@ -256,6 +272,33 @@ export function buildRootObject() { issuerTable.getIssuerRecordByIssuer(issuer).brand, getAmountMath, makeZCFMint, + addEmptySeat: keyword => { + const initialAllocation = harden({}); + const proposal = harden({ want: {} }); + const { notifier, updater } = makeNotifierKit(); + const zoeSeatAdminPromiseKit = makePromiseKit(); + + E(zoeInstanceAdmin) + .makeEmptySeat(keyword, initialAllocation, proposal) + .then(({ seatAdmin: zoeSeatAdmin, notifier: zoeNotifier }) => { + updateNotiferFrom(updater, zoeNotifier); + zoeSeatAdminPromiseKit.resolve(zoeSeatAdmin); + }); + + const seatData = harden({ + proposal, + initialAllocation, + notifier, + }); + const { zcfSeat, zcfSeatAdmin } = makeSeatAdmin( + allSeatStagings, + zoeSeatAdminPromiseKit.promise, + seatData, + getAmountMath, + ); + seatToZCFSeatAdmin.init(zcfSeat, zcfSeatAdmin); + return { zcfSeat, zcfSeatAdmin }; + }, }; harden(zcf); diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index e016a5bd608..308f5802e4c 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -207,6 +207,34 @@ function makeZoe(vatAdminSvc) { brandToPurse.init(brand, E(issuer).makeEmptyPurse()); } }), + makeEmptySeat: (keyword, initialAllocation) => { + let currentAllocation = initialAllocation; + + const { notifier, updater } = makeNotifierKit(); + + const seatAdmin = { + replaceAllocation: replacementAllocation => { + harden(replacementAllocation); + updater.updateState(replacementAllocation); + currentAllocation = replacementAllocation; + }, + exit: () => { + updater.finish(undefined); + const instanceAdmin = instanceToInstanceAdmin.get(instance); + instanceAdmin.removeZoeSeatAdmin(seatAdmin); + + // burn the holdings to keep Zoe's book straight + Object.entries(currentAllocation).forEach(([_, payoutAmount]) => { + const purse = brandToPurse.get(payoutAmount.brand); + const { issuer } = issuerTable.get(payoutAmount.brand); + E(issuer).burn(E(purse).withdraw(payoutAmount)); + }); + }, + }; + + harden(seatAdmin); + return { seatAdmin, notifier }; + }, shutdown: () => { exitAllSeats(); adminNode.terminate(); @@ -237,6 +265,8 @@ function makeZoe(vatAdminSvc) { instanceToInstanceAdmin.init(instance, instanceAdmin); + // At this point, the contract will start executing. All must be ready + const { creatorFacet = {}, publicFacet = {}, From 5235f5b7cc56f207a931ddd28c83345d7371ab1e Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Sun, 9 Aug 2020 11:40:27 -0700 Subject: [PATCH 16/31] refactor: cleanup synchronous ZCF seat creation drop keyword parameter in addEmptySeat() extract makeZoeSeatAdminKit, use it for emptySeat, too. rename zcf seat construction to use 'Kit' --- .../zoe/src/contractFacet/contractFacet.js | 23 +-- packages/zoe/src/contractFacet/exit.js | 5 + packages/zoe/src/contractFacet/seat.js | 4 +- packages/zoe/src/internal-types.js | 2 +- packages/zoe/src/zoeService/zoe.js | 141 ++++++------------ packages/zoe/src/zoeService/zoeSeat.js | 63 ++++++++ 6 files changed, 125 insertions(+), 113 deletions(-) create mode 100644 packages/zoe/src/zoeService/zoeSeat.js diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 54f2e45cadd..7b56ba1e175 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -18,7 +18,7 @@ import { areRightsConserved } from './rightsConservation'; import { makeIssuerTable } from '../issuerTable'; import { assertKeywordName, getKeywords } from '../zoeService/cleanProposal'; import { evalContractBundle } from './evalContractCode'; -import { makeSeatAdmin } from './seat'; +import { makeZcfSeatAdminKit } from './seat'; import { makeExitObj } from './exit'; import { objectMap } from '../objArrayConversion'; @@ -272,17 +272,19 @@ export function buildRootObject() { issuerTable.getIssuerRecordByIssuer(issuer).brand, getAmountMath, makeZCFMint, - addEmptySeat: keyword => { + addEmptySeat: () => { const initialAllocation = harden({}); const proposal = harden({ want: {} }); const { notifier, updater } = makeNotifierKit(); const zoeSeatAdminPromiseKit = makePromiseKit(); + const userSeatPromiseKit = makePromiseKit(); E(zoeInstanceAdmin) - .makeEmptySeat(keyword, initialAllocation, proposal) - .then(({ seatAdmin: zoeSeatAdmin, notifier: zoeNotifier }) => { + .makeEmptySeat(initialAllocation, proposal) + .then(({ zoeSeatAdmin, notifier: zoeNotifier, userSeat }) => { updateNotiferFrom(updater, zoeNotifier); zoeSeatAdminPromiseKit.resolve(zoeSeatAdmin); + userSeatPromiseKit.resolve(userSeat); }); const seatData = harden({ @@ -290,24 +292,24 @@ export function buildRootObject() { initialAllocation, notifier, }); - const { zcfSeat, zcfSeatAdmin } = makeSeatAdmin( + const { zcfSeat, zcfSeatAdmin } = makeZcfSeatAdminKit( allSeatStagings, zoeSeatAdminPromiseKit.promise, seatData, getAmountMath, ); seatToZCFSeatAdmin.init(zcfSeat, zcfSeatAdmin); - return { zcfSeat, zcfSeatAdmin }; + return { zcfSeat, zcfSeatAdmin, userSeat: userSeatPromiseKit.promise }; }, }; harden(zcf); - // To Zoe, we will return the invite and an object such that Zoe - // can tell us about new seats. + // addSeatObject gives Zoe the ability to notify ZCF when a new seat is + // added in offer(). ZCF responds with the exitObj and offerResult. /** @type AddSeatObj */ const addSeatObj = { addSeat: (invitationHandle, zoeSeatAdmin, seatData) => { - const { zcfSeatAdmin, zcfSeat } = makeSeatAdmin( + const { zcfSeatAdmin, zcfSeat } = makeZcfSeatAdminKit( allSeatStagings, zoeSeatAdmin, seatData, @@ -324,8 +326,7 @@ export function buildRootObject() { }); const exitObj = makeExitObj(seatData.proposal, zoeSeatAdmin); /** @type AddSeatResult */ - const addSeatResult = { offerResultP, exitObj }; - return harden(addSeatResult); + return harden({ offerResultP, exitObj }); }, }; harden(addSeatObj); diff --git a/packages/zoe/src/contractFacet/exit.js b/packages/zoe/src/contractFacet/exit.js index 741f7ebed31..a4c699cf506 100644 --- a/packages/zoe/src/contractFacet/exit.js +++ b/packages/zoe/src/contractFacet/exit.js @@ -1,6 +1,11 @@ import { assert, details, q } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; +/** + * Makes the appropriate exitObj, which runs in ZCF and allows the seat's owner + * to request the position be exited. + */ + /** @type MakeExitObj */ export const makeExitObj = (proposal, zoeSeatAdmin) => { const [exitKind] = Object.getOwnPropertyNames(proposal.exit); diff --git a/packages/zoe/src/contractFacet/seat.js b/packages/zoe/src/contractFacet/seat.js index acea74df054..292eb718f6c 100644 --- a/packages/zoe/src/contractFacet/seat.js +++ b/packages/zoe/src/contractFacet/seat.js @@ -8,8 +8,8 @@ import { isOfferSafe } from './offerSafety'; import '../../exported'; import '../internal-types'; -/** @type MakeSeatAdmin */ -export const makeSeatAdmin = ( +/** @type MakeZcfSeatAdminKit */ +export const makeZcfSeatAdminKit = ( allSeatStagings, zoeSeatAdmin, seatData, diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index e2a8fae4032..56eb61473f3 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -64,7 +64,7 @@ /** * Make the ZCF seat and seat admin - * @callback MakeSeatAdmin + * @callback MakeZcfSeatAdmin * @param {WeakSet} allSeatStagings - a set of valid * seatStagings where allocations have been checked for offerSafety * @param {ZoeSeatAdmin} zoeSeatAdmin diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index 308f5802e4c..989518c82ed 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -3,7 +3,6 @@ import makeIssuerKit from '@agoric/ertp'; import makeWeakStore from '@agoric/weak-store'; import { assert, details } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; -import { makeNotifierKit } from '@agoric/notifier'; import { makePromiseKit } from '@agoric/promise-kit'; /** @@ -15,6 +14,7 @@ import '../../exported'; import '../internal-types'; import { makeIssuerTable } from '../issuerTable'; +import { makeZoeSeatAdminKit } from './zoeSeat'; import zcfContractBundle from '../../bundles/bundle-contractFacet'; import { arrayToObj } from '../objArrayConversion'; import { cleanKeywords, cleanProposal } from './cleanProposal'; @@ -181,6 +181,29 @@ function makeZoe(vatAdminSvc) { return zoeMint; }; + const bundle = installation.getBundle(); + const addSeatObjPromiseKit = makePromiseKit(); + const publicFacetPromiseKit = makePromiseKit(); + + /** @type {InstanceAdmin} */ + const instanceAdmin = { + addZoeSeatAdmin: async (invitationHandle, zoeSeatAdmin, seatData) => { + zoeSeatAdmins.add(zoeSeatAdmin); + return E( + /** @type Promise */ (addSeatObjPromiseKit.promise), + ).addSeat(invitationHandle, zoeSeatAdmin, seatData); + }, + hasZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.has(zoeSeatAdmin), + removeZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.delete(zoeSeatAdmin), + getPublicFacet: () => publicFacetPromiseKit.promise, + getTerms: () => instanceRecord.terms, + getIssuers: () => instanceRecord.issuerKeywordRecord, + getBrands: () => instanceRecord.brandKeywordRecord, + getInstance: () => instance, + }; + + instanceToInstanceAdmin.init(instance, instanceAdmin); + /** @type {ZoeInstanceAdmin} */ const zoeInstanceAdminForZcf = { makeInvitation: (invitationHandle, description, customProperties) => { @@ -207,33 +230,15 @@ function makeZoe(vatAdminSvc) { brandToPurse.init(brand, E(issuer).makeEmptyPurse()); } }), - makeEmptySeat: (keyword, initialAllocation) => { - let currentAllocation = initialAllocation; - - const { notifier, updater } = makeNotifierKit(); - - const seatAdmin = { - replaceAllocation: replacementAllocation => { - harden(replacementAllocation); - updater.updateState(replacementAllocation); - currentAllocation = replacementAllocation; - }, - exit: () => { - updater.finish(undefined); - const instanceAdmin = instanceToInstanceAdmin.get(instance); - instanceAdmin.removeZoeSeatAdmin(seatAdmin); - - // burn the holdings to keep Zoe's book straight - Object.entries(currentAllocation).forEach(([_, payoutAmount]) => { - const purse = brandToPurse.get(payoutAmount.brand); - const { issuer } = issuerTable.get(payoutAmount.brand); - E(issuer).burn(E(purse).withdraw(payoutAmount)); - }); - }, - }; - - harden(seatAdmin); - return { seatAdmin, notifier }; + makeEmptySeat: (initialAllocation, proposal) => { + const { userSeat, notifier, zoeSeatAdmin } = makeZoeSeatAdminKit( + initialAllocation, + instanceAdmin, + proposal, + brandToPurse, + ); + zoeSeatAdmins.add(zoeSeatAdmin); + return { userSeat, notifier, zoeSeatAdmin }; }, shutdown: () => { exitAllSeats(); @@ -242,29 +247,6 @@ function makeZoe(vatAdminSvc) { makeZoeMint, }; - const bundle = installation.getBundle(); - const addSeatObjPromiseKit = makePromiseKit(); - const publicFacetPromiseKit = makePromiseKit(); - - /** @type {InstanceAdmin} */ - const instanceAdmin = { - addZoeSeatAdmin: async (invitationHandle, zoeSeatAdmin, seatData) => { - zoeSeatAdmins.add(zoeSeatAdmin); - return E( - /** @type Promise */ (addSeatObjPromiseKit.promise), - ).addSeat(invitationHandle, zoeSeatAdmin, seatData); - }, - hasZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.has(zoeSeatAdmin), - removeZoeSeatAdmin: zoeSeatAdmin => zoeSeatAdmins.delete(zoeSeatAdmin), - getPublicFacet: () => publicFacetPromiseKit.promise, - getTerms: () => instanceRecord.terms, - getIssuers: () => instanceRecord.issuerKeywordRecord, - getBrands: () => instanceRecord.brandKeywordRecord, - getInstance: () => instance, - }; - - instanceToInstanceAdmin.init(instance, instanceAdmin); - // At this point, the contract will start executing. All must be ready const { @@ -331,60 +313,21 @@ function makeZoe(vatAdminSvc) { return Promise.all(paymentDepositedPs).then(amountsArray => { const initialAllocation = arrayToObj(amountsArray, proposalKeywords); - const payoutPromiseKit = makePromiseKit(); const offerResultPromiseKit = makePromiseKit(); const exitObjPromiseKit = makePromiseKit(); - const { notifier, updater } = makeNotifierKit(); - let currentAllocation = initialAllocation; - const instanceAdmin = instanceToInstanceAdmin.get(instance); /** @type {ZoeSeatAdmin} */ - const zoeSeatAdmin = { - replaceAllocation: replacementAllocation => { - assert( - instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), - `Cannot replace allocation. Seat has already exited`, - ); - harden(replacementAllocation); - // Merging happens in ZCF, so replacementAllocation can - // replace the old allocation entirely. - updater.updateState(replacementAllocation); - currentAllocation = replacementAllocation; - }, - exit: () => { - assert( - instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), - `Cannot exit seat. Seat has already exited`, - ); - updater.finish(undefined); - instanceAdmin.removeZoeSeatAdmin(zoeSeatAdmin); - - /** @type {PaymentPKeywordRecord} */ - const payout = {}; - Object.entries(currentAllocation).forEach( - ([keyword, payoutAmount]) => { - const purse = brandToPurse.get(payoutAmount.brand); - payout[keyword] = E(purse).withdraw(payoutAmount); - }, - ); - harden(payout); - payoutPromiseKit.resolve(payout); + const { userSeat, notifier, zoeSeatAdmin } = makeZoeSeatAdminKit( + initialAllocation, + instanceAdmin, + proposal, + brandToPurse, + { + offerResult: offerResultPromiseKit, + exitObj: exitObjPromiseKit, }, - }; - harden(zoeSeatAdmin); - - /** @type {UserSeat} */ - const userSeat = { - getCurrentAllocation: async () => currentAllocation, - getProposal: async () => proposal, - getPayouts: async () => payoutPromiseKit.promise, - getPayout: async keyword => - payoutPromiseKit.promise.then(payouts => payouts[keyword]), - getOfferResult: async () => offerResultPromiseKit.promise, - exit: async () => - exitObjPromiseKit.promise.then(exitObj => E(exitObj).exit()), - }; + ); const seatData = harden({ proposal, initialAllocation, notifier }); diff --git a/packages/zoe/src/zoeService/zoeSeat.js b/packages/zoe/src/zoeService/zoeSeat.js new file mode 100644 index 00000000000..a1521f3b5d3 --- /dev/null +++ b/packages/zoe/src/zoeService/zoeSeat.js @@ -0,0 +1,63 @@ +import { makePromiseKit } from '@agoric/promise-kit'; +import { makeNotifierKit } from '@agoric/notifier'; +import { assert } from '@agoric/assert'; +import { E } from '@agoric/eventual-send'; + +export const makeZoeSeatAdminKit = ( + initialAllocation, + instanceAdmin, + proposal, + brandToPurse, + promises = {}, +) => { + const payoutPromiseKit = promises.payout || makePromiseKit(); + const offerResultPromiseKit = promises.offerResult || makePromiseKit(); + const exitObjPromiseKit = promises.exitObj || makePromiseKit(); + const { notifier, updater } = makeNotifierKit(); + + let currentAllocation = initialAllocation; + const zoeSeatAdmin = harden({ + replaceAllocation: replacementAllocation => { + assert( + instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), + `Cannot replace allocation. Seat has already exited`, + ); + harden(replacementAllocation); + // Merging happens in ZCF, so replacementAllocation can + // replace the old allocation entirely. + updater.updateState(replacementAllocation); + currentAllocation = replacementAllocation; + }, + exit: () => { + assert( + instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), + `Cannot exit seat. Seat has already exited`, + ); + updater.finish(undefined); + instanceAdmin.removeZoeSeatAdmin(zoeSeatAdmin); + + /** @type {PaymentPKeywordRecord} */ + const payout = {}; + Object.entries(currentAllocation).forEach(([keyword, payoutAmount]) => { + const purse = brandToPurse.get(payoutAmount.brand); + payout[keyword] = E(purse).withdraw(payoutAmount); + }); + harden(payout); + payoutPromiseKit.resolve(payout); + }, + getCurrentAllocation: () => currentAllocation, + }); + + const userSeat = harden({ + getCurrentAllocation: async () => zoeSeatAdmin.getCurrentAllocation(), + getProposal: async () => proposal, + getPayouts: async () => payoutPromiseKit.promise, + getPayout: async keyword => + payoutPromiseKit.promise.then(payouts => payouts[keyword]), + getOfferResult: async () => offerResultPromiseKit.promise, + exit: async () => + exitObjPromiseKit.promise.then(exitObj => E(exitObj).exit()), + }); + + return { userSeat, zoeSeatAdmin, notifier }; +}; From 6e897bdadebbbcc3e3e041bad8be82d11a5a8b95 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Sun, 9 Aug 2020 13:51:32 -0700 Subject: [PATCH 17/31] refactor: use updateFromNotifier from library --- .../zoe/src/contractFacet/contractFacet.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 7b56ba1e175..a6d8b64a2c8 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -12,7 +12,7 @@ import { E } from '@agoric/eventual-send'; import makeWeakStore from '@agoric/weak-store'; import makeAmountMath from '@agoric/ertp/src/amountMath'; -import { makeNotifierKit } from '@agoric/notifier'; +import { makeNotifierKit, updateFromNotifier } from '@agoric/notifier'; import { makePromiseKit } from '@agoric/promise-kit'; import { areRightsConserved } from './rightsConservation'; import { makeIssuerTable } from '../issuerTable'; @@ -166,20 +166,6 @@ export function buildRootObject() { return zcfMint; }; - function updateNotiferFrom(updater, notifier, nextCount = undefined) { - notifier.getUpdateSince(nextCount).then( - ({ value, updateCount }) => { - if (updateCount) { - updater.updateState(value); - updateNotiferFrom(updater, notifier, updateCount); - } else { - updater.finish(value); - } - }, - e => updater.fail(e), - ); - } - /** @type ContractFacet */ const zcf = { reallocate: (/** @type SeatStaging[] */ ...seatStagings) => { @@ -282,7 +268,7 @@ export function buildRootObject() { E(zoeInstanceAdmin) .makeEmptySeat(initialAllocation, proposal) .then(({ zoeSeatAdmin, notifier: zoeNotifier, userSeat }) => { - updateNotiferFrom(updater, zoeNotifier); + updateFromNotifier(updater, zoeNotifier); zoeSeatAdminPromiseKit.resolve(zoeSeatAdmin); userSeatPromiseKit.resolve(userSeat); }); From 1624f6f35bc421bdacdf46a48a460a57b780d260 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sun, 9 Aug 2020 14:22:59 -0600 Subject: [PATCH 18/31] refactor: add types to reveal the wallet changes needed for Zoe --- .../lib/ag-solo/vats/lib-board.js | 2 + packages/dapp-svelte-wallet/api/jsconfig.json | 18 + packages/dapp-svelte-wallet/api/package.json | 7 +- .../api/src/lib-dehydrate.js | 21 +- .../dapp-svelte-wallet/api/src/lib-wallet.js | 321 +++++++++++------- .../dapp-svelte-wallet/api/src/observable.js | 6 + packages/dapp-svelte-wallet/api/src/types.js | 99 ++++++ .../api/test/test-lib-wallet.js | 5 + packages/zoe/src/objArrayConversion.js | 8 +- 9 files changed, 347 insertions(+), 140 deletions(-) create mode 100644 packages/dapp-svelte-wallet/api/jsconfig.json create mode 100644 packages/dapp-svelte-wallet/api/src/types.js diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/lib-board.js b/packages/cosmic-swingset/lib/ag-solo/vats/lib-board.js index f49228e983c..4de2a444128 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/lib-board.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/lib-board.js @@ -37,3 +37,5 @@ function makeBoard(seed = 0) { } export { makeBoard }; + +/** @typedef {ReturnType} Board */ diff --git a/packages/dapp-svelte-wallet/api/jsconfig.json b/packages/dapp-svelte-wallet/api/jsconfig.json new file mode 100644 index 00000000000..5150e130bd4 --- /dev/null +++ b/packages/dapp-svelte-wallet/api/jsconfig.json @@ -0,0 +1,18 @@ +// This file can contain .js-specific Typescript compiler config. +{ + "compilerOptions": { + "target": "es2019", + + "noEmit": true, +/* + // The following flags are for creating .d.ts files: + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, +*/ + "downlevelIteration": true, + "strictNullChecks": true, + "moduleResolution": "node", + }, + "include": ["src/**/*.js", "test/**/*.js", "exports.js"], +} diff --git a/packages/dapp-svelte-wallet/api/package.json b/packages/dapp-svelte-wallet/api/package.json index e0ae76c7ec3..4b6b38cf4d2 100644 --- a/packages/dapp-svelte-wallet/api/package.json +++ b/packages/dapp-svelte-wallet/api/package.json @@ -6,9 +6,10 @@ "scripts": { "build": "exit 0", "test": "tape -r esm 'test/**/*.js'", - "lint": "eslint '**/*.{js,jsx}'", - "lint-fix": "eslint --fix '**/*.{js,jsx}'", - "lint-check": "eslint '**/*.{js,jsx}'", + "lint": "yarn lint:types && eslint '**/*.js'", + "lint-fix": "yarn lint --fix", + "lint-check": "yarn lint", + "lint:types": "tsc -p jsconfig.json", "lint-fix-jessie": "eslint -c '.eslintrc-jessie.js' --fix '**/*.{js,jsx}'", "lint-check-jessie": "eslint -c '.eslintrc-jessie.js' '**/*.{js,jsx}'" }, diff --git a/packages/dapp-svelte-wallet/api/src/lib-dehydrate.js b/packages/dapp-svelte-wallet/api/src/lib-dehydrate.js index 0a65e9e0a45..23080e63aa2 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-dehydrate.js +++ b/packages/dapp-svelte-wallet/api/src/lib-dehydrate.js @@ -36,7 +36,7 @@ export const makeDehydrator = (initialUnnamedCount = 0) => { const searchOrder = []; // Paths are kept across all kinds. - /** @type {Store} */ + /** @type {Store} */ const valToPaths = makeStore('value'); /** @@ -83,11 +83,16 @@ export const makeDehydrator = (initialUnnamedCount = 0) => { return `${IMPLODE_PREFIX}${JSON.stringify(path)}`; }; + /** + * @template T + * @param {string} kind + * @returns {Mapping} + */ const makeMapping = kind => { assert.typeof(kind, 'string', details`kind ${kind} must be a string`); - /** @type {Store} */ + /** @type {Store} */ const rawValToPetname = makeStore('value'); - /** @type {Store} */ + /** @type {Store} */ const valToPetname = { ...rawValToPetname, set(key, val) { @@ -108,9 +113,9 @@ export const makeDehydrator = (initialUnnamedCount = 0) => { return rawValToPetname.values().map(val => explode(val)); }, }; - /** @type {Store} */ + /** @type {Store} */ const rawPetnameToVal = makeStore('petname'); - /** @type {Store} */ + /** @type {Store} */ const petnameToVal = { ...rawPetnameToVal, init(key, val) { @@ -251,6 +256,7 @@ export const makeDehydrator = (initialUnnamedCount = 0) => { petnameToVal.delete(petname); valToPetname.delete(val); }; + /** @type {Mapping} */ const mapping = harden({ implode, explode, @@ -321,6 +327,11 @@ export const makeDehydrator = (initialUnnamedCount = 0) => { hydrate, dehydrate, edgeMapping, + /** + * @template T + * @param {string} kind + * @returns {Mapping} + */ makeMapping: kind => { const mapping = makeMapping(kind); searchOrder.push(kind); diff --git a/packages/dapp-svelte-wallet/api/src/lib-wallet.js b/packages/dapp-svelte-wallet/api/src/lib-wallet.js index 9667773bd0a..e966a785f2a 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/src/lib-wallet.js @@ -1,3 +1,5 @@ +// @ts-check + import { assert, details, q } from '@agoric/assert'; import makeStore from '@agoric/store'; import makeWeakStore from '@agoric/weak-store'; @@ -14,9 +16,30 @@ import { makePromiseKit } from '@agoric/promise-kit'; import makeObservablePurse from './observable'; import { makeDehydrator } from './lib-dehydrate'; +import '@agoric/zoe/exported'; +import './types'; + // does nothing const noActionStateChangeHandler = _newState => {}; +const cmp = (a, b) => { + if (a > b) { + return 1; + } + if (a === b) { + return 0; + } + return -1; +}; + +/** + * @typedef {Object} MakeWalletParams + * @property {ZoeService} zoe + * @property {Board} board + * @property {(state: any) => void} [pursesStateChangeHandler=noActionStateChangeHandler] + * @property {(state: any) => void} [inboxStateChangeHandler=noActionStateChangeHandler] + * @param {MakeWalletParams} param0 + */ export async function makeWallet({ zoe, board, @@ -26,18 +49,28 @@ export async function makeWallet({ // Create the petname maps so we can dehydrate information sent to // the frontend. const { makeMapping, dehydrate, edgeMapping } = makeDehydrator(); + /** @type {Mapping} */ const purseMapping = makeMapping('purse'); + /** @type {Mapping} */ const brandMapping = makeMapping('brand'); + /** @type {Mapping} */ const contactMapping = makeMapping('contact'); + /** @type {Mapping} */ const instanceMapping = makeMapping('instance'); + /** @type {Mapping} */ const installationMapping = makeMapping('installation'); - // Brand Table - // Columns: key:brand | issuer | amountMath + /** + * Brand Table + */ const makeBrandTable = () => { - const validateSomewhat = makeValidateProperties( - harden(['brand', 'issuer', 'issuerBoardId', 'amountMath']), - ); + /** @type {(record: any) => record is BrandRecord} */ + const validateSomewhat = makeValidateProperties([ + 'brand', + 'issuer', + 'issuerBoardId', + 'amountMath', + ]); const issuersInProgress = makeStore('issuer'); const issuerToBrand = makeWeakStore('issuer'); @@ -93,8 +126,11 @@ export async function makeWallet({ }; const brandTable = makeBrandTable(); + /** @type {WeakStore} */ const purseToBrand = makeWeakStore('purse'); + /** @type {Store} */ const brandToDepositFacetId = makeStore('brand'); + /** @type {Store} */ const brandToAutoDepositPurse = makeStore('brand'); // Offers that the wallet knows about (the inbox). @@ -108,18 +144,24 @@ export async function makeWallet({ const idToOutcome = new Map(); // Client-side representation of the purses inbox; + /** @type {Map} */ const pursesState = new Map(); + + /** @type {Map} */ const pursesFullState = new Map(); const inboxState = new Map(); - // The default Zoe invite purse is used to make an offer. + /** + * The default Zoe invite purse is used to make an offer. + * @type {Purse} + */ let zoeInvitePurse; function getSortedValues(map) { const entries = [...map.entries()]; // Sort for determinism. const values = entries - .sort(([id1], [id2]) => id1 > id2) + .sort(([id1], [id2]) => cmp(id1, id2)) .map(([_id, value]) => value); return JSON.stringify(values); @@ -140,10 +182,15 @@ export async function makeWallet({ // @qclass is lost. const { unserialize: fillInSlots } = makeMarshal(noOp, identityFn); - const { notifier: pursesNotifier, updater: pursesUpdater } = makeNotifierKit( - [], - ); + const { + notifier: pursesNotifier, + updater: pursesUpdater, + } = /** @type {NotifierRecord} */ (makeNotifierKit([])); + /** + * @param {Petname} pursePetname + * @param {Purse} purse + */ async function updatePursesState(pursePetname, purse) { const purseKey = purseMapping.implode(pursePetname); for (const key of pursesState.keys()) { @@ -166,6 +213,9 @@ export async function makeWallet({ // We have a depositId for the purse. depositBoardId = brandToDepositFacetId.get(brand); } + /** + * @type {PursesJSONState} + */ const jstate = { brandBoardId, ...(depositBoardId && { depositBoardId }), @@ -302,7 +352,9 @@ export async function makeWallet({ const { updater: issuersUpdater, notifier: issuersNotifier, - } = makeNotifierKit([]); + } = /** @type {NotifierRecord<[Petname, BrandRecord][]>} */ (makeNotifierKit( + [], + )); function updateAllIssuersState() { issuersUpdater.updateState( @@ -485,7 +537,7 @@ export async function makeWallet({ const { updater: contactsUpdater, notifier: contactsNotifier, - } = makeNotifierKit([]); + } = /** @type {NotifierRecord<[Petname, Contact][]>} */ (makeNotifierKit([])); const addContact = async (petname, actions) => { const already = await E(board).has(actions); @@ -591,7 +643,7 @@ export async function makeWallet({ origin === null || (offer.requestContext && offer.requestContext.dappOrigin === origin), ) - .sort(([id1], [id2]) => id1 > id2) + .sort(([id1], [id2]) => cmp(id1, id2)) .map(([_id, offer]) => harden(offer)); } @@ -654,10 +706,12 @@ export async function makeWallet({ return { proposal, inviteP, purseKeywordRecord }; }; + /** @type {Store} */ const dappOrigins = makeStore('dappOrigin'); - const { notifier: dappsNotifier, updater: dappsUpdater } = makeNotifierKit( - [], - ); + const { + notifier: dappsNotifier, + updater: dappsUpdater, + } = /** @type {NotifierRecord} */ (makeNotifierKit([])); function updateDapp(dappRecord) { harden(dappRecord); @@ -673,62 +727,63 @@ export async function makeWallet({ let resolve; let reject; let approvalP; + dappRecord = { suggestedPetname, petname: suggestedPetname, origin, - }; - - dappRecord.actions = { - setPetname(petname) { - if (dappRecord.petname === petname) { + approvalP, + actions: { + setPetname(petname) { + if (dappRecord.petname === petname) { + return dappRecord.actions; + } + if (edgeMapping.valToPetname.has(origin)) { + edgeMapping.renamePetname(petname, origin); + } else { + edgeMapping.suggestPetname(petname, origin); + } + dappRecord = { + ...dappRecord, + petname, + }; + updateDapp(dappRecord); + updateAllState(); return dappRecord.actions; - } - if (edgeMapping.valToPetname.has(origin)) { - edgeMapping.renamePetname(petname, origin); - } else { - edgeMapping.suggestPetname(petname, origin); - } - dappRecord = { - ...dappRecord, - petname, - }; - updateDapp(dappRecord); - updateAllState(); - return dappRecord.actions; - }, - enable() { - // Enable the dapp with the attached petname. - dappRecord = { - ...dappRecord, - enable: true, - }; - edgeMapping.suggestPetname(dappRecord.petname, origin); - updateDapp(dappRecord); + }, + enable() { + // Enable the dapp with the attached petname. + dappRecord = { + ...dappRecord, + enable: true, + }; + edgeMapping.suggestPetname(dappRecord.petname, origin); + updateDapp(dappRecord); - // Allow the pending requests to pass. - resolve(); - return dappRecord.actions; - }, - disable(reason = undefined) { - // Reject the pending dapp requests. - if (reject) { - reject(reason); - } - // Create a new, suspended-approval record. - ({ resolve, reject, promise: approvalP } = makePromiseKit()); - dappRecord = { - ...dappRecord, - enable: false, - approvalP, - }; - updateDapp(dappRecord); - return dappRecord.actions; + // Allow the pending requests to pass. + resolve(); + return dappRecord.actions; + }, + disable(reason = undefined) { + // Reject the pending dapp requests. + if (reject) { + reject(reason); + } + // Create a new, suspended-approval record. + ({ resolve, reject, promise: approvalP } = makePromiseKit()); + dappRecord = { + ...dappRecord, + enable: false, + approvalP, + }; + updateDapp(dappRecord); + return dappRecord.actions; + }, }, }; // Prepare the table entry to be updated. - dappOrigins.init(origin, {}); + dappOrigins.init(origin, dappRecord); // Initially disable it. dappRecord.actions.disable(); @@ -903,11 +958,15 @@ export async function makeWallet({ }); } + /** @type {Store} */ const payments = makeStore('payment'); const { updater: paymentsUpdater, notifier: paymentsNotifier, - } = makeNotifierKit([]); + } = /** @type {NotifierRecord} */ (makeNotifierKit([])); + /** + * @param {PaymentRecord} param0 + */ const updatePaymentRecord = ({ actions, ...preDisplay }) => { const displayPayment = fillInSlots(dehydrate(harden(preDisplay))); const paymentRecord = { ...preDisplay, actions, displayPayment }; @@ -915,100 +974,100 @@ export async function makeWallet({ paymentsUpdater.updateState([...payments.values()]); }; + /** + * @param {Payment} payment + * @param {Purse | Petname=} depositTo + */ const addPayment = async (payment, depositTo = undefined) => { - /** - * @typedef {Object} PaymentRecord - * @property {Issuer} issuer - * @property {Payment} payment - * @property {Brand} brand - * @property {'pending'|'deposited'} status - * @property {typeof actions} actions - */ - // We don't even create the record until we get an alleged brand. const brand = await E(payment).getAllegedBrand(); - /** @type {Partial} */ - let paymentRecord = { + /** @type {PaymentRecord} */ + let paymentRecord; + + const depositedPK = makePromiseKit(); + paymentRecord = { payment, brand, issuer: undefined, status: undefined, - }; - - const depositedPK = makePromiseKit(); - const actions = { - async deposit(purseOrPetname = undefined) { - let purse; - if (purseOrPetname === undefined) { - if (!brandToAutoDepositPurse.has(brand)) { - // No automatic purse right now. - return depositedPK.promise; + actions: { + async deposit(purseOrPetname = undefined) { + /** @type {Purse} */ + let purse; + if (purseOrPetname === undefined) { + if (!brandToAutoDepositPurse.has(brand)) { + // No automatic purse right now. + return depositedPK.promise; + } + // Plop into the current autodeposit purse. + purse = brandToAutoDepositPurse.get(brand); + } else if ( + Array.isArray(purseOrPetname) || + typeof purseOrPetname === 'string' + ) { + purse = purseMapping.petnameToVal.get(purseOrPetname); + } else { + purse = purseOrPetname; } - // Plop into the current autodeposit purse. - purse = brandToAutoDepositPurse.get(brand); - } else if (typeof purseOrPetname === 'string') { - purse = purseMapping.petnameToVal.get(purseOrPetname); - } else { - purse = purseOrPetname; - } - paymentRecord = { - ...paymentRecord, - status: 'pending', - }; - updatePaymentRecord(paymentRecord); - // Now try depositing. - const depositedAmount = await E(purse).deposit(payment); - paymentRecord = { - ...paymentRecord, - status: 'deposited', - depositedAmount, - }; - updatePaymentRecord(paymentRecord); - depositedPK.resolve(depositedAmount); - return depositedPK.promise; - }, - async refresh() { - if (!brandTable.has(brand)) { - return false; - } - - const { issuer } = paymentRecord; - if (!issuer) { paymentRecord = { ...paymentRecord, - ...brandTable.get(brand), + status: 'pending', }; updatePaymentRecord(paymentRecord); - } + // Now try depositing. + const depositedAmount = await E(purse).deposit(payment); + paymentRecord = { + ...paymentRecord, + status: 'deposited', + depositedAmount, + }; + updatePaymentRecord(paymentRecord); + depositedPK.resolve(depositedAmount); + return depositedPK.promise; + }, + async refresh() { + if (!brandTable.has(brand)) { + return false; + } - return actions.getAmountOf(); - }, - async getAmountOf() { - const { issuer } = paymentRecord; + const { issuer } = paymentRecord; + if (!issuer) { + paymentRecord = { + ...paymentRecord, + ...brandTable.get(brand), + }; + updatePaymentRecord(paymentRecord); + } - // Fetch the current amount of the payment. - const lastAmount = await E(issuer).getAmountOf(payment); + return paymentRecord.actions.getAmountOf(); + }, + async getAmountOf() { + const { issuer } = paymentRecord; + assert(issuer); - paymentRecord = { - ...paymentRecord, - lastAmount, - }; - updatePaymentRecord(paymentRecord); - return true; + // Fetch the current amount of the payment. + const lastAmount = await E(issuer).getAmountOf(payment); + + paymentRecord = { + ...paymentRecord, + lastAmount, + }; + updatePaymentRecord(paymentRecord); + return true; + }, }, }; - paymentRecord.actions = actions; payments.init(payment, harden(paymentRecord)); - const refreshed = await actions.refresh(); + const refreshed = await paymentRecord.actions.refresh(); if (!refreshed) { // Only update if the refresh didn't. updatePaymentRecord(paymentRecord); } // Try an automatic deposit. - return actions.deposit(depositTo); + return paymentRecord.actions.deposit(depositTo); }; // Allow people to send us payments. diff --git a/packages/dapp-svelte-wallet/api/src/observable.js b/packages/dapp-svelte-wallet/api/src/observable.js index bf524f71e2f..ecd750accb7 100644 --- a/packages/dapp-svelte-wallet/api/src/observable.js +++ b/packages/dapp-svelte-wallet/api/src/observable.js @@ -1,3 +1,9 @@ +/** + * @param {import("@agoric/eventual-send").EProxy} E + * @param {Purse} purse + * @param {() => void} onFulfilled + * @return {Purse} + */ export default function makeObservablePurse(E, purse, onFulfilled) { return { makeDepositFacet() { diff --git a/packages/dapp-svelte-wallet/api/src/types.js b/packages/dapp-svelte-wallet/api/src/types.js new file mode 100644 index 00000000000..d7003f5c836 --- /dev/null +++ b/packages/dapp-svelte-wallet/api/src/types.js @@ -0,0 +1,99 @@ +/** + * @template T + * @typedef {import('@agoric/promise-kit').ERef} ERef + */ + +/** + * @typedef {import('@agoric/cosmic-swingset/lib/ag-solo/vats/lib-board').Board} Board + */ + +/** + * @typedef {string | string[]} Petname + */ + +/** + * @typedef {Object} PursesJSONState + * @property {string} brandBoardId + * @property {string=} depositBoardId + * @property {Petname} brandPetname + * @property {Petname} pursePetname + * @property {any} value + * @property {any} currentAmountSlots + * @property {any} currentAmount + */ +/** + * @typedef {Object} PursesAddedState + * @property {Purse} purse + * @property {Brand} brand + * @property {PurseActions} actions + */ + +/** + * @typedef {PursesJSONState & PursesAddedState} PursesFullState + */ + +/** + * @typedef {Object} PurseActions + * @property {(receiverP: ERef<{ receive: (payment: Payment) => void }>, valueToSend: Value) => Promise} send + * @property {(payment: Payment) => Promise} receive + * @property {(payment: Payment, amount?: Amount) => Promise} deposit + */ + +/** + * @typedef {Object} BrandRecord + * @property {Brand} brand + * @property {Issuer} issuer + * @property {string} issuerBoardId + * @property {AmountMath} amountMath + */ + +/** + * @typedef {Object} Contact + * @property {string=} depositBoardId + */ + +/** + * @typedef {Object} DappRecord + * @property {Promise=} approvalP + * @property {Petname} suggestedPetname + * @property {Petname} petname + * @property {string} origin + * @property {DappActions} actions + * + * @typedef {Object} DappActions + * @property {(petname: Petname) => DappActions} setPetname + * @property {() => DappActions} enable + * @property {(reason: any) => DappActions} disable + */ + +/** + * @template T + * @typedef {Object} Mapping + * @property {(petname: Petname) => string} implode + * @property {(str: string) => Petname} explode + * @property {WeakStore} valToPetname + * @property {WeakStore} valToPaths + * @property {Store} petnameToVal + * @property {(petname: Petname, val: T) => void} addPetname + * @property {(path: string[], val: T) => void} addPath + * @property {(petname: Petname, val: T) => void} renamePetname + * @property {(petname: Petname) => void} deletePetname + * @property {(petname: Petname, val: T) => void} suggestPetname + * @property {string} kind + */ + +/** + * @typedef {Object} PaymentRecord + * @property {Issuer=} issuer + * @property {Payment} payment + * @property {Brand} brand + * @property {'pending'|'deposited'|undefined} status + * @property {PaymentActions} actions + * @property {Amount=} lastAmount + * @property {Amount=} depositedAmount + * + * @typedef {Object} PaymentActions + * @property {(purseOrPetname?: Purse | Petname) => Promise} deposit + * @property {() => Promise} refresh + * @property {() => Promise} getAmountOf + */ diff --git a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js index 33041466244..92b8f585790 100644 --- a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js @@ -1,3 +1,4 @@ +// @ts-check import '@agoric/install-ses'; // calls lockdown() // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; @@ -13,6 +14,8 @@ import { E } from '@agoric/eventual-send'; import { makeBoard } from '@agoric/cosmic-swingset/lib/ag-solo/vats/lib-board'; import { makeWallet } from '../src/lib-wallet'; +import '../src/types'; + const setupTest = async () => { const pursesStateChangeLog = []; const inboxStateChangeLog = []; @@ -787,8 +790,10 @@ test('lib-wallet addOffer for autoswap swap', async t => { autoswapInstanceHandle, ); + /** @type {Issuer} */ const liquidityIssuer = await E(publicAPI).getLiquidityIssuer(); + /** @param {Issuer} issuer */ const getLocalAmountMath = issuer => Promise.all([ E(issuer).getBrand(), diff --git a/packages/zoe/src/objArrayConversion.js b/packages/zoe/src/objArrayConversion.js index dcb7762e275..c86c9262b63 100644 --- a/packages/zoe/src/objArrayConversion.js +++ b/packages/zoe/src/objArrayConversion.js @@ -1,6 +1,12 @@ import { assert, details, q } from '@agoric/assert'; -/** @type {(...args: T) => T} */ +/** + * @typedef {bigint|boolean|null|number|string|symbol|undefined} Primitive + */ + +/** + * @type {(...args: T) => T} + */ export const tuple = (...args) => args; /** From 0b761fec195b5487c1020718f368156bfe4719b1 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Sun, 9 Aug 2020 16:44:30 -0700 Subject: [PATCH 19/31] refactor: post review cleanups; mostly type info improved type annotations rename makeEmptySeat to makeOfferlessSeat don't return the zcfSeatAdmin to the contract --- .../zoe/src/contractFacet/contractFacet.js | 6 +-- packages/zoe/src/internal-types.js | 44 +++++++++++++++---- packages/zoe/src/types.js | 9 +++- packages/zoe/src/zoeService/zoe.js | 5 ++- packages/zoe/src/zoeService/zoeSeat.js | 5 ++- 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index a6d8b64a2c8..45f424de7a5 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -258,7 +258,7 @@ export function buildRootObject() { issuerTable.getIssuerRecordByIssuer(issuer).brand, getAmountMath, makeZCFMint, - addEmptySeat: () => { + makeEmptySeatKit: () => { const initialAllocation = harden({}); const proposal = harden({ want: {} }); const { notifier, updater } = makeNotifierKit(); @@ -266,7 +266,7 @@ export function buildRootObject() { const userSeatPromiseKit = makePromiseKit(); E(zoeInstanceAdmin) - .makeEmptySeat(initialAllocation, proposal) + .makeOfferlessSeat(initialAllocation, proposal) .then(({ zoeSeatAdmin, notifier: zoeNotifier, userSeat }) => { updateFromNotifier(updater, zoeNotifier); zoeSeatAdminPromiseKit.resolve(zoeSeatAdmin); @@ -285,7 +285,7 @@ export function buildRootObject() { getAmountMath, ); seatToZCFSeatAdmin.init(zcfSeat, zcfSeatAdmin); - return { zcfSeat, zcfSeatAdmin, userSeat: userSeatPromiseKit.promise }; + return { zcfSeat, userSeat: userSeatPromiseKit.promise }; }, }; harden(zcf); diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index 56eb61473f3..b39b44a754d 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -50,11 +50,6 @@ * - replace the currentAllocation with this allocation */ -/** - * @typedef {Object} ZCFSeatAdmin - * @property {(seatStaging: SeatStaging) => void} commit - */ - /** * @typedef {Object} SeatData * @property {ProposalRecord} proposal @@ -63,16 +58,40 @@ */ /** + * @typedef {{UserSeat, ZoeSeatAdmin, Notifier}} ZoeSeatAdminKit + * + * @typedef MakeZoeSeatAdminKit + * Make the Zoe seat admin, user seat and a notifier + * @param {InstanceAdmin} instanceAdmin - pass-by-copy data to use to make the seat + * @param {Allocation} initialAllocation + * @param {Proposal} proposal + * @param {Proposal} brandToPurse + * @param {{ offerResult: Promise=, exitObj: Promise=}=} promises + * @returns {ZoeSeatAdminKit} + * + * @typedef ZoeSeatAdmin + * @property {Allocation => void} replaceAllocation + * @property {() => void} exit + * @property {() => Allocation} getCurrentAllocation + */ + +/** + * @typedef MakeZcfSeatAdminKit * Make the ZCF seat and seat admin - * @callback MakeZcfSeatAdmin * @param {WeakSet} allSeatStagings - a set of valid * seatStagings where allocations have been checked for offerSafety * @param {ZoeSeatAdmin} zoeSeatAdmin * - a presence from Zoe such that ZCF can tell Zoe * about seat events * @param {SeatData} seatData - pass-by-copy data to use to make the seat - * @param {(brand: Brand) => AmountMath} getAmountMath - * @returns {{zcfSeatAdmin: ZCFSeatAdmin, zcfSeat: ZCFSeat}} + * @param {GetAmountMath} getAmountMath + * @returns {ZcfSeatAdminKit} + */ + +/** + * @typedef {{zcfSeatAdmin: ZCFSeatAdmin, zcfSeat: ZCFSeat}} ZcfSeatAdminKit + * @typedef {Object} ZCFSeatAdmin + * @property {(seatStaging: SeatStaging) => void} commit */ /** @@ -112,8 +131,8 @@ * ) => Payment} makeInvitation * @property {() => void} shutdown * @property {(issuerP: ERef, keyword: Keyword) => void} saveIssuer - * * @property {MakeZoeMint} makeZoeMint + * @property {MakeOfferlessSeat} makeOfferlessSeat */ /** @@ -123,6 +142,13 @@ * @returns {ZoeMint} */ +/** + * @callback MakeOfferlessSeat + * @param {Allocation} initialAllocation + * @param {Proposal} proposal + * @returns {ZoeSeatAdminKit} + */ + /** * @typedef {Object} ZoeMint * @property {() => IssuerRecord} getIssuerRecord diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 21ce35f24a1..9bf9fbc9a9b 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -74,12 +74,14 @@ /** * @typedef {Object} UserSeat - * @property {() => Promise} getCurrentAllocation + * @property {() => Allocation} getCurrentAllocation * @property {() => Promise} getProposal * @property {() => Promise} getPayouts * @property {(keyword: Keyword) => Promise} getPayout * @property {() => Promise} getOfferResult * @property {() => void=} exit + * + * @typedef {any} OfferResult */ /** @@ -193,6 +195,7 @@ * @property {(issuer: Issuer) => Brand} getBrandForIssuer * @property {GetAmountMath} getAmountMath * @property {MakeZCFMint} makeZCFMint + * @property {ZcfSeatKit} makeEmptySeatKit */ /** @@ -370,6 +373,10 @@ * @property {() => Allocation} getStagedAllocation */ +/** + * @typedef {{ zcfSeat: ZCFSeat, userSeat: UserSeat}} ZcfSeatKit + */ + /** * @callback OfferHandler * @param {ZCFSeat} seat diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index 989518c82ed..35f1d7aa9ac 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -230,7 +230,8 @@ function makeZoe(vatAdminSvc) { brandToPurse.init(brand, E(issuer).makeEmptyPurse()); } }), - makeEmptySeat: (initialAllocation, proposal) => { + // A Seat requested by the contract without an offer + makeOfferlessSeat: (initialAllocation, proposal) => { const { userSeat, notifier, zoeSeatAdmin } = makeZoeSeatAdminKit( initialAllocation, instanceAdmin, @@ -317,7 +318,7 @@ function makeZoe(vatAdminSvc) { const exitObjPromiseKit = makePromiseKit(); const instanceAdmin = instanceToInstanceAdmin.get(instance); - /** @type {ZoeSeatAdmin} */ + /** @type {ZoeSeatAdminKit} */ const { userSeat, notifier, zoeSeatAdmin } = makeZoeSeatAdminKit( initialAllocation, instanceAdmin, diff --git a/packages/zoe/src/zoeService/zoeSeat.js b/packages/zoe/src/zoeService/zoeSeat.js index a1521f3b5d3..e93f9e2a730 100644 --- a/packages/zoe/src/zoeService/zoeSeat.js +++ b/packages/zoe/src/zoeService/zoeSeat.js @@ -10,12 +10,14 @@ export const makeZoeSeatAdminKit = ( brandToPurse, promises = {}, ) => { - const payoutPromiseKit = promises.payout || makePromiseKit(); + const payoutPromiseKit = makePromiseKit(); const offerResultPromiseKit = promises.offerResult || makePromiseKit(); const exitObjPromiseKit = promises.exitObj || makePromiseKit(); const { notifier, updater } = makeNotifierKit(); let currentAllocation = initialAllocation; + + /** @type ZoeSeatAdmin */ const zoeSeatAdmin = harden({ replaceAllocation: replacementAllocation => { assert( @@ -48,6 +50,7 @@ export const makeZoeSeatAdminKit = ( getCurrentAllocation: () => currentAllocation, }); + /** @type UserSeat */ const userSeat = harden({ getCurrentAllocation: async () => zoeSeatAdmin.getCurrentAllocation(), getProposal: async () => proposal, From 8de6a7bdb688e20122e231dc8a02e7d66d1af7dc Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sun, 9 Aug 2020 21:33:36 -0600 Subject: [PATCH 20/31] fix: make Zoe typesafe again --- .../zoe/src/contractFacet/contractFacet.js | 8 +++-- packages/zoe/src/contractFacet/seat.js | 4 +-- packages/zoe/src/internal-types.js | 34 ++++++++----------- packages/zoe/src/issuerTable.js | 2 +- packages/zoe/src/objArrayConversion.js | 31 +++++++++++------ packages/zoe/src/table.js | 22 ++++++++---- packages/zoe/src/types.js | 4 +-- packages/zoe/src/zoeService/zoe.js | 3 +- packages/zoe/src/zoeService/zoeSeat.js | 12 +++++-- 9 files changed, 72 insertions(+), 48 deletions(-) diff --git a/packages/zoe/src/contractFacet/contractFacet.js b/packages/zoe/src/contractFacet/contractFacet.js index 45f424de7a5..55a1f0e74ae 100644 --- a/packages/zoe/src/contractFacet/contractFacet.js +++ b/packages/zoe/src/contractFacet/contractFacet.js @@ -16,7 +16,11 @@ import { makeNotifierKit, updateFromNotifier } from '@agoric/notifier'; import { makePromiseKit } from '@agoric/promise-kit'; import { areRightsConserved } from './rightsConservation'; import { makeIssuerTable } from '../issuerTable'; -import { assertKeywordName, getKeywords } from '../zoeService/cleanProposal'; +import { + assertKeywordName, + getKeywords, + cleanProposal, +} from '../zoeService/cleanProposal'; import { evalContractBundle } from './evalContractCode'; import { makeZcfSeatAdminKit } from './seat'; import { makeExitObj } from './exit'; @@ -260,7 +264,7 @@ export function buildRootObject() { makeZCFMint, makeEmptySeatKit: () => { const initialAllocation = harden({}); - const proposal = harden({ want: {} }); + const proposal = cleanProposal(getAmountMath, harden({})); const { notifier, updater } = makeNotifierKit(); const zoeSeatAdminPromiseKit = makePromiseKit(); const userSeatPromiseKit = makePromiseKit(); diff --git a/packages/zoe/src/contractFacet/seat.js b/packages/zoe/src/contractFacet/seat.js index 292eb718f6c..7416f7fec5f 100644 --- a/packages/zoe/src/contractFacet/seat.js +++ b/packages/zoe/src/contractFacet/seat.js @@ -8,7 +8,7 @@ import { isOfferSafe } from './offerSafety'; import '../../exported'; import '../internal-types'; -/** @type MakeZcfSeatAdminKit */ +/** @type {MakeZcfSeatAdminKit} */ export const makeZcfSeatAdminKit = ( allSeatStagings, zoeSeatAdmin, @@ -22,7 +22,7 @@ export const makeZcfSeatAdminKit = ( let currentAllocation = harden(seatData.initialAllocation); let exited = false; // seat is "active" - /** @type ZCFSeatAdmin */ + /** @type {ZCFSeatAdmin} */ const zcfSeatAdmin = harden({ commit: seatStaging => { assert( diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index b39b44a754d..8db3acd4e74 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -36,20 +36,13 @@ * @template T * @typedef {Object} Table * @property {(record: any) => record is T} validate - * @property {(record: Omit, handle: H = harden({})) => H} create - * @property {(handle: any) => T & {handle: {}}} get + * @property {(record: Omit, handle: H) => H} create + * @property {(handle: any) => T} get * @property {(handle: any) => boolean} has * @property {(handle: any) => void} delete * @property {(handle: H, partialRecord: Partial) => H} update */ -/** - * @typedef {Object} ZoeSeatAdmin - * @property {() => void} exit - exit seat - * @property {(replacementAllocation: Allocation) => void} replaceAllocation - * - replace the currentAllocation with this allocation - */ - /** * @typedef {Object} SeatData * @property {ProposalRecord} proposal @@ -58,29 +51,32 @@ */ /** - * @typedef {{UserSeat, ZoeSeatAdmin, Notifier}} ZoeSeatAdminKit + * @typedef {Object} ZoeSeatAdminKit + * @property {UserSeat} userSeat + * @property {ZoeSeatAdmin} zoeSeatAdmin + * @property {Notifier} notifier * - * @typedef MakeZoeSeatAdminKit + * @callback MakeZoeSeatAdminKit * Make the Zoe seat admin, user seat and a notifier - * @param {InstanceAdmin} instanceAdmin - pass-by-copy data to use to make the seat * @param {Allocation} initialAllocation - * @param {Proposal} proposal - * @param {Proposal} brandToPurse - * @param {{ offerResult: Promise=, exitObj: Promise=}=} promises + * @param {InstanceAdmin} instanceAdmin - pass-by-copy data to use to make the seat + * @param {ProposalRecord} proposal + * @param {WeakStore>} brandToPurse + * @param {{ offerResult?: PromiseRecord, exitObj?: PromiseRecord}} [promises={}] * @returns {ZoeSeatAdminKit} * - * @typedef ZoeSeatAdmin - * @property {Allocation => void} replaceAllocation + * @typedef {Object} ZoeSeatAdmin + * @property {(allocation: Allocation) => void} replaceAllocation * @property {() => void} exit * @property {() => Allocation} getCurrentAllocation */ /** - * @typedef MakeZcfSeatAdminKit + * @callback MakeZcfSeatAdminKit * Make the ZCF seat and seat admin * @param {WeakSet} allSeatStagings - a set of valid * seatStagings where allocations have been checked for offerSafety - * @param {ZoeSeatAdmin} zoeSeatAdmin + * @param {ERef} zoeSeatAdmin * - a presence from Zoe such that ZCF can tell Zoe * about seat events * @param {SeatData} seatData - pass-by-copy data to use to make the seat diff --git a/packages/zoe/src/issuerTable.js b/packages/zoe/src/issuerTable.js index 54c83ef7de3..275da98fe6a 100644 --- a/packages/zoe/src/issuerTable.js +++ b/packages/zoe/src/issuerTable.js @@ -38,7 +38,7 @@ const makeIssuerTable = () => { /** @param {Table} table */ const makeCustomMethods = table => { - /** @type {WeakStore>} */ + /** @type {WeakStore>} */ const issuersInProgress = makeWeakStore('issuer'); /** @type {WeakStore} */ diff --git a/packages/zoe/src/objArrayConversion.js b/packages/zoe/src/objArrayConversion.js index c86c9262b63..e8d6a004ec2 100644 --- a/packages/zoe/src/objArrayConversion.js +++ b/packages/zoe/src/objArrayConversion.js @@ -1,3 +1,5 @@ +// @ts-check + import { assert, details, q } from '@agoric/assert'; /** @@ -11,7 +13,7 @@ export const tuple = (...args) => args; /** * @template T - * @template U + * @template {string | number} U * @param {T[]} array * @param {U[]} keys */ @@ -20,8 +22,9 @@ export const arrayToObj = (array, keys) => { array.length === keys.length, details`array and keys must be of equal length`, ); - /** @type {{[Keyword: U]: T}} */ - const obj = {}; + const obj = + /** @type {Record} */ + ({}); keys.forEach((key, i) => (obj[key] = array[i])); return obj; }; @@ -57,6 +60,7 @@ export const assertSubset = (whole, part) => { * @returns {Pick} */ export const filterObj = (obj, subsetKeys) => { + /** @type {Partial>} */ const newObj = {}; subsetKeys.forEach(key => { assert( @@ -65,7 +69,9 @@ export const filterObj = (obj, subsetKeys) => { ); newObj[key] = obj[key]; }); - return newObj; + + const picked = /** @type {Pick} */ (newObj); + return picked; }; /** @@ -76,6 +82,7 @@ export const filterObj = (obj, subsetKeys) => { * @returns {Allocation} * */ export const filterFillAmounts = (allocation, amountMathKeywordRecord) => { + /** @type {Allocation} */ const filledAllocation = {}; const subsetKeywords = Object.getOwnPropertyNames(amountMathKeywordRecord); subsetKeywords.forEach(keyword => { @@ -89,10 +96,14 @@ export const filterFillAmounts = (allocation, amountMathKeywordRecord) => { }; /** - * @template P, T, U - * @param {{[P]: T}} original - * @param {(pair: [P, T]) => [P, U]} mapPairFn - * @returns {{[P]: U}} + * @template T, U + * @template {keyof T} K + * @param {Record} original + * @param {(pair: [K, T]) => [K, U]} mapPairFn + * @returns {Record} */ -export const objectMap = (original, mapPairFn) => - Object.fromEntries(Object.entries(original).map(mapPairFn)); +export const objectMap = (original, mapPairFn) => { + const ents = /** @type {[K, T][]} */ (Object.entries(original)); + const mapEnts = ents.map(ent => mapPairFn(ent)); + return /** @type {Record} */ (Object.fromEntries(mapEnts)); +}; diff --git a/packages/zoe/src/table.js b/packages/zoe/src/table.js index 53accde2a4e..c86a49ed39b 100644 --- a/packages/zoe/src/table.js +++ b/packages/zoe/src/table.js @@ -1,3 +1,5 @@ +// @ts-check + import makeWeakStore from '@agoric/weak-store'; import { assert, details } from '@agoric/assert'; @@ -7,6 +9,7 @@ import './internal-types'; /** * This definition is used to ensure the proper typing of * makeCustomMethodsFn. + * @returns {Object} */ const DEFAULT_CUSTOM_METHODS = _ => ({}); @@ -15,6 +18,7 @@ const DEFAULT_CUSTOM_METHODS = _ => ({}); * * @template {string} H * @param {H} handleType the string literal type of the handle + * @returns {Handle} */ export const makeHandle = handleType => { // This assert ensures that handleType is referenced. @@ -24,7 +28,7 @@ export const makeHandle = handleType => { }; /** - * @template {{}} T + * @template {Object} T * @template U * @param {(record: any) => record is U} validateFn * @param {string} [handleDebugName='Handle'] the debug name for the table key @@ -45,14 +49,16 @@ export const makeTable = ( /** @type {Table} */ const table = { validate: validateFn, - create: (record, handle = harden({})) => { + create: (record, handle) => { + const hnd = + /** @type {NonNullable} */ (handle || harden({})); record = harden({ ...record, - handle, // reliably add the handle to the record + handle: hnd, // reliably add the handle to the record }); table.validate(record); - handleToRecord.init(handle, record); - return handle; + handleToRecord.init(hnd, /** @type {U} */ (record)); + return hnd; }, get: handleToRecord.get, has: handleToRecord.has, @@ -78,7 +84,7 @@ export const makeTable = ( /** * @template T - * @template {(keyof T)[]} U + * @template {(keyof T | 'handle')[]} U * @param {U} expectedProperties * @returns {Validator>} */ @@ -87,7 +93,8 @@ export const makeValidateProperties = expectedProperties => { const checkSet = new Set(expectedProperties); checkSet.add('handle'); const checkProperties = harden([...checkSet.values()].sort()); - return obj => { + /** @type {Validator>} */ + const validator = obj => { const actualProperties = Object.getOwnPropertyNames(obj); actualProperties.sort(); assert( @@ -103,4 +110,5 @@ export const makeValidateProperties = expectedProperties => { } return true; }; + return validator; }; diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 9bf9fbc9a9b..1b45a995dba 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -195,7 +195,7 @@ * @property {(issuer: Issuer) => Brand} getBrandForIssuer * @property {GetAmountMath} getAmountMath * @property {MakeZCFMint} makeZCFMint - * @property {ZcfSeatKit} makeEmptySeatKit + * @property {() => ZcfSeatKit} makeEmptySeatKit */ /** @@ -374,7 +374,7 @@ */ /** - * @typedef {{ zcfSeat: ZCFSeat, userSeat: UserSeat}} ZcfSeatKit + * @typedef {{ zcfSeat: ZCFSeat, userSeat: ERef}} ZcfSeatKit */ /** diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index 35f1d7aa9ac..9a752c3b089 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -235,7 +235,7 @@ function makeZoe(vatAdminSvc) { const { userSeat, notifier, zoeSeatAdmin } = makeZoeSeatAdminKit( initialAllocation, instanceAdmin, - proposal, + cleanProposal(getAmountMath, proposal), brandToPurse, ); zoeSeatAdmins.add(zoeSeatAdmin); @@ -318,7 +318,6 @@ function makeZoe(vatAdminSvc) { const exitObjPromiseKit = makePromiseKit(); const instanceAdmin = instanceToInstanceAdmin.get(instance); - /** @type {ZoeSeatAdminKit} */ const { userSeat, notifier, zoeSeatAdmin } = makeZoeSeatAdminKit( initialAllocation, instanceAdmin, diff --git a/packages/zoe/src/zoeService/zoeSeat.js b/packages/zoe/src/zoeService/zoeSeat.js index e93f9e2a730..2e1289bc9fb 100644 --- a/packages/zoe/src/zoeService/zoeSeat.js +++ b/packages/zoe/src/zoeService/zoeSeat.js @@ -1,8 +1,14 @@ +// @ts-check + import { makePromiseKit } from '@agoric/promise-kit'; import { makeNotifierKit } from '@agoric/notifier'; import { assert } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; +import '../types'; +import '../internal-types'; + +/** @type {MakeZoeSeatAdminKit} */ export const makeZoeSeatAdminKit = ( initialAllocation, instanceAdmin, @@ -17,7 +23,7 @@ export const makeZoeSeatAdminKit = ( let currentAllocation = initialAllocation; - /** @type ZoeSeatAdmin */ + /** @type {ZoeSeatAdmin} */ const zoeSeatAdmin = harden({ replaceAllocation: replacementAllocation => { assert( @@ -50,9 +56,9 @@ export const makeZoeSeatAdminKit = ( getCurrentAllocation: () => currentAllocation, }); - /** @type UserSeat */ + /** @type {UserSeat} */ const userSeat = harden({ - getCurrentAllocation: async () => zoeSeatAdmin.getCurrentAllocation(), + getCurrentAllocation: () => zoeSeatAdmin.getCurrentAllocation(), getProposal: async () => proposal, getPayouts: async () => payoutPromiseKit.promise, getPayout: async keyword => From 1016d356a4739d5559433db9b09f5520df9bbb3e Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Sun, 9 Aug 2020 14:45:06 -0700 Subject: [PATCH 21/31] refactor: bring autoswap up to date on the new Zoe spike branch Based on the synchronous seat work. The unit tests for autoswap pass. I haven't yet done the swingset tests. --- packages/zoe/src/contracts/autoswap.js | 423 +++++++++--------- .../test/unitTests/contracts/test-autoswap.js | 264 +++++------ .../test/unitTests/contracts/test-barter.js | 10 +- .../contracts/test-simpleExchange.js | 30 +- packages/zoe/test/zoeTestHelpers.js | 11 +- 5 files changed, 339 insertions(+), 399 deletions(-) diff --git a/packages/zoe/src/contracts/autoswap.js b/packages/zoe/src/contracts/autoswap.js index 76334755f15..7fd86137386 100644 --- a/packages/zoe/src/contracts/autoswap.js +++ b/packages/zoe/src/contracts/autoswap.js @@ -1,14 +1,13 @@ // @ts-check -import makeIssuerKit from '@agoric/ertp'; - // Eventually will be importable from '@agoric/zoe-contract-support' import { getInputPrice, calcLiqValueToMint, calcValueToRemove, - makeZoeHelpers, + assertProposalKeywords, } from '../contractSupport'; +import { trade, assertNatMathHelpers } from '../contractSupport/zoeHelpers'; import '../../exported'; @@ -23,245 +22,231 @@ import '../../exported'; * adding and removing liquidity, and for doing a swap. Other API operations * support monitoring the price and the size of the liquidity pool. * - * @param {ContractFacet} zcf + * @type {ContractStartFn} */ -const makeContract = zcf => { - // Create the liquidity mint and issuer. +const start = async (zcf, _terms) => { + // Create a local liquidity mint and issuer. + const liquidityMint = await zcf.makeZCFMint('Liquidity'); + // AWAIT //////////////////// + const { - mint: liquidityMint, issuer: liquidityIssuer, amountMath: liquidityAmountMath, - } = makeIssuerKit('liquidity'); - + } = liquidityMint.getIssuerRecord(); let liqTokenSupply = 0; - const { - makeEmptyOffer, - checkHook, - escrowAndAllocateTo, - assertNatMathHelpers, - trade, - } = makeZoeHelpers(zcf); - - return zcf.addNewIssuer(liquidityIssuer, 'Liquidity').then(() => { - const { brandKeywordRecord } = zcf.getInstanceRecord(); - Object.values(brandKeywordRecord).forEach(brand => - assertNatMathHelpers(brand), - ); - const getPoolKeyword = brandToMatch => { - const entries = Object.entries(brandKeywordRecord); - for (const [keyword, brand] of entries) { - if (brand === brandToMatch) { - return keyword; - } + const { brandKeywordRecord } = zcf.getInstanceRecord(); + Object.values(brandKeywordRecord).forEach(brand => + assertNatMathHelpers(zcf, brand), + ); + const getPoolKeyword = brandToMatch => { + const entries = Object.entries(brandKeywordRecord); + for (const [keyword, brand] of entries) { + if (brand === brandToMatch) { + return keyword; } - throw new Error('getPoolKeyword: brand not found'); - }; + } + throw new Error('getPoolKeyword: brand not found'); + }; + + const { zcfSeat: poolSeat } = zcf.addEmptySeat(); + const getPoolAmount = brand => { + const keyword = getPoolKeyword(brand); + return poolSeat.getCurrentAllocation()[keyword]; + }; + + /** @type {OfferHandler} */ + const swapHandler = swapSeat => { + const { + give: { In: amountIn }, + want: { Out: wantedAmountOut }, + } = swapSeat.getProposal(); + const outputValue = getInputPrice( + harden({ + inputValue: amountIn.value, + inputReserve: getPoolAmount(amountIn.brand).value, + outputReserve: getPoolAmount(wantedAmountOut.brand).value, + }), + ); + const amountOut = zcf + .getAmountMath(wantedAmountOut.brand) + .make(outputValue); + + trade( + zcf, + { + seat: poolSeat, + gains: { + [getPoolKeyword(amountIn.brand)]: amountIn, + }, + losses: { + [getPoolKeyword(amountOut.brand)]: amountOut, + }, + }, + { + seat: swapSeat, + gains: { Out: amountOut }, + losses: { In: amountIn }, + }, + ); + swapSeat.exit(); + return `Swap successfully completed.`; + }; + + /** @type {OfferHandler} */ + const addLiquidityHandler = liqSeat => { + const userAllocation = liqSeat.getCurrentAllocation(); + + // Calculate how many liquidity tokens we should be minting. + // Calculations are based on the values represented by TokenA. + // If the current supply is zero, start off by just taking the + // value at TokenA and using it as the value for the + // liquidity token. + const tokenAPoolAmount = getPoolAmount(userAllocation.TokenA.brand); + const inputReserve = tokenAPoolAmount ? tokenAPoolAmount.value : 0; + const liquidityValueOut = calcLiqValueToMint( + harden({ + liqTokenSupply, + inputValue: userAllocation.TokenA.value, + inputReserve, + }), + ); + const liquidityAmountOut = liquidityAmountMath.make(liquidityValueOut); + liquidityMint.mintGains({ Liquidity: liquidityAmountOut }, poolSeat); + liqTokenSupply += liquidityValueOut; + + trade( + zcf, + { + seat: poolSeat, + gains: { + TokenA: userAllocation.TokenA, + TokenB: userAllocation.TokenB, + }, + }, + { seat: liqSeat, gains: { Liquidity: liquidityAmountOut } }, + ); - return makeEmptyOffer().then(poolHandle => { - const getPoolAmount = brand => { - const keyword = getPoolKeyword(brand); - return zcf.getCurrentAllocation(poolHandle)[keyword]; - }; + liqSeat.exit(); + return 'Added liquidity.'; + }; - const swapHook = offerHandle => { - const { - proposal: { - give: { In: amountIn }, - want: { Out: wantedAmountOut }, - }, - } = zcf.getOffer(offerHandle); - const outputValue = getInputPrice( + /** @type {OfferHandler} */ + const removeLiquidityHandler = removeLiqSeat => { +// TODO (cth) should burn tokens? + const userAllocation = removeLiqSeat.getCurrentAllocation(); + const liquidityValueIn = userAllocation.Liquidity.value; + + const newUserTokenAAmount = zcf + .getAmountMath(userAllocation.TokenA.brand) + .make( + calcValueToRemove( harden({ - inputValue: amountIn.value, - inputReserve: getPoolAmount(amountIn.brand).value, - outputReserve: getPoolAmount(wantedAmountOut.brand).value, + liqTokenSupply, + poolValue: getPoolAmount(userAllocation.TokenA.brand).value, + liquidityValueIn, }), - ); - const amountOut = zcf - .getAmountMath(wantedAmountOut.brand) - .make(outputValue); - - trade( - { - offerHandle: poolHandle, - gains: { - [getPoolKeyword(amountIn.brand)]: amountIn, - }, - losses: { - [getPoolKeyword(amountOut.brand)]: amountOut, - }, - }, - { - offerHandle, - gains: { Out: amountOut }, - losses: { In: amountIn }, - }, - ); - zcf.complete(harden([offerHandle])); - return `Swap successfully completed.`; - }; - - const addLiquidityHook = offerHandle => { - const userAllocation = zcf.getCurrentAllocation(offerHandle); - - // Calculate how many liquidity tokens we should be minting. - // Calculations are based on the values represented by TokenA. - // If the current supply is zero, start off by just taking the - // value at TokenA and using it as the value for the - // liquidity token. - const tokenAPoolAmount = getPoolAmount(userAllocation.TokenA.brand); - const inputReserve = tokenAPoolAmount ? tokenAPoolAmount.value : 0; - const liquidityValueOut = calcLiqValueToMint( + ), + ); + const newUserTokenBAmount = zcf + .getAmountMath(userAllocation.TokenB.brand) + .make( + calcValueToRemove( harden({ liqTokenSupply, - inputValue: userAllocation.TokenA.value, - inputReserve, + poolValue: getPoolAmount(userAllocation.TokenB.brand).value, + liquidityValueIn, }), - ); - const liquidityAmountOut = liquidityAmountMath.make(liquidityValueOut); - const liquidityPaymentP = liquidityMint.mintPayment(liquidityAmountOut); - - return escrowAndAllocateTo({ - amount: liquidityAmountOut, - payment: liquidityPaymentP, - keyword: 'Liquidity', - recipientHandle: offerHandle, - }).then(() => { - liqTokenSupply += liquidityValueOut; - - trade( - { - offerHandle: poolHandle, - gains: { - TokenA: userAllocation.TokenA, - TokenB: userAllocation.TokenB, - }, - }, - // We've already given the user their liquidity using - // escrowAndAllocateTo - { offerHandle, gains: {} }, - ); - - zcf.complete(harden([offerHandle])); - return 'Added liquidity.'; - }); - }; - - const removeLiquidityHook = offerHandle => { - const userAllocation = zcf.getCurrentAllocation(offerHandle); - const liquidityValueIn = userAllocation.Liquidity.value; - - const newUserTokenAAmount = zcf - .getAmountMath(userAllocation.TokenA.brand) - .make( - calcValueToRemove( - harden({ - liqTokenSupply, - poolValue: getPoolAmount(userAllocation.TokenA.brand).value, - liquidityValueIn, - }), - ), - ); - const newUserTokenBAmount = zcf - .getAmountMath(userAllocation.TokenB.brand) - .make( - calcValueToRemove( - harden({ - liqTokenSupply, - poolValue: getPoolAmount(userAllocation.TokenB.brand).value, - liquidityValueIn, - }), - ), - ); - - liqTokenSupply -= liquidityValueIn; - - trade( - { - offerHandle: poolHandle, - gains: { Liquidity: userAllocation.Liquidity }, - }, - { - offerHandle, - gains: { - TokenA: newUserTokenAAmount, - TokenB: newUserTokenBAmount, - }, - }, - ); - - zcf.complete(harden([offerHandle])); - return 'Liquidity successfully removed.'; - }; - - const addLiquidityExpected = harden({ - give: { TokenA: null, TokenB: null }, - want: { Liquidity: null }, - }); + ), + ); - const removeLiquidityExpected = harden({ - want: { TokenA: null, TokenB: null }, - give: { Liquidity: null }, - }); + liqTokenSupply -= liquidityValueIn; + + trade( + zcf, + { + seat: poolSeat, + gains: { Liquidity: userAllocation.Liquidity }, + }, + { + seat: removeLiqSeat, + gains: { + TokenA: newUserTokenAAmount, + TokenB: newUserTokenBAmount, + }, + }, + ); - const swapExpected = { - want: { Out: null }, - give: { In: null }, - }; + removeLiqSeat.exit(); + return 'Liquidity successfully removed.'; + }; - const makeAddLiquidityInvite = () => - zcf.makeInvitation( - checkHook(addLiquidityHook, addLiquidityExpected), - 'autoswap add liquidity', - ); + const addLiquidityExpected = harden({ + give: { TokenA: null, TokenB: null }, + want: { Liquidity: null }, + }); - const makeRemoveLiquidityInvite = () => - zcf.makeInvitation( - checkHook(removeLiquidityHook, removeLiquidityExpected), - 'autoswap remove liquidity', - ); + const removeLiquidityExpected = harden({ + want: { TokenA: null, TokenB: null }, + give: { Liquidity: null }, + }); - const makeSwapInvite = () => - zcf.makeInvitation(checkHook(swapHook, swapExpected), 'autoswap swap'); + const swapExpected = { + want: { Out: null }, + give: { In: null }, + }; - /** - * `getCurrentPrice` calculates the result of a trade, given a certain amount - * of digital assets in. - * @param {Amount} amountIn - the amount of digital - * assets to be sent in - */ - const getCurrentPrice = (amountIn, brandOut) => { - const inputReserve = getPoolAmount(amountIn.brand).value; - const outputReserve = getPoolAmount(brandOut).value; - const outputValue = getInputPrice( - harden({ - inputValue: amountIn.value, - inputReserve, - outputReserve, - }), - ); - return zcf.getAmountMath(brandOut).make(outputValue); - }; + const makeAddLiquidityInvite = () => + zcf.makeInvitation( + assertProposalKeywords(addLiquidityHandler, addLiquidityExpected), + 'autoswap add liquidity', + ); - const getPoolAllocation = () => - zcf.getCurrentAllocation(poolHandle, brandKeywordRecord); + const makeRemoveLiquidityInvite = () => + zcf.makeInvitation( + assertProposalKeywords(removeLiquidityHandler, removeLiquidityExpected), + 'autoswap remove liquidity', + ); - zcf.initPublicAPI( - harden({ - getCurrentPrice, - getLiquidityIssuer: () => liquidityIssuer, - getPoolAllocation, - makeSwapInvite, - makeAddLiquidityInvite, - makeRemoveLiquidityInvite, - }), - ); + const makeSwapInvite = () => + zcf.makeInvitation( + assertProposalKeywords(swapHandler, swapExpected), + 'autoswap swap', + ); - return makeAddLiquidityInvite(); - }); + /** + * `getCurrentPrice` calculates the result of a trade, given a certain amount + * of digital assets in. + * @param {Amount} amountIn - the amount of digital + * assets to be sent in + * @param brandOut - The brand of asset desired + */ + const getCurrentPrice = (amountIn, brandOut) => { + const inputReserve = getPoolAmount(amountIn.brand).value; + const outputReserve = getPoolAmount(brandOut).value; + const outputValue = getInputPrice( + harden({ + inputValue: amountIn.value, + inputReserve, + outputReserve, + }), + ); + return zcf.getAmountMath(brandOut).make(outputValue); + }; + + const getPoolAllocation = poolSeat.getCurrentAllocation; + + const publicFacet = harden({ + getCurrentPrice, + getLiquidityIssuer: () => liquidityIssuer, + getPoolAllocation, + makeSwapInvite, + makeAddLiquidityInvite, + makeRemoveLiquidityInvite, }); + + return { publicFacet, creatorFacet: publicFacet }; }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/test/unitTests/contracts/test-autoswap.js b/packages/zoe/test/unitTests/contracts/test-autoswap.js index 606418fd85f..4046b62a852 100644 --- a/packages/zoe/test/unitTests/contracts/test-autoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-autoswap.js @@ -1,16 +1,17 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; -// eslint-disable-next-line import/no-extraneous-dependencies -import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; -// noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; -import fakeVatAdmin from './fakeVatAdmin'; +import { + installationPFromSource, + assertOfferResult, + assertPayoutAmount, + getInviteFields, +} from '../../zoeTestHelpers'; -const autoswapRoot = `${__dirname}/../../../src/contracts/autoswap`; +const autoswap = `${__dirname}/../../../src/contracts/autoswap`; test('autoSwap with valid offers', async t => { t.plan(19); @@ -22,9 +23,10 @@ test('autoSwap with valid offers', async t => { simoleanMint, moola, simoleans, + zoe, } = setup(); - const zoe = makeZoe(fakeVatAdmin); const inviteIssuer = zoe.getInvitationIssuer(); + const installation = await installationPFromSource(zoe, autoswap); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(10)); @@ -36,20 +38,15 @@ test('autoSwap with valid offers', async t => { const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(3)); // Alice creates an autoswap instance - - // Pack the contract. - const bundle = await bundleSource(autoswapRoot); - - const installationHandle = await zoe.install(bundle); const issuerKeywordRecord = harden({ TokenA: moolaIssuer, TokenB: simoleanIssuer, }); - const { - invite: aliceInvite, - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, issuerKeywordRecord); - const liquidityIssuer = await E(publicAPI).getLiquidityIssuer(); + const { creatorFacet: publicFacet } = await zoe.makeInstance( + installation, + issuerKeywordRecord, + ); + const liquidityIssuer = await E(publicFacet).getLiquidityIssuer(); const liquidity = liquidityIssuer.getAmountMath().make; // Alice adds liquidity @@ -63,24 +60,18 @@ test('autoSwap with valid offers', async t => { TokenA: aliceMoolaPayment, TokenB: aliceSimoleanPayment, }; - - const { - payout: aliceAddLiquidityPayoutP, - outcome: liquidityOkP, - } = await zoe.offer(aliceInvite, aliceProposal, alicePayments); - - t.equals(await liquidityOkP, 'Added liquidity.', `added liquidity message`); - - const liquidityPayments = await aliceAddLiquidityPayoutP; - const liquidityPayout = await liquidityPayments.Liquidity; - - t.deepEquals( - await liquidityIssuer.getAmountOf(liquidityPayout), - liquidity(10), - `liquidity payout`, + const aliceInvite = await publicFacet.makeAddLiquidityInvite(); + const aliceSeat = await zoe.offer( + aliceInvite, + aliceProposal, + alicePayments, ); + assertOfferResult(t, aliceSeat, 'Added liquidity.'); + + const liquidityPayout = await aliceSeat.getPayout('Liquidity'); + assertPayoutAmount(t, liquidityIssuer, liquidityPayout, liquidity(10)); t.deepEquals( - await E(publicAPI).getPoolAllocation(), + await E(publicFacet).getPoolAllocation(), { TokenA: moola(10), TokenB: simoleans(5), @@ -90,20 +81,16 @@ test('autoSwap with valid offers', async t => { ); // Alice creates an invite for autoswap and sends it to Bob - const bobInvite = await E(publicAPI).makeSwapInvite(); + const bobInvite = await E(publicFacet).makeSwapInvite(); // Bob claims it const bobExclInvite = await inviteIssuer.claim(bobInvite); - const getInstanceHandle = iP => - E(inviteIssuer) - .getAmountOf(iP) - .then(amount => amount.value[0].instanceHandle); - const bobInstanceHandle = await getInstanceHandle(bobExclInvite); const { - publicAPI: bobAutoswap, - installationHandle: bobInstallationId, - } = zoe.getInstanceRecord(bobInstanceHandle); - t.equals(bobInstallationId, installationHandle, `installationHandle`); + installation: bobInstallation, + instance: bobInstance, + } = await getInviteFields(inviteIssuer, bobExclInvite); + t.equals(bobInstallation, installation, `installation`); + const bobAutoswap = E(zoe).getPublicFacet(bobInstance); // Bob looks up the price of 3 moola in simoleans const simoleanAmounts = await E(bobAutoswap).getCurrentPrice( @@ -113,37 +100,28 @@ test('autoSwap with valid offers', async t => { t.deepEquals(simoleanAmounts, simoleans(1), `currentPrice`); // Bob escrows - const bobMoolaForSimProposal = harden({ want: { Out: simoleans(1) }, give: { In: moola(3) }, }); const bobMoolaForSimPayments = harden({ In: bobMoolaPayment }); - const { payout: bobPayoutP, outcome: offerOkP } = await zoe.offer( + const bobSeat = await zoe.offer( bobExclInvite, bobMoolaForSimProposal, bobMoolaForSimPayments, ); // Bob swaps - t.equal(await offerOkP, 'Swap successfully completed.', `swap message 1`); - - const bobPayout = await bobPayoutP; + assertOfferResult(t, bobSeat, 'Swap successfully completed.'); - const bobMoolaPayout1 = await bobPayout.In; - const bobSimoleanPayout1 = await bobPayout.Out; + const { + In: bobMoolaPayout1, + Out: bobSimoleanPayout1, + } = await bobSeat.getPayouts(); - t.deepEqual( - await moolaIssuer.getAmountOf(bobMoolaPayout1), - moola(0), - `bob moola`, - ); - t.deepEqual( - await simoleanIssuer.getAmountOf(bobSimoleanPayout1), - simoleans(1), - `bob simoleans`, - ); + assertPayoutAmount(t, moolaIssuer, bobMoolaPayout1, moola(0)); + assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout1, simoleans(1)); t.deepEquals( await E(bobAutoswap).getPoolAllocation(), { @@ -151,7 +129,7 @@ test('autoSwap with valid offers', async t => { TokenB: simoleans(4), Liquidity: liquidity(0), }, - `pool allocation`, + `pool allocation after first swap`, ); // Bob looks up the price of 3 simoleans @@ -169,70 +147,56 @@ test('autoSwap with valid offers', async t => { }); const simsForMoolaPayments = harden({ In: bobSimoleanPayment }); - const { - payout: bobSimsForMoolaPayoutP, - outcome: simsForMoolaOkP, - } = await zoe.offer( + const bobSecondSeat = await zoe.offer( bobSecondInvite, bobSimsForMoolaProposal, simsForMoolaPayments, ); - t.equal( - await simsForMoolaOkP, - 'Swap successfully completed.', - `swap message 2`, - ); + assertOfferResult(t, bobSeat, 'Swap successfully completed.'); - const bobSimsForMoolaPayout = await bobSimsForMoolaPayoutP; - const bobMoolaPayout2 = await bobSimsForMoolaPayout.Out; - const bobSimoleanPayout2 = await bobSimsForMoolaPayout.In; + const { + Out: bobMoolaPayout2, + In: bobSimoleanPayout2, + } = await bobSecondSeat.getPayouts(); + assertPayoutAmount(t, moolaIssuer, bobMoolaPayout2, moola(5)); + assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout2, simoleans(0)); - t.deepEqual(await moolaIssuer.getAmountOf(bobMoolaPayout2), moola(5)); t.deepEqual( - await simoleanIssuer.getAmountOf(bobSimoleanPayout2), - simoleans(0), + await E(bobAutoswap).getPoolAllocation(), + { + TokenA: moola(8), + TokenB: simoleans(7), + Liquidity: liquidity(0), + }, + `pool allocation after swap`, ); - t.deepEqual(await E(bobAutoswap).getPoolAllocation(), { - TokenA: moola(8), - TokenB: simoleans(7), - Liquidity: liquidity(0), - }); // Alice removes her liquidity + const aliceSecondInvite = await E(publicFacet).makeRemoveLiquidityInvite(); // She's not picky... - const aliceSecondInvite = await E(publicAPI).makeRemoveLiquidityInvite(); const aliceRemoveLiquidityProposal = harden({ give: { Liquidity: liquidity(10) }, want: { TokenA: moola(0), TokenB: simoleans(0) }, }); - const { - payout: aliceRemoveLiquidityPayoutP, - outcome: removeLiquidityResultP, - } = await zoe.offer( + const aliceRmLiqSeat = await zoe.offer( aliceSecondInvite, aliceRemoveLiquidityProposal, harden({ Liquidity: liquidityPayout }), ); - t.equals(await removeLiquidityResultP, 'Liquidity successfully removed.'); - - const aliceRemoveLiquidityPayout = await aliceRemoveLiquidityPayoutP; - const aliceMoolaPayout = await aliceRemoveLiquidityPayout.TokenA; - const aliceSimoleanPayout = await aliceRemoveLiquidityPayout.TokenB; - const aliceLiquidityPayout = await aliceRemoveLiquidityPayout.Liquidity; - - t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(8)); - t.deepEquals( - await simoleanIssuer.getAmountOf(aliceSimoleanPayout), - simoleans(7), - ); - t.deepEquals( - await liquidityIssuer.getAmountOf(aliceLiquidityPayout), - liquidity(0), - ); - t.deepEquals(await E(publicAPI).getPoolAllocation(), { + assertOfferResult(t, aliceRmLiqSeat, 'Liquidity successfully removed.'); + const { + TokenA: aliceMoolaPayout, + TokenB: aliceSimoleanPayout, + Liquidity: aliceLiquidityPayout, + } = await aliceRmLiqSeat.getPayouts(); + assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(8)); + assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(7)); + assertPayoutAmount(t, liquidityIssuer, aliceLiquidityPayout, liquidity(0)); + + t.deepEquals(await E(publicFacet).getPoolAllocation(), { TokenA: moola(0), TokenB: simoleans(0), Liquidity: liquidity(10), @@ -253,9 +217,10 @@ test('autoSwap - test fee', async t => { simoleanMint, moola, simoleans, + zoe, } = setup(); - const zoe = makeZoe(fakeVatAdmin); const inviteIssuer = zoe.getInvitationIssuer(); + const installation = await installationPFromSource(zoe, autoswap); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(10000)); @@ -265,20 +230,15 @@ test('autoSwap - test fee', async t => { const bobMoolaPayment = moolaMint.mintPayment(moola(1000)); // Alice creates an autoswap instance - - // Pack the contract. - const bundle = await bundleSource(autoswapRoot); - - const installationHandle = await zoe.install(bundle); const issuerKeywordRecord = harden({ TokenA: moolaIssuer, TokenB: simoleanIssuer, }); - const { - invite: aliceAddLiquidityInvite, - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, issuerKeywordRecord); - const liquidityIssuer = await E(publicAPI).getLiquidityIssuer(); + const { creatorFacet: publicFacet } = await zoe.makeInstance( + installation, + issuerKeywordRecord, + ); + const liquidityIssuer = await E(publicFacet).getLiquidityIssuer(); const liquidity = liquidityIssuer.getAmountMath().make; // Alice adds liquidity @@ -294,42 +254,40 @@ test('autoSwap - test fee', async t => { TokenB: aliceSimoleanPayment, }); - const { - payout: aliceAddLiquidityPayoutP, - outcome: liquidityOkP, - } = await zoe.offer(aliceAddLiquidityInvite, aliceProposal, alicePayments); + const aliceAddLiquidityInvite = await publicFacet.makeAddLiquidityInvite(); + const aliceSeat = await zoe.offer( + aliceAddLiquidityInvite, + aliceProposal, + alicePayments, + ); - t.equals(await liquidityOkP, 'Added liquidity.'); + assertOfferResult(t, aliceSeat, 'Added liquidity.'); - const liquidityPayments = await aliceAddLiquidityPayoutP; - const liquidityPayout = await liquidityPayments.Liquidity; + const liquidityPayout = await aliceSeat.getPayout('Liquidity'); + assertPayoutAmount(t, liquidityIssuer, liquidityPayout, liquidity(10000)); t.deepEquals( - await liquidityIssuer.getAmountOf(liquidityPayout), - liquidity(10000), + await E(publicFacet).getPoolAllocation(), + { + TokenA: moola(10000), + TokenB: simoleans(10000), + Liquidity: liquidity(0), + }, + `pool allocation`, ); - t.deepEquals(await E(publicAPI).getPoolAllocation(), { - TokenA: moola(10000), - TokenB: simoleans(10000), - Liquidity: liquidity(0), - }); // Alice creates an invite for autoswap and sends it to Bob - const bobInvite = await E(publicAPI).makeSwapInvite(); + const bobInvite = await E(publicFacet).makeSwapInvite(); // Bob claims it const bobExclInvite = await inviteIssuer.claim(bobInvite); - const getInstanceHandle = iP => - E(inviteIssuer) - .getAmountOf(iP) - .then(amount => amount.value[0].instanceHandle); - const bobInstanceHandle = await getInstanceHandle(bobExclInvite); const { - publicAPI: bobAutoswap, - installationHandle: bobInstallationId, - } = zoe.getInstanceRecord(bobInstanceHandle); - t.equals(bobInstallationId, installationHandle); + installation: bobInstallation, + instance: bobInstance, + } = await getInviteFields(inviteIssuer, bobExclInvite); + t.equals(bobInstallation, bobInstallation); + const bobAutoswap = E(zoe).getPublicFacet(bobInstance); // Bob looks up the price of 1000 moola in simoleans const simoleanAmounts = await E(bobAutoswap).getCurrentPrice( moola(1000), @@ -345,28 +303,30 @@ test('autoSwap - test fee', async t => { const bobMoolaForSimPayments = harden({ In: bobMoolaPayment }); // Bob swaps - const { payout: bobPayoutP, outcome: offerOkP } = await zoe.offer( + const bobSeat = await zoe.offer( bobExclInvite, bobMoolaForSimProposal, bobMoolaForSimPayments, ); - t.equal(await offerOkP, 'Swap successfully completed.'); + assertOfferResult(t, bobSeat, 'Swap successfully completed.'); - const bobPayout = await bobPayoutP; - const bobMoolaPayout = await bobPayout.In; - const bobSimoleanPayout = await bobPayout.Out; + const { + In: bobMoolaPayout, + Out: bobSimoleanPayout, + } = await bobSeat.getPayouts(); - t.deepEqual(await moolaIssuer.getAmountOf(bobMoolaPayout), moola(0)); - t.deepEqual( - await simoleanIssuer.getAmountOf(bobSimoleanPayout), - simoleans(906), + assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(0)); + assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(906)); + t.deepEquals( + await E(bobAutoswap).getPoolAllocation(), + { + TokenA: moola(11000), + TokenB: simoleans(9094), + Liquidity: liquidity(0), + }, + `pool allocation after first swap`, ); - t.deepEquals(await E(bobAutoswap).getPoolAllocation(), { - TokenA: moola(11000), - TokenB: simoleans(9094), - Liquidity: liquidity(0), - }); } catch (e) { t.assert(false, e); console.log(e); diff --git a/packages/zoe/test/unitTests/contracts/test-barter.js b/packages/zoe/test/unitTests/contracts/test-barter.js index 5cce91660d6..14f159afaba 100644 --- a/packages/zoe/test/unitTests/contracts/test-barter.js +++ b/packages/zoe/test/unitTests/contracts/test-barter.js @@ -7,7 +7,7 @@ import { E } from '@agoric/eventual-send'; import { setup } from '../setupBasicMints'; import { installationPFromSource, - assertPayout, + assertPayoutDeposit, assertOfferResult, getInviteFields, } from '../../zoeTestHelpers'; @@ -124,11 +124,11 @@ test('barter with valid offers', async t => { // 6: Alice deposits her payout to ensure she can // Alice had 0 moola and 4 simoleans. - assertPayout(t, aliceMoolaPayout, aliceMoolaPurse, 0); - assertPayout(t, aliceSimoleanPayout, aliceSimoleanPurse, 4); + assertPayoutDeposit(t, aliceMoolaPayout, aliceMoolaPurse, moola(0)); + assertPayoutDeposit(t, aliceSimoleanPayout, aliceSimoleanPurse, simoleans(4)); // 7: Bob deposits his original payments to ensure he can // Bob had 3 moola and 3 simoleans. - assertPayout(t, bobMoolaPayout, bobMoolaPurse, 3); - assertPayout(t, bobSimoleanPayout, bobSimoleanPurse, 3); + assertPayoutDeposit(t, bobMoolaPayout, bobMoolaPurse, moola(3)); + assertPayoutDeposit(t, bobSimoleanPayout, bobSimoleanPurse, simoleans(3)); }); diff --git a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js index e1184dbe270..144c29d243d 100644 --- a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js +++ b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js @@ -3,18 +3,15 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; // eslint-disable-next-line import/no-extraneous-dependencies -import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; import { assert, details } from '@agoric/assert'; // noinspection ES6PreferShortImport -import { makeZoe } from '../../../src/zoeService/zoe'; import { setup } from '../setupBasicMints'; import { setupNonFungible } from '../setupNonFungibleMints'; -import fakeVatAdmin from './fakeVatAdmin'; import { installationPFromSource, - assertPayout, + assertPayoutDeposit, assertOfferResult, getInviteFields, } from '../../zoeTestHelpers'; @@ -71,7 +68,7 @@ test('simpleExchange with valid offers', async t => { }, `Order book is empty`, ); - t.equals(beforeAliceCount, 2); + t.equals(beforeAliceCount, 3); }); const { @@ -115,11 +112,11 @@ test('simpleExchange with valid offers', async t => { }, `order notifier is updated with Alice's sell order`, ); - t.equals(afterAliceCount, 3); + t.equals(afterAliceCount, 4); aliceNotifier.getUpdateSince(afterAliceCount).then(update => { t.notOk(update.value.sells[0], 'accepted offer from Bob'); - t.equals(update.updateCount, 4); + t.equals(update.updateCount, 5); }); }); @@ -200,13 +197,13 @@ test('simpleExchange with valid offers', async t => { // 6: Alice deposits her payout to ensure she can // Alice had 0 moola and 4 simoleans. - assertPayout(t, aliceMoolaPayout, aliceMoolaPurse, 0); - assertPayout(t, aliceSimoleanPayout, aliceSimoleanPurse, 4); + assertPayoutDeposit(t, aliceMoolaPayout, aliceMoolaPurse, moola(0)); + assertPayoutDeposit(t, aliceSimoleanPayout, aliceSimoleanPurse, simoleans(4)); // 7: Bob deposits his original payments to ensure he can // Bob had 3 moola and 3 simoleans. - assertPayout(t, bobMoolaPayout, bobMoolaPurse, 3); - assertPayout(t, bobSimoleanPayout, bobSimoleanPurse, 3); + assertPayoutDeposit(t, bobMoolaPayout, bobMoolaPurse, moola(3)); + assertPayoutDeposit(t, bobSimoleanPayout, bobSimoleanPurse, simoleans(3)); }); test('simpleExchange with multiple sell offers', async t => { @@ -441,8 +438,11 @@ test('simpleExchange with non-fungible assets', async t => { // Assert that the correct payout were received. // Alice has an empty RPG purse, and the Cheshire Cat. // Bob has an empty CryptoCat purse, and the Spell of Binding he wanted. - assertPayout(t, aliceRpgPayout, aliceRpgPurse, []); - assertPayout(t, aliceCcPayout, aliceCcPurse, ['Cheshire Cat']); - assertPayout(t, bobRpgPayout, bobRpgPurse, spell); - assertPayout(t, bobCcPayout, bobCcPurse, []); + const noCats = amountMaths.get('cc').getEmpty(); + const noRpgItems = amountMaths.get('rpg').getEmpty(); + assertPayoutDeposit(t, aliceRpgPayout, aliceRpgPurse, noRpgItems); + const cheshireCatAmount = cryptoCats(harden(['Cheshire Cat'])); + assertPayoutDeposit(t, aliceCcPayout, aliceCcPurse, cheshireCatAmount); + assertPayoutDeposit(t, bobRpgPayout, bobRpgPurse, rpgItems(spell)); + assertPayoutDeposit(t, bobCcPayout, bobCcPurse, noCats); }); diff --git a/packages/zoe/test/zoeTestHelpers.js b/packages/zoe/test/zoeTestHelpers.js index 42545f53691..ea4b423677f 100644 --- a/packages/zoe/test/zoeTestHelpers.js +++ b/packages/zoe/test/zoeTestHelpers.js @@ -1,14 +1,9 @@ import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; -export const assertPayout = (t, payout, purse, amount) => { - payout.then(payment => { - purse.deposit(payment); - t.deepEquals( - purse.getCurrentAmount().value, - amount, - `payout was ${amount}`, - ); +export const assertPayoutAmount = (t, issuer, payout, expectedAmount) => { + issuer.getAmountOf(payout).then(amount => { + t.deepEquals(amount, expectedAmount, `payout was ${amount.value}`); }); }; From 0179ec5da4792e582a769821c341d452ebe570b0 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Mon, 10 Aug 2020 11:47:19 -0700 Subject: [PATCH 22/31] fix: update autoswap from addEmptySeat() to makeEmptySeatKit() --- packages/zoe/src/contracts/autoswap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zoe/src/contracts/autoswap.js b/packages/zoe/src/contracts/autoswap.js index 7fd86137386..0d7ec444756 100644 --- a/packages/zoe/src/contracts/autoswap.js +++ b/packages/zoe/src/contracts/autoswap.js @@ -49,7 +49,7 @@ const start = async (zcf, _terms) => { throw new Error('getPoolKeyword: brand not found'); }; - const { zcfSeat: poolSeat } = zcf.addEmptySeat(); + const { zcfSeat: poolSeat } = zcf.makeEmptySeatKit(); const getPoolAmount = brand => { const keyword = getPoolKeyword(brand); return poolSeat.getCurrentAllocation()[keyword]; From e00ae8348951614df259bccb4001bb45f0da54eb Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Mon, 10 Aug 2020 15:13:06 -0700 Subject: [PATCH 23/31] chore: add publicFacet and instance to MakeInstanceResult (#1418) --- packages/zoe/src/types.js | 9 +++------ packages/zoe/src/zoeService/zoe.js | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index 1b45a995dba..f81f21feb1a 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -106,14 +106,11 @@ * expected for every rule under `give`. */ -/** - * @typedef {Object} CreatorFacetWInstance - * @property {() => Instance} getInstance - */ - /** * @typedef {Object} MakeInstanceResult - * @property {CreatorFacetWInstance & Record} creatorFacet + * @property {Record} creatorFacet + * @property {{}} Instance + * @property {Record} publicFacet * @property {Payment} creatorInvitation */ diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index 9a752c3b089..beba8e5f299 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -266,15 +266,12 @@ function makeZoe(vatAdminSvc) { addSeatObjPromiseKit.resolve(addSeatObj); publicFacetPromiseKit.resolve(publicFacet); - const creatorFacetWInstance = { - ...creatorFacet, - getInstance: () => instance, - }; - // Actually returned to the user. return { - creatorFacet: creatorFacetWInstance, + creatorFacet, creatorInvitation: await creatorInvitation, + instance, + publicFacet, }; }, offer: async ( From b7f46c2bcf666ea07966a0baecaa8d15a812e4c2 Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Mon, 10 Aug 2020 15:18:12 -0700 Subject: [PATCH 24/31] chore: fix typing of MakeInstanceResult --- packages/zoe/src/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index f81f21feb1a..deec0d6f673 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -109,7 +109,7 @@ /** * @typedef {Object} MakeInstanceResult * @property {Record} creatorFacet - * @property {{}} Instance + * @property {Instance} Instance * @property {Record} publicFacet * @property {Payment} creatorInvitation */ From e34eb0762ff8f8fadf71f3527d2144bb4a73f15c Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Mon, 10 Aug 2020 15:56:54 -0700 Subject: [PATCH 25/31] chore: add hasExited, getNotifier to UserSeat --- packages/zoe/src/types.js | 4 +++- packages/zoe/src/zoeService/zoeSeat.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index deec0d6f673..fc3d3e2c375 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -74,12 +74,14 @@ /** * @typedef {Object} UserSeat - * @property {() => Allocation} getCurrentAllocation + * @property {() => Promise} getCurrentAllocation * @property {() => Promise} getProposal * @property {() => Promise} getPayouts * @property {(keyword: Keyword) => Promise} getPayout * @property {() => Promise} getOfferResult * @property {() => void=} exit + * @property {() => Promise} hasExited + * @property {() => Promise} getNotifier * * @typedef {any} OfferResult */ diff --git a/packages/zoe/src/zoeService/zoeSeat.js b/packages/zoe/src/zoeService/zoeSeat.js index 2e1289bc9fb..1fef83ed3a4 100644 --- a/packages/zoe/src/zoeService/zoeSeat.js +++ b/packages/zoe/src/zoeService/zoeSeat.js @@ -58,14 +58,16 @@ export const makeZoeSeatAdminKit = ( /** @type {UserSeat} */ const userSeat = harden({ - getCurrentAllocation: () => zoeSeatAdmin.getCurrentAllocation(), + getCurrentAllocation: async () => zoeSeatAdmin.getCurrentAllocation(), getProposal: async () => proposal, getPayouts: async () => payoutPromiseKit.promise, getPayout: async keyword => payoutPromiseKit.promise.then(payouts => payouts[keyword]), getOfferResult: async () => offerResultPromiseKit.promise, + hasExited: async () => instanceAdmin.hasZoeSeatAdmin(zoeSeatAdmin), exit: async () => exitObjPromiseKit.promise.then(exitObj => E(exitObj).exit()), + getNotifier: async () => notifier, }); return { userSeat, zoeSeatAdmin, notifier }; From 69f402ea393c0189cbe1ff02d9981174030faa8a Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Mon, 10 Aug 2020 17:43:44 -0700 Subject: [PATCH 26/31] Update more unit tests (#1422) * chore: update tests * chore: finish automaticRefund tests * chore: update barterExchange and brokenContract tests. Small changes to Zoe and types (#1427) --- packages/zoe/src/contracts/barterExchange.js | 12 +- packages/zoe/src/types.js | 2 +- packages/zoe/src/zoeService/zoe.js | 9 + .../unitTests/contracts/brokenAutoRefund.js | 17 +- .../unitTests/contracts/test-atomicSwap.js | 39 ++-- .../contracts/test-automaticRefund.js | 207 +++++++----------- .../test/unitTests/contracts/test-barter.js | 41 ++-- .../contracts/test-brokenContract.js | 12 +- packages/zoe/test/zoeTestHelpers.js | 6 +- 9 files changed, 151 insertions(+), 194 deletions(-) diff --git a/packages/zoe/src/contracts/barterExchange.js b/packages/zoe/src/contracts/barterExchange.js index 70a88f0a661..b6b9f1cc25d 100644 --- a/packages/zoe/src/contracts/barterExchange.js +++ b/packages/zoe/src/contracts/barterExchange.js @@ -4,8 +4,7 @@ import makeStore from '@agoric/store'; import '../../exported'; // Eventually will be importable from '@agoric/zoe-contract-support' -import { trade } from '../contractSupport'; -import { satisfies } from '../contractSupport/zoeHelpers'; +import { trade, satisfies } from '../contractSupport'; /** * This Barter Exchange accepts offers to trade arbitrary goods for other @@ -126,16 +125,15 @@ const start = (zcf, _terms) => { return 'Trade completed.'; }; - const makeExchangeInvite = () => + const makeExchangeInvitation = () => zcf.makeInvitation(exchangeOfferHandler, 'exchange'); - const inviteFacet = harden({ makeInvite: makeExchangeInvite }); + const publicFacet = harden({ makeInvitation: makeExchangeInvitation }); const creatorFacet = harden({ - makeInvite: makeExchangeInvite, - getPublicFacet: () => inviteFacet, + makeInvitation: makeExchangeInvitation, }); - return { publicFacet: inviteFacet, creatorFacet }; + return { publicFacet, creatorFacet }; }; harden(start); diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index fc3d3e2c375..f3f28d88328 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -111,7 +111,7 @@ /** * @typedef {Object} MakeInstanceResult * @property {Record} creatorFacet - * @property {Instance} Instance + * @property {Instance} instance * @property {Record} publicFacet * @property {Payment} creatorInvitation */ diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index beba8e5f299..2ee0859805a 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -266,6 +266,15 @@ function makeZoe(vatAdminSvc) { addSeatObjPromiseKit.resolve(addSeatObj); publicFacetPromiseKit.resolve(publicFacet); + // creatorInvitation can be undefined, but if it is defined, + // let's make sure it is an invitation. + if (creatorInvitation !== undefined) { + assert( + await invitationIssuer.isLive(creatorInvitation), + details`The contract did not correctly return a creatorInvitation`, + ); + } + // Actually returned to the user. return { creatorFacet, diff --git a/packages/zoe/test/unitTests/contracts/brokenAutoRefund.js b/packages/zoe/test/unitTests/contracts/brokenAutoRefund.js index cb4c9daad68..44dda264def 100644 --- a/packages/zoe/test/unitTests/contracts/brokenAutoRefund.js +++ b/packages/zoe/test/unitTests/contracts/brokenAutoRefund.js @@ -2,18 +2,17 @@ /** * This is a a broken contact to test zoe's error handling - * @type {import('@agoric/zoe').MakeContract} zoe - the contract facet of zoe + * @type {ContractStartFn} */ -const makeContract = zcf => { - const refundOfferHook = offerHandle => { - zcf.complete(harden([offerHandle])); +const start = zcf => { + const refund = seat => { + seat.exit(); return `The offer was accepted`; }; - const makeRefundInvite = () => - zcf.makeInvitation(refundOfferHook, 'getRefund'); + const makeRefundInvite = () => zcf.makeInvitation(refund, 'getRefund'); // should be makeRefundInvite(). Intentionally wrong to provoke an error. - return makeRefundInvite; + return { creatorInvitation: makeRefundInvite }; }; -harden(makeContract); -export { makeContract }; +harden(start); +export { start }; diff --git a/packages/zoe/test/unitTests/contracts/test-atomicSwap.js b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js index 0af8aa6dacd..087e1217f52 100644 --- a/packages/zoe/test/unitTests/contracts/test-atomicSwap.js +++ b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js @@ -336,14 +336,13 @@ test('zoe - non-fungible atomicSwap', async t => { // Checking handling of duplicate issuers. I'd have preferred a raffle contract test('zoe - atomicSwap like-for-like', async t => { t.plan(13); - const { moolaIssuer, moolaMint, moola } = setup(); - const zoe = makeZoe(fakeVatAdmin); + const { moolaIssuer, moolaMint, moola, zoe } = setup(); const invitationIssuer = zoe.getInvitationIssuer(); // pack the contract const bundle = await bundleSource(atomicSwapRoot); // install the contract - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); @@ -358,8 +357,8 @@ test('zoe - atomicSwap like-for-like', async t => { Asset: moolaIssuer, Price: moolaIssuer, }); - const { invite: aliceInvite } = await zoe.makeInstance( - installationHandle, + const { creatorInvitation: aliceInvitation } = await zoe.makeInstance( + installation, issuerKeywordRecord, ); @@ -372,8 +371,8 @@ test('zoe - atomicSwap like-for-like', async t => { const alicePayments = { Asset: aliceMoolaPayment }; // 3: Alice makes the first offer in the swap. - const { payout: alicePayoutP, outcome: bobInviteP } = await zoe.offer( - aliceInvite, + const aliceSeat = await zoe.offer( + aliceInvitation, aliceProposal, alicePayments, ); @@ -382,17 +381,15 @@ test('zoe - atomicSwap like-for-like', async t => { // on how to use it and Bob decides he wants to be the // counter-party. + const bobInviteP = E(aliceSeat).getOfferResult(); const bobExclusiveInvite = await invitationIssuer.claim(bobInviteP); const { value: [bobInviteValue], } = await invitationIssuer.getAmountOf(bobExclusiveInvite); - const { - installationHandle: bobInstallationId, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInviteValue.instanceHandle); + const bobIssuers = zoe.getIssuers(bobInviteValue.instance); - t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); + t.equals(bobInviteValue.installation, installation, 'bobInstallationId'); t.deepEquals(bobIssuers, { Asset: moolaIssuer, Price: moolaIssuer }); t.deepEquals(bobInviteValue.asset, moola(3)); t.deepEquals(bobInviteValue.price, moola(7)); @@ -405,24 +402,18 @@ test('zoe - atomicSwap like-for-like', async t => { const bobPayments = { Price: bobMoolaPayment }; // 5: Bob makes an offer - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( - bobExclusiveInvite, - bobProposal, - bobPayments, - ); + const bobSeat = await zoe.offer(bobExclusiveInvite, bobProposal, bobPayments); t.equals( - await bobOutcomeP, + await E(bobSeat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; - const bobAssetPayout = await bobPayout.Asset; - const bobPricePayout = await bobPayout.Price; + const bobAssetPayout = await bobSeat.getPayout('Asset'); + const bobPricePayout = await bobSeat.getPayout('Price'); - const aliceAssetPayout = await alicePayout.Asset; - const alicePricePayout = await alicePayout.Price; + const aliceAssetPayout = await aliceSeat.getPayout('Asset'); + const alicePricePayout = await aliceSeat.getPayout('Price'); // Alice gets what Alice wanted t.deepEquals( diff --git a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js index 51ef70cc0d6..eaccaa6b39c 100644 --- a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js +++ b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js @@ -65,7 +65,7 @@ test('zoe - automaticRefund same issuer', async t => { const { moolaR, moola, zoe } = setup(); // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); - const installationHandle = await zoe.install(bundle); + const installation = await E(zoe).install(bundle); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(9)); @@ -75,8 +75,8 @@ test('zoe - automaticRefund same issuer', async t => { Contribution1: moolaR.issuer, Contribution2: moolaR.issuer, }); - const { invite } = await zoe.makeInstance( - installationHandle, + const { creatorInvitation } = await E(zoe).makeInstance( + installation, issuerKeywordRecord, ); @@ -86,14 +86,13 @@ test('zoe - automaticRefund same issuer', async t => { }); const alicePayments = harden({ Contribution2: aliceMoolaPayment }); - const { payout: payoutP } = await zoe.offer( - invite, + const seat = await E(zoe).offer( + creatorInvitation, aliceProposal, alicePayments, ); - const alicePayout = await payoutP; - const aliceMoolaPayout = await alicePayout.Contribution2; + const aliceMoolaPayout = await E(seat).getPayout('Contribution2'); // Alice got back what she put in t.deepEquals( @@ -110,9 +109,8 @@ test('zoe with automaticRefund', async t => { t.plan(11); try { // Setup zoe and mints - const { moolaR, simoleanR, moola, simoleans } = setup(); - const zoe = makeZoe(fakeVatAdmin); - const inviteIssuer = zoe.getInvitationIssuer(); + const { moolaR, simoleanR, moola, simoleans, zoe } = setup(); + const invitationIssuer = zoe.getInvitationIssuer(); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); @@ -128,15 +126,15 @@ test('zoe with automaticRefund', async t => { const bundle = await bundleSource(automaticRefundRoot); // 1: Alice creates an automatic refund instance - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); const issuerKeywordRecord = harden({ Contribution1: moolaR.issuer, Contribution2: simoleanR.issuer, }); const { - invite: aliceInvite, - instanceRecord: { publicAPI }, - } = await zoe.makeInstance(installationHandle, issuerKeywordRecord); + creatorInvitation: aliceInvitation, + publicFacet, + } = await zoe.makeInstance(installation, issuerKeywordRecord); // 2: Alice escrows with zoe const aliceProposal = harden({ @@ -146,43 +144,35 @@ test('zoe with automaticRefund', async t => { }); const alicePayments = { Contribution1: aliceMoolaPayment }; - // Alice gets two kinds of things back: a payout promise - // that resolves to the array of promises for payments, - // and an outcome promise, which is whatever the offerHook - // returns. + // Alice gets a seat back. // // In the 'automaticRefund' trivial contract, you just get your - // payments back when you make an offer. The outcome is simply + // payments back when you make an offer. The offerResult is simply // the string 'The offer was accepted' - const { payout: alicePayoutP, outcome: aliceOutcomeP } = await zoe.offer( - aliceInvite, + const aliceSeat = await zoe.offer( + aliceInvitation, aliceProposal, alicePayments, ); - const bobInvite = await E(publicAPI).makeInvite(); - const count = await E(publicAPI).getOffersCount(); + const bobInvitation = await E(publicFacet).makeInvitation(); + const count = await E(publicFacet).getOffersCount(); t.equals(count, 1); - // Imagine that Alice has shared the bobInvite with Bob. He - // will do a claim on the invite with the Zoe invite issuer and - // will check that the installationId and terms match what he + // Imagine that Alice has shared the bobInvitation with Bob. He + // will do a claim on the invitation with the Zoe invitation issuer and + // will check that the installation and terms match what he // expects - const exclusBobInvite = await inviteIssuer.claim(bobInvite); - const getInstanceHandle = iP => - E(inviteIssuer) - .getAmountOf(iP) - .then(amount => amount.value[0].instanceHandle); - const bobInstanceHandle = await getInstanceHandle(exclusBobInvite); + const exclusBobInvitation = await invitationIssuer.claim(bobInvitation); const { - installationHandle: bobInstallationId, - issuerKeywordRecord: bobIssuers, - } = zoe.getInstanceRecord(bobInstanceHandle); - t.equals(bobInstallationId, installationHandle); + value: [bobInviteValue], + } = await E(invitationIssuer).getAmountOf(exclusBobInvitation); + t.equals(bobInviteValue.installation, installation); // bob wants to know what issuers this contract is about and in // what order. Is it what he expects? + const bobIssuers = await E(zoe).getIssuers(bobInviteValue.instance); t.deepEquals(bobIssuers, { Contribution1: moolaR.issuer, Contribution2: simoleanR.issuer, @@ -197,16 +187,15 @@ test('zoe with automaticRefund', async t => { }); const bobPayments = { Contribution2: bobSimoleanPayment }; - // Bob also gets two things back: a payout promise and an - // outcome promise. - const { payout: bobPayoutP, outcome: bobOutcomeP } = await zoe.offer( - exclusBobInvite, + // Bob also gets a seat back + const bobSeat = await zoe.offer( + exclusBobInvitation, bobProposal, bobPayments, ); - t.equals(await aliceOutcomeP, 'The offer was accepted'); - t.equals(await bobOutcomeP, 'The offer was accepted'); + t.equals(await E(aliceSeat).getOfferResult(), 'The offer was accepted'); + t.equals(await E(bobSeat).getOfferResult(), 'The offer was accepted'); // These promise resolve when the offer completes, but it may // still take longer for a remote issuer to actually make the @@ -214,13 +203,11 @@ test('zoe with automaticRefund', async t => { // separately. // offer completes - const alicePayout = await alicePayoutP; - const bobPayout = await bobPayoutP; - const aliceMoolaPayout = await alicePayout.Contribution1; - const aliceSimoleanPayout = await alicePayout.Contribution2; + const aliceMoolaPayout = await aliceSeat.getPayout('Contribution1'); + const aliceSimoleanPayout = await aliceSeat.getPayout('Contribution2'); - const bobMoolaPayout = await bobPayout.Contribution1; - const bobSimoleanPayout = await bobPayout.Contribution2; + const bobMoolaPayout = await bobSeat.getPayout('Contribution1'); + const bobSimoleanPayout = await bobSeat.getPayout('Contribution2'); // Alice got back what she put in t.deepEquals( @@ -259,8 +246,7 @@ test('multiple instances of automaticRefund for the same Zoe', async t => { t.plan(6); try { // Setup zoe and mints - const { moolaR, simoleanR, moola, simoleans } = setup(); - const zoe = makeZoe(fakeVatAdmin); + const { moolaR, simoleanR, moola, simoleans, zoe } = setup(); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(30)); @@ -270,79 +256,56 @@ test('multiple instances of automaticRefund for the same Zoe', async t => { [moola10, moola10, moola10], ); - // 1: Alice creates 3 automatic refund instances + // Alice creates 3 automatic refund instances // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); const issuerKeywordRecord = harden({ ContributionA: moolaR.issuer, ContributionB: simoleanR.issuer, }); - const inviteIssuer = zoe.getInvitationIssuer(); - const { invite: aliceInvite1 } = await zoe.makeInstance( - installationHandle, - issuerKeywordRecord, - ); - const getInstanceHandle = iP => - E(inviteIssuer) - .getAmountOf(iP) - .then(amount => amount.value[0].instanceHandle); - const instanceHandle1 = await getInstanceHandle(aliceInvite1); - const { publicAPI: publicAPI1 } = zoe.getInstanceRecord(instanceHandle1); - - const { invite: aliceInvite2 } = await zoe.makeInstance( - installationHandle, - issuerKeywordRecord, - ); const { - value: [{ instanceHandle: instanceHandle2 }], - } = await inviteIssuer.getAmountOf(aliceInvite2); - const { publicAPI: publicAPI2 } = zoe.getInstanceRecord(instanceHandle2); + creatorInvitation: aliceInvitation1, + publicFacet: publicFacet1, + } = await zoe.makeInstance(installation, issuerKeywordRecord); - const { invite: aliceInvite3 } = await zoe.makeInstance( - installationHandle, - issuerKeywordRecord, - ); - const instanceHandle3 = await getInstanceHandle(aliceInvite3); - const { publicAPI: publicAPI3 } = zoe.getInstanceRecord(instanceHandle3); + const { + creatorInvitation: aliceInvitation2, + publicFacet: publicFacet2, + } = await zoe.makeInstance(installation, issuerKeywordRecord); + + const { + creatorInvitation: aliceInvitation3, + publicFacet: publicFacet3, + } = await zoe.makeInstance(installation, issuerKeywordRecord); - // 2: Alice escrows with zoe const aliceProposal = harden({ give: { ContributionA: moola(10) }, want: { ContributionB: simoleans(7) }, }); - // 5: Alice makes an offer - const { payout: payoutP1 } = await zoe.offer( - aliceInvite1, + const seat1 = await zoe.offer( + aliceInvitation1, aliceProposal, harden({ ContributionA: aliceMoolaPayments[0] }), ); - // 3: Alice escrows with zoe - // 5: Alice makes an offer - const { payout: payoutP2 } = await zoe.offer( - aliceInvite2, + const seat2 = await zoe.offer( + aliceInvitation2, aliceProposal, harden({ ContributionA: aliceMoolaPayments[1] }), ); - // 4: Alice escrows with zoe - // 5: Alice makes an offer - const { payout: payoutP3 } = await zoe.offer( - aliceInvite3, + const seat3 = await zoe.offer( + aliceInvitation3, aliceProposal, harden({ ContributionA: aliceMoolaPayments[2] }), ); - const payout1 = await payoutP1; - const payout2 = await payoutP2; - const payout3 = await payoutP3; - - const moolaPayout1 = await payout1.ContributionA; - const moolaPayout2 = await payout2.ContributionA; - const moolaPayout3 = await payout3.ContributionA; + const moolaPayout1 = await seat1.getPayout('ContributionA'); + const moolaPayout2 = await seat2.getPayout('ContributionA'); + const moolaPayout3 = await seat3.getPayout('ContributionA'); // Ensure that she got what she put in for each t.deepEquals( @@ -359,9 +322,9 @@ test('multiple instances of automaticRefund for the same Zoe', async t => { ); // Ensure that the number of offers received by each instance is one - t.equals(await E(publicAPI1).getOffersCount(), 1); - t.equals(await E(publicAPI2).getOffersCount(), 1); - t.equals(await E(publicAPI3).getOffersCount(), 1); + t.equals(await E(publicFacet1).getOffersCount(), 1); + t.equals(await E(publicFacet2).getOffersCount(), 1); + t.equals(await E(publicFacet3).getOffersCount(), 1); } catch (e) { t.assert(false, e); console.log(e); @@ -372,8 +335,7 @@ test('zoe - alice tries to complete after completion has already occurred', asyn t.plan(5); try { // Setup zoe and mints - const { moolaR, simoleanR, moola, simoleans } = setup(); - const zoe = makeZoe(fakeVatAdmin); + const { moolaR, simoleanR, moola, simoleans, zoe } = setup(); // Setup Alice const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); @@ -382,13 +344,13 @@ test('zoe - alice tries to complete after completion has already occurred', asyn // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); const issuerKeywordRecord = harden({ ContributionA: moolaR.issuer, ContributionB: simoleanR.issuer, }); - const { invite } = await zoe.makeInstance( - installationHandle, + const { creatorInvitation } = await zoe.makeInstance( + installation, issuerKeywordRecord, ); @@ -398,19 +360,24 @@ test('zoe - alice tries to complete after completion has already occurred', asyn }); const alicePayments = { ContributionA: aliceMoolaPayment }; - const { completeObj, payout: payoutP, outcome: outcomeP } = await zoe.offer( - invite, + const aliceSeat = await zoe.offer( + creatorInvitation, aliceProposal, alicePayments, ); - await outcomeP; + await E(aliceSeat).getOfferResult(); - t.throws(() => completeObj.complete(), /Error: Offer is not active/); + console.log( + 'EXPECTED ERROR: Cannot exit seat. Seat has already exited >>>', + ); + t.rejects( + () => E(aliceSeat).exit(), + /Error: Cannot exit seat. Seat has already exited/, + ); - const payout = await payoutP; - const moolaPayout = await payout.ContributionA; - const simoleanPayout = await payout.ContributionB; + const moolaPayout = await aliceSeat.getPayout('ContributionA'); + const simoleanPayout = await aliceSeat.getPayout('ContributionB'); // Alice got back what she put in t.deepEquals( @@ -441,20 +408,19 @@ test('zoe - alice tries to complete after completion has already occurred', asyn test('zoe - automaticRefund non-fungible', async t => { t.plan(1); // Setup zoe and mints - const { ccIssuer, ccMint, cryptoCats } = setupNonFungible(); + const { ccIssuer, ccMint, cryptoCats, zoe } = setupNonFungible(); - const zoe = makeZoe(fakeVatAdmin); // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); // Setup Alice const aliceCcPayment = ccMint.mintPayment(cryptoCats(harden(['tigger']))); // 1: Alice creates an automatic refund instance const issuerKeywordRecord = harden({ Contribution: ccIssuer }); - const { invite } = await zoe.makeInstance( - installationHandle, + const { creatorInvitation } = await zoe.makeInstance( + installation, issuerKeywordRecord, ); @@ -464,14 +430,9 @@ test('zoe - automaticRefund non-fungible', async t => { }); const alicePayments = { Contribution: aliceCcPayment }; - const { payout: payoutP } = await zoe.offer( - invite, - aliceProposal, - alicePayments, - ); + const seat = await zoe.offer(creatorInvitation, aliceProposal, alicePayments); - const alicePayout = await payoutP; - const aliceCcPayout = await alicePayout.Contribution; + const aliceCcPayout = await seat.getPayout('Contribution'); // Alice got back what she put in t.deepEquals( diff --git a/packages/zoe/test/unitTests/contracts/test-barter.js b/packages/zoe/test/unitTests/contracts/test-barter.js index 14f159afaba..7012108b756 100644 --- a/packages/zoe/test/unitTests/contracts/test-barter.js +++ b/packages/zoe/test/unitTests/contracts/test-barter.js @@ -7,9 +7,9 @@ import { E } from '@agoric/eventual-send'; import { setup } from '../setupBasicMints'; import { installationPFromSource, - assertPayoutDeposit, + assertPayoutAmount, assertOfferResult, - getInviteFields, + getInvitationFields, } from '../../zoeTestHelpers'; const barter = `${__dirname}/../../../src/contracts/barterExchange`; @@ -26,30 +26,25 @@ test('barter with valid offers', async t => { simoleans, zoe, } = setup(); - const inviteIssuer = zoe.getInvitationIssuer(); + const invitationIssuer = zoe.getInvitationIssuer(); const installation = await installationPFromSource(zoe, barter); // Setup Alice const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); // Setup Bob const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); - const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); // 1: Simon creates a barter instance and spreads the instance far and // wide with instructions on how to use it. - const { creatorFacet } = await zoe.makeInstance(installation, { + const { instance } = await zoe.makeInstance(installation, { Asset: moolaIssuer, Price: simoleanIssuer, }); - const publicFacet = await E(creatorFacet).getPublicFacet(); - const simonInvite = await E(publicFacet).makeInvite(); - const { instance } = await getInviteFields(simonInvite); - const aliceInvite = await E(E(zoe).getPublicFacet(instance)).makeInvite(); + const aliceInvitation = await E( + E(zoe).getPublicFacet(instance), + ).makeInvitation(); // 2: Alice escrows with zoe to create a sell order. She wants to // sell 3 moola and wants to receive at least 4 simoleans in @@ -62,21 +57,23 @@ test('barter with valid offers', async t => { const alicePayments = { In: aliceMoolaPayment }; // 3: Alice adds her sell order to the exchange const aliceSeat = await zoe.offer( - aliceInvite, + aliceInvitation, aliceSellOrderProposal, alicePayments, ); assertOfferResult(t, aliceSeat, 'Trade completed.'); - const bobInvite = await E(E(zoe).getPublicFacet(instance)).makeInvite(); + const bobInvitation = await E( + E(zoe).getPublicFacet(instance), + ).makeInvitation(); const { installation: bobInstallation, instance: bobInstance, - } = await getInviteFields(bobInvite); + } = await getInvitationFields(invitationIssuer, bobInvitation); // 4: Bob decides to join. - const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); + const bobExclusiveInvitation = await invitationIssuer.claim(bobInvitation); t.equals(bobInstallation, installation); t.equals(bobInstance, instance); @@ -92,7 +89,7 @@ test('barter with valid offers', async t => { // 5: Bob escrows with zoe const bobSeat = await zoe.offer( - bobExclusiveInvite, + bobExclusiveInvitation, bobBuyOrderProposal, bobPayments, ); @@ -122,13 +119,11 @@ test('barter with valid offers', async t => { // Alice sold all of her moola t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); - // 6: Alice deposits her payout to ensure she can // Alice had 0 moola and 4 simoleans. - assertPayoutDeposit(t, aliceMoolaPayout, aliceMoolaPurse, moola(0)); - assertPayoutDeposit(t, aliceSimoleanPayout, aliceSimoleanPurse, simoleans(4)); + assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(0)); + assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(4)); - // 7: Bob deposits his original payments to ensure he can // Bob had 3 moola and 3 simoleans. - assertPayoutDeposit(t, bobMoolaPayout, bobMoolaPurse, moola(3)); - assertPayoutDeposit(t, bobSimoleanPayout, bobSimoleanPurse, simoleans(3)); + assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(3)); + assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(3)); }); diff --git a/packages/zoe/test/unitTests/contracts/test-brokenContract.js b/packages/zoe/test/unitTests/contracts/test-brokenContract.js index 8642b35d8ce..ebd73ff8374 100644 --- a/packages/zoe/test/unitTests/contracts/test-brokenContract.js +++ b/packages/zoe/test/unitTests/contracts/test-brokenContract.js @@ -18,14 +18,18 @@ test('zoe - brokenAutomaticRefund', async t => { const zoe = makeZoe(fakeVatAdmin); // Pack the contract. const bundle = await bundleSource(automaticRefundRoot); - const installationHandle = await zoe.install(bundle); + const installation = await zoe.install(bundle); const issuerKeywordRecord = harden({ Contribution: moolaR.issuer }); - // 1: Alice tries to create an instance, but the contract is badly written. + // Alice tries to create an instance, but the contract is badly + // written. + console.log( + 'EXPECTED ERROR: The contract did not correctly return a creatorInvitation', + ); t.rejects( - () => zoe.makeInstance(installationHandle, issuerKeywordRecord), - new Error('invites must be issued by InviteIssuer'), + () => zoe.makeInstance(installation, issuerKeywordRecord), + new Error('The contract did not correctly return a creatorInvitation'), 'makeInstance should have thrown', ); }); diff --git a/packages/zoe/test/zoeTestHelpers.js b/packages/zoe/test/zoeTestHelpers.js index ea4b423677f..d8adf9e3978 100644 --- a/packages/zoe/test/zoeTestHelpers.js +++ b/packages/zoe/test/zoeTestHelpers.js @@ -24,7 +24,7 @@ export const assertRejectedOfferResult = (t, seat, expected) => { export const installationPFromSource = (zoe, source) => bundleSource(source).then(b => zoe.install(b)); -export const getInviteFields = (inviteIssuer, inviteP) => - E(inviteIssuer) - .getAmountOf(inviteP) +export const getInvitationFields = (invitationIssuer, invitationP) => + E(invitationIssuer) + .getAmountOf(invitationP) .then(amount => amount.value[0]); From 148ec6516a9e9129ac48564babfa86470fc449fa Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sun, 9 Aug 2020 19:10:16 -0600 Subject: [PATCH 27/31] fix: make the wallet pass unit tests --- .../dapp-svelte-wallet/api/src/lib-wallet.js | 118 +++++++------ .../api/test/test-lib-wallet.js | 158 +++++++++--------- packages/zoe/src/objArrayConversion.js | 2 +- 3 files changed, 134 insertions(+), 144 deletions(-) diff --git a/packages/dapp-svelte-wallet/api/src/lib-wallet.js b/packages/dapp-svelte-wallet/api/src/lib-wallet.js index e966a785f2a..53f0863f599 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/src/lib-wallet.js @@ -140,7 +140,7 @@ export async function makeWallet({ // Compiled offers (all ready to execute). const idToCompiledOfferP = new Map(); const idToComplete = new Map(); - const idToOfferHandle = new Map(); + const idToSeat = new Map(); const idToOutcome = new Map(); // Client-side representation of the purses inbox; @@ -374,11 +374,11 @@ export async function makeWallet({ // handle the update, which has already resolved to a record. If the offer is // 'done', mark the offer 'complete', otherwise resubscribe to the notifier. - function updateOrResubscribe(id, offerHandle, update) { + function updateOrResubscribe(id, seat, update) { const { updateCount } = update; if (updateCount === undefined) { // TODO do we still need these? - idToOfferHandle.delete(id); + idToSeat.delete(id); const offer = idToOffer.get(id); const completedOffer = { @@ -391,21 +391,26 @@ export async function makeWallet({ } else { E(idToNotifierP.get(id)) .getUpdateSince(updateCount) - .then(nextUpdate => updateOrResubscribe(id, offerHandle, nextUpdate)); + .then(nextUpdate => updateOrResubscribe(id, seat, nextUpdate)); } } - // There's a new offer. Ask Zoe to notify us when the offer is complete. - async function subscribeToNotifier(id, offerHandle) { - E(zoe) - .getOfferNotifier(offerHandle) + /** + * There's a new offer. Ask Zoe to notify us when the offer is complete. + * + * @param {string} id + * @param {UserSeat} seat + */ + async function subscribeToNotifier(id, seat) { + E(seat) + .getNotifier() .then(offerNotifierP => { if (!idToNotifierP.has(id)) { idToNotifierP.init(id, offerNotifierP); } E(offerNotifierP) .getUpdateSince() - .then(update => updateOrResubscribe(id, offerHandle, update)); + .then(update => updateOrResubscribe(id, seat, update)); }); } @@ -452,57 +457,48 @@ export async function makeWallet({ // === AWAITING TURN === // ===================== - const { - payout: payoutObjP, - completeObj, - outcome: outcomeP, - offerHandle: offerHandleP, - } = await E(zoe).offer( + const seat = await E(zoe).offer( invite, harden(proposal), harden(paymentKeywordRecord), ); - // ===================== - // === AWAITING TURN === - // ===================== - // This settles when the payments are escrowed in Zoe - const offerHandle = await offerHandleP; - // ===================== // === AWAITING TURN === // ===================== // This settles when the offer hook completes. - const outcome = await outcomeP; + const outcome = await E(seat).getOfferResult(); // We'll resolve when deposited. - const depositedP = payoutObjP.then(payoutObj => { - const payoutIndexToKeyword = []; - return Promise.all( - Object.entries(payoutObj).map(([keyword, payoutP], i) => { - // keyword may be an index for zoeKind === 'indexed', but we can still treat it - // as the keyword name for looking up purses and payouts (just happens to - // be an integer). - payoutIndexToKeyword[i] = keyword; - return payoutP; - }), - ).then(payoutArray => - Promise.all( - payoutArray.map(async (payoutP, payoutIndex) => { - const keyword = payoutIndexToKeyword[payoutIndex]; - const purse = purseKeywordRecord[keyword]; - if (purse && payoutP) { - const payout = await payoutP; - // eslint-disable-next-line no-use-before-define - return addPayment(payout, purse); - } - return undefined; + const depositedP = E(seat) + .getPayouts() + .then(payoutObj => { + const payoutIndexToKeyword = []; + return Promise.all( + Object.entries(payoutObj).map(([keyword, payoutP], i) => { + // keyword may be an index for zoeKind === 'indexed', but we can still treat it + // as the keyword name for looking up purses and payouts (just happens to + // be an integer). + payoutIndexToKeyword[i] = keyword; + return payoutP; }), - ), - ); - }); + ).then(payoutArray => + Promise.all( + payoutArray.map(async (payoutP, payoutIndex) => { + const keyword = payoutIndexToKeyword[payoutIndex]; + const purse = purseKeywordRecord[keyword]; + if (purse && payoutP) { + const payout = await payoutP; + // eslint-disable-next-line no-use-before-define + return addPayment(payout, purse); + } + return undefined; + }), + ), + ); + }); - return { depositedP, completeObj, outcome, offerHandle }; + return { depositedP, seat }; } // === API @@ -875,6 +871,7 @@ export async function makeWallet({ return undefined; } + /** @type {{ outcome?: any, depositedP?: Promise }} */ let ret = {}; let alreadyResolved = false; const rejected = e => { @@ -899,31 +896,27 @@ export async function makeWallet({ updateInboxState(id, pendingOffer); const compiledOffer = await idToCompiledOfferP.get(id); - const { - depositedP, - completeObj, - outcome, - offerHandle, - } = await executeOffer(compiledOffer); + const { depositedP, seat } = await executeOffer(compiledOffer); idToComplete.set(id, () => { alreadyResolved = true; - return E(completeObj).complete(); + return E(seat).exit(); }); - idToOfferHandle.set(id, offerHandle); + idToSeat.set(id, seat); // The offer might have been postponed, or it might have been immediately // consummated. Only subscribe if it was postponed. - E(zoe) - .isOfferActive(offerHandle) - .then(active => { - if (active) { - subscribeToNotifier(id, offerHandle); + E(seat) + .hasExited() + .then(exited => { + if (!exited) { + subscribeToNotifier(id, seat); } }); // The outcome is most often a string that can be returned, but // it could be an object. We don't do anything currently if it // is an object, but we will store it here for future use. + const outcome = E(seat).getOfferResult(); idToOutcome.set(id, outcome); ret = { outcome, depositedP }; @@ -951,6 +944,7 @@ export async function makeWallet({ return ret; } + /** @returns {[Petname, Issuer][]} */ function getIssuers() { return brandMapping.petnameToVal.entries().map(([petname, brand]) => { const { issuer } = brandTable.get(brand); @@ -1281,8 +1275,8 @@ export async function makeWallet({ cancelOffer, acceptOffer, getOffers, - getOfferHandle: id => idToOfferHandle.get(id), - getOfferHandles: ids => ids.map(wallet.getOfferHandle), + getSeat: id => idToSeat.get(id), + getSeats: ids => ids.map(wallet.getSeat), enableAutoDeposit, disableAutoDeposit, getDepositFacetId, diff --git a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js index 92b8f585790..3d5b894c6d6 100644 --- a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js @@ -1,7 +1,7 @@ // @ts-check import '@agoric/install-ses'; // calls lockdown() // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'tape-promise/tape'; import bundleSource from '@agoric/bundle-source'; import makeAmountMath from '@agoric/ertp/src/amountMath'; @@ -10,13 +10,14 @@ import { makeZoe } from '@agoric/zoe'; import fakeVatAdmin from '@agoric/zoe/test/unitTests/contracts/fakeVatAdmin'; import { makeRegistrar } from '@agoric/registrar'; +import { assert } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; import { makeBoard } from '@agoric/cosmic-swingset/lib/ag-solo/vats/lib-board'; import { makeWallet } from '../src/lib-wallet'; import '../src/types'; -const setupTest = async () => { +async function setupTest() { const pursesStateChangeLog = []; const inboxStateChangeLog = []; const pursesStateChangeHandler = data => { @@ -38,12 +39,12 @@ const setupTest = async () => { '@agoric/zoe/src/contracts/automaticRefund', ); const automaticRefundBundle = await bundleSource(automaticRefundContractRoot); - const installationHandle = await zoe.install(automaticRefundBundle); + const installation = await zoe.install(automaticRefundBundle); const issuerKeywordRecord = harden({ Contribution: moolaBundle.issuer }); - const { - invite, - instanceRecord: { handle: instanceHandle }, - } = await zoe.makeInstance(installationHandle, issuerKeywordRecord); + const { creatorInvitation: invite, instance } = await zoe.makeInstance( + installation, + issuerKeywordRecord, + ); // Create Autoswap instance const autoswapContractRoot = require.resolve( @@ -56,13 +57,15 @@ const setupTest = async () => { TokenB: simoleanBundle.issuer, }); const { - invite: addLiquidityInvite, - instanceRecord: { handle: autoswapInstanceHandle }, + publicFacet: autoswapPublicFacet, + instance: autoswapInstanceHandle, } = await zoe.makeInstance( autoswapInstallationHandle, autoswapIssuerKeywordRecord, ); + const addLiquidityInvite = await autoswapPublicFacet.makeAddLiquidityInvite(); + const wallet = await makeWallet({ zoe, board, @@ -79,14 +82,14 @@ const setupTest = async () => { wallet, invite, addLiquidityInvite, - installationHandle, - instanceHandle, + installation, + instance, autoswapInstanceHandle, autoswapInstallationHandle, pursesStateChangeLog, inboxStateChangeLog, }; -}; +} test('lib-wallet issuer and purse methods', async t => { t.plan(11); @@ -210,7 +213,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t zoe, invite, autoswapInstallationHandle, - instanceHandle, + instance, wallet, pursesStateChangeLog, inboxStateChangeLog, @@ -236,13 +239,13 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t const inviteIssuer = await E(zoe).getInvitationIssuer(); const zoeInvitePurse = await E(wallet).getPurse('Default Zoe invite purse'); const { - value: [{ handle: inviteHandle, installationHandle }], + value: [{ handle: inviteHandle, installation }], } = await E(inviteIssuer).getAmountOf(invite); const inviteHandleBoardId1 = await E(board).getId(inviteHandle); await wallet.deposit('Default Zoe invite purse', invite); - const instanceHandleBoardId = await E(board).getId(instanceHandle); - const installationHandleBoardId = await E(board).getId(installationHandle); + const instanceHandleBoardId = await E(board).getId(instance); + const installationHandleBoardId = await E(board).getId(installation); const formulateBasicOffer = (id, inviteHandleBoardId) => harden({ @@ -265,12 +268,13 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t // invite purse balance is rendered with petnames. We should see // unnamed first, then the petnames after those are suggested by // the dapp. - const { publicAPI } = await E(zoe).getInstanceRecord(instanceHandle); - const invite2 = await E(publicAPI).makeInvite(); + /** @type {{ makeInvitation(): Invitation }} */ + const publicAPI = await E(zoe).getPublicFacet(instance); + const invite2 = await E(publicAPI).makeInvitation(); const { value: [{ handle: inviteHandle2 }], } = await E(inviteIssuer).getAmountOf(invite2); - await wallet.deposit('Default Zoe invite purse', invite2); + wallet.deposit('Default Zoe invite purse', invite2); const currentAmount = await E(zoeInvitePurse).getCurrentAmount(); t.deepEquals( @@ -279,8 +283,8 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: inviteHandle2, - instanceHandle, - installationHandle, + instance, + installation, }, ], `a single invite in zoe purse`, @@ -300,13 +304,13 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: {}, - instanceHandle: {}, - installationHandle: {}, + instance: {}, + installation: {}, }, ], currentAmountSlots: { body: - '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', + '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instance":{"@qclass":"slot","index":2},"installation":{"@qclass":"slot","index":3}}]}', slots: [ { kind: 'brand', petname: 'zoe invite' }, { kind: 'unnamed', petname: 'unnamed-1' }, @@ -320,8 +324,8 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: { kind: 'unnamed', petname: 'unnamed-1' }, - instanceHandle: { kind: 'unnamed', petname: 'unnamed-2' }, - installationHandle: { kind: 'unnamed', petname: 'unnamed-3' }, + instance: { kind: 'unnamed', petname: 'unnamed-2' }, + installation: { kind: 'unnamed', petname: 'unnamed-3' }, }, ], }, @@ -329,7 +333,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t `zoeInvitePurseState with no names and invite2`, ); - const automaticRefundInstanceBoardId = await E(board).getId(instanceHandle); + const automaticRefundInstanceBoardId = await E(board).getId(instance); await wallet.suggestInstance( 'automaticRefund', automaticRefundInstanceBoardId, @@ -337,12 +341,12 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t t.equals( wallet.getInstance('automaticRefund'), - instanceHandle, - `automaticRefund instanceHandle stored in wallet`, + instance, + `automaticRefund instance stored in wallet`, ); const automaticRefundInstallationBoardId = await E(board).getId( - installationHandle, + installation, ); await wallet.suggestInstallation( 'automaticRefund', @@ -351,8 +355,8 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t t.equals( wallet.getInstallation('automaticRefund'), - installationHandle, - `automaticRefund installationHandle stored in wallet`, + installation, + `automaticRefund installation stored in wallet`, ); const autoswapInstallationBoardId = await E(board).getId( @@ -363,7 +367,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t t.equals( wallet.getInstallation('autoswap'), autoswapInstallationHandle, - `autoswap installationHandle stored in wallet`, + `autoswap installation stored in wallet`, ); const zoeInvitePurseState2 = JSON.parse( @@ -381,13 +385,13 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: {}, - instanceHandle: {}, - installationHandle: {}, + instance: {}, + installation: {}, }, ], currentAmountSlots: { body: - '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', + '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instance":{"@qclass":"slot","index":2},"installation":{"@qclass":"slot","index":3}}]}', slots: [ { kind: 'brand', petname: 'zoe invite' }, { kind: 'unnamed', petname: 'unnamed-4' }, @@ -401,8 +405,8 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: { kind: 'unnamed', petname: 'unnamed-4' }, - instanceHandle: { kind: 'instance', petname: 'automaticRefund' }, - installationHandle: { + instance: { kind: 'instance', petname: 'automaticRefund' }, + installation: { kind: 'installation', petname: 'automaticRefund', }, @@ -444,12 +448,12 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t `resuggesting a petname doesn't error`, ); - await wallet.renameInstallation('automaticRefund2', installationHandle); + await wallet.renameInstallation('automaticRefund2', installation); t.equals( wallet.getInstallation('automaticRefund2'), - installationHandle, - `automaticRefund installationHandle renamed in wallet`, + installation, + `automaticRefund installation renamed in wallet`, ); // We need this await for the pursesStateChangeLog to be updated @@ -462,8 +466,8 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: inviteHandle2, - instanceHandle, - installationHandle, + instance, + installation, }, ], `a single invite in zoe purse`, @@ -484,13 +488,13 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: inviteHandle2, - instanceHandle, - installationHandle, + instance, + installation: {}, }, ], currentAmountSlots: { body: - '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instanceHandle":{"@qclass":"slot","index":2},"installationHandle":{"@qclass":"slot","index":3}}]}', + '{"brand":{"@qclass":"slot","index":0},"value":[{"description":"getRefund","handle":{"@qclass":"slot","index":1},"instance":{"@qclass":"slot","index":2},"installation":{"@qclass":"slot","index":3}}]}', slots: [ { kind: 'brand', petname: 'zoe invite' }, { kind: 'unnamed', petname: 'unnamed-4' }, @@ -504,8 +508,8 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t { description: 'getRefund', handle: { kind: 'unnamed', petname: 'unnamed-4' }, - instanceHandle: { kind: 'instance', petname: 'automaticRefund' }, - installationHandle: { + instance: { kind: 'instance', petname: 'automaticRefund' }, + installation: { kind: 'installation', petname: 'automaticRefund', }, @@ -547,7 +551,7 @@ test('lib-wallet offer methods', async t => { invite, zoe, board, - instanceHandle, + instance, pursesStateChangeLog, inboxStateChangeLog, } = await setupTest(); @@ -561,12 +565,12 @@ test('lib-wallet offer methods', async t => { const inviteIssuer = await E(zoe).getInvitationIssuer(); const { - value: [{ handle: inviteHandle, installationHandle }], + value: [{ handle: inviteHandle, installation }], } = await E(inviteIssuer).getAmountOf(invite); const inviteHandleBoardId1 = await E(board).getId(inviteHandle); await wallet.deposit('Default Zoe invite purse', invite); - const instanceHandleBoardId = await E(board).getId(instanceHandle); - const installationHandleBoardId = await E(board).getId(installationHandle); + const instanceHandleBoardId = await E(board).getId(instance); + const installationHandleBoardId = await E(board).getId(installation); const formulateBasicOffer = (id, inviteHandleBoardId) => harden({ @@ -613,16 +617,14 @@ test('lib-wallet offer methods', async t => { ], `offer structure`, ); - const { outcome, depositedP } = await wallet.acceptOffer(id); + const accepted = await wallet.acceptOffer(id); + assert(accepted); + const { outcome, depositedP } = accepted; t.equals(await outcome, 'The offer was accepted', `offer was accepted`); await depositedP; - const offerHandles = wallet.getOfferHandles(harden([id])); - const offerHandle = wallet.getOfferHandle(id); - t.equals( - offerHandle, - offerHandles[0], - `both getOfferHandle(s) methods work`, - ); + const seats = wallet.getSeats(harden([id])); + const seat = wallet.getSeat(id); + t.equals(seat, seats[0], `both getSeat(s) methods work`); const moolaPurse = wallet.getPurse('Fun budget'); t.deepEquals( await moolaPurse.getCurrentAmount(), @@ -631,8 +633,9 @@ test('lib-wallet offer methods', async t => { ); const rawId2 = '1588645230204'; const id2 = `unknown#${rawId2}`; - const { publicAPI } = await E(zoe).getInstanceRecord(instanceHandle); - const invite2 = await E(publicAPI).makeInvite(); + /** @type {{ makeInvitation(): Invitation}} */ + const publicAPI = await E(zoe).getPublicFacet(instance); + const invite2 = await E(publicAPI).makeInvitation(); const { value: [{ handle: inviteHandle2 }], } = await E(inviteIssuer).getAmountOf(invite2); @@ -786,11 +789,8 @@ test('lib-wallet addOffer for autoswap swap', async t => { simoleanBundle.mint.mintPayment(simoleanBundle.amountMath.make(1000)), ); - const { publicAPI } = await E(zoe).getInstanceRecord( - autoswapInstanceHandle, - ); - - /** @type {Issuer} */ + /** @type {{ getLiquidityIssuer(): Issuer, makeSwapInvite(): Invitation }} */ + const publicAPI = await E(zoe).getPublicFacet(autoswapInstanceHandle); const liquidityIssuer = await E(publicAPI).getLiquidityIssuer(); /** @param {Issuer} issuer */ @@ -820,6 +820,8 @@ test('lib-wallet addOffer for autoswap swap', async t => { const moolaPurse = purses.get('Fun budget'); const simoleanPurse = purses.get('Nest egg'); + assert(moolaPurse); + assert(simoleanPurse); const moolaPayment = await E(moolaPurse).withdraw(proposal.give.TokenA); const simoleanPayment = await E(simoleanPurse).withdraw( @@ -830,12 +832,8 @@ test('lib-wallet addOffer for autoswap swap', async t => { TokenA: moolaPayment, TokenB: simoleanPayment, }); - const { outcome: addLiqOutcome } = await E(zoe).offer( - addLiquidityInvite, - proposal, - payments, - ); - await addLiqOutcome; + const liqSeat = await E(zoe).offer(addLiquidityInvite, proposal, payments); + await E(liqSeat).getOfferResult(); const invite = await E(publicAPI).makeSwapInvite(); const inviteIssuer = await E(zoe).getInvitationIssuer(); @@ -881,20 +879,18 @@ test('lib-wallet addOffer for autoswap swap', async t => { await wallet.addOffer(offer); - const { outcome, depositedP } = await wallet.acceptOffer(id); + const accepted = await wallet.acceptOffer(id); + assert(accepted); + const { outcome, depositedP } = accepted; t.equals( await outcome, 'Swap successfully completed.', `offer was accepted`, ); await depositedP; - const offerHandles = wallet.getOfferHandles(harden([id])); - const offerHandle = wallet.getOfferHandle(id); - t.equals( - offerHandle, - offerHandles[0], - `both getOfferHandle(s) methods work`, - ); + const seats = wallet.getSeats(harden([id])); + const seat = wallet.getSeat(id); + t.equals(seat, seats[0], `both getSeat(s) methods work`); t.deepEquals( await moolaPurse.getCurrentAmount(), moolaBundle.amountMath.make(70), diff --git a/packages/zoe/src/objArrayConversion.js b/packages/zoe/src/objArrayConversion.js index e8d6a004ec2..61c129e5f14 100644 --- a/packages/zoe/src/objArrayConversion.js +++ b/packages/zoe/src/objArrayConversion.js @@ -13,7 +13,7 @@ export const tuple = (...args) => args; /** * @template T - * @template {string | number} U + * @template {string | number | symbol} U * @param {T[]} array * @param {U[]} keys */ From 71560998dabbc379ba834ecf9dee21c0ad6f6ce9 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 10 Aug 2020 17:56:08 -0600 Subject: [PATCH 28/31] refactor: clear up some more types --- packages/zoe/src/types.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/zoe/src/types.js b/packages/zoe/src/types.js index f3f28d88328..f5c31dc7327 100644 --- a/packages/zoe/src/types.js +++ b/packages/zoe/src/types.js @@ -111,8 +111,8 @@ /** * @typedef {Object} MakeInstanceResult * @property {Record} creatorFacet - * @property {Instance} instance * @property {Record} publicFacet + * @property {Instance} instance * @property {Payment} creatorInvitation */ @@ -209,7 +209,7 @@ * * @property {() => IssuerRecord} getIssuerRecord * @property {(gains: AmountKeywordRecord, - * zcfSeat: ZCFSeat=, + * zcfSeat?: ZCFSeat, * ) => ZCFSeat} mintGains * All the amounts in gains must be of this ZCFMint's brand. * The gains' keywords are in the namespace of that seat. @@ -354,7 +354,7 @@ /** * @typedef {Object} ZCFSeat * @property {() => void} exit - * @property {(msg: string=) => never} kickOut + * @property {(msg?: string) => void} kickOut * @property {() => Notifier} getNotifier * @property {() => boolean} hasExited * @property {() => ProposalRecord} getProposal From eed785630a928deecf9c243ff3399ebca9e4b625 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 10 Aug 2020 19:02:41 -0600 Subject: [PATCH 29/31] fix(dapp-svelte-wallet): minor cleanups --- .../dapp-svelte-wallet/api/src/lib-wallet.js | 19 +++++++++---------- packages/dapp-svelte-wallet/ui/package.json | 9 +++------ .../ui/src/Transaction.svelte | 2 +- packages/dapp-svelte-wallet/ui/src/captp.js | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/dapp-svelte-wallet/api/src/lib-wallet.js b/packages/dapp-svelte-wallet/api/src/lib-wallet.js index 53f0863f599..56929b120c8 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/src/lib-wallet.js @@ -463,12 +463,6 @@ export async function makeWallet({ harden(paymentKeywordRecord), ); - // ===================== - // === AWAITING TURN === - // ===================== - // This settles when the offer hook completes. - const outcome = await E(seat).getOfferResult(); - // We'll resolve when deposited. const depositedP = E(seat) .getPayouts() @@ -676,7 +670,10 @@ export async function makeWallet({ }; const compileOffer = async offer => { - const { inviteHandleBoardId } = offer; + const { + inviteHandleBoardId, // Keep for backward-compatibility. + invitationHandleBoardId = inviteHandleBoardId, + } = offer; const { proposal, purseKeywordRecord } = compileProposal( offer.proposalTemplate, ); @@ -685,14 +682,16 @@ export async function makeWallet({ const { value: inviteValueElems } = await E( zoeInvitePurse, ).getCurrentAmount(); - const inviteHandle = await E(board).getValue(inviteHandleBoardId); + const inviteHandle = await E(board).getValue(invitationHandleBoardId); const matchInvite = element => element.handle === inviteHandle; const inviteBrand = purseToBrand.get(zoeInvitePurse); const { amountMath: inviteAmountMath } = brandTable.get(inviteBrand); const matchingInvite = inviteValueElems.find(matchInvite); assert( matchingInvite, - details`Cannot find invite corresponding to ${q(inviteHandleBoardId)}`, + details`Cannot find invite corresponding to ${q( + invitationHandleBoardId, + )}`, ); const inviteAmount = inviteAmountMath.make( harden([inviteValueElems.find(matchInvite)]), @@ -916,7 +915,7 @@ export async function makeWallet({ // The outcome is most often a string that can be returned, but // it could be an object. We don't do anything currently if it // is an object, but we will store it here for future use. - const outcome = E(seat).getOfferResult(); + const outcome = await E(seat).getOfferResult(); idToOutcome.set(id, outcome); ret = { outcome, depositedP }; diff --git a/packages/dapp-svelte-wallet/ui/package.json b/packages/dapp-svelte-wallet/ui/package.json index 681c4a38e9d..29deff3d87c 100644 --- a/packages/dapp-svelte-wallet/ui/package.json +++ b/packages/dapp-svelte-wallet/ui/package.json @@ -1,5 +1,5 @@ { - "name": "@agoric/wallet-frontend", + "name": "@agoric/dapp-svelte-wallet-ui", "version": "1.0.0", "scripts": { "build": "rollup -c", @@ -14,7 +14,7 @@ "@agoric/captp": "^1.3.3", "@rollup/plugin-commonjs": "^12.0.0", "@rollup/plugin-node-resolve": "^8.0.0", - "livereload-js": "file:livereload-js-v3.3.1.tgz", + "livereload-js": "https://github.com/agoric-labs/livereload-js", "rollup": "^2.3.4", "rollup-plugin-livereload": "^1.0.0", "rollup-plugin-svelte": "^5.0.3", @@ -28,8 +28,5 @@ }, "files": [ "public" - ], - "dependencies": { - "livereload-js": "https://github.com/agoric-labs/livereload-js" - } + ] } diff --git a/packages/dapp-svelte-wallet/ui/src/Transaction.svelte b/packages/dapp-svelte-wallet/ui/src/Transaction.svelte index 2b14f4da5ae..2d2bc98e4aa 100644 --- a/packages/dapp-svelte-wallet/ui/src/Transaction.svelte +++ b/packages/dapp-svelte-wallet/ui/src/Transaction.svelte @@ -17,7 +17,7 @@ if (!obj) { return; } - const { outcome } = obj; + let { outcome } = obj; if (typeof outcome !== 'string') { outcome = 'Offer was accepted.'; } diff --git a/packages/dapp-svelte-wallet/ui/src/captp.js b/packages/dapp-svelte-wallet/ui/src/captp.js index d13898296ed..813147d59f1 100644 --- a/packages/dapp-svelte-wallet/ui/src/captp.js +++ b/packages/dapp-svelte-wallet/ui/src/captp.js @@ -27,7 +27,7 @@ export function makeCapTPConnection(makeConnection, { onReset }) { // Stable identity for the connection handler. async function onOpen(event) { const { abort: ctpAbort, dispatch: ctpDispatch, getBootstrap } = - makeCapTP('@agoric/wallet-frontend', sendMessage); + makeCapTP('@agoric/dapp-svelte-wallet-ui', sendMessage); abort = ctpAbort; dispatch = ctpDispatch; From 24d69f69adc5505e76869d6037e7aea4a2ad36c6 Mon Sep 17 00:00:00 2001 From: Dean Tribble Date: Tue, 11 Aug 2020 21:18:32 -0700 Subject: [PATCH 30/31] test: initial stab at trying Ava test framework --- package.json | 20 + .../splitPayments/test-splitPayments.js | 6 +- .../mathHelpers/test-natMathHelpers.js | 200 ++-- .../mathHelpers/test-setMathHelpers.js | 152 ++- .../mathHelpers/test-strSetMathHelpers.js | 457 ++++---- .../ERTP/test/unitTests/test-issuerObj.js | 244 ++-- packages/ERTP/test/unitTests/test-mintObj.js | 107 +- .../test/metering/test-dynamic-vat-metered.js | 2 +- .../test-dynamic-vat-subcompartment.js | 2 +- .../metering/test-dynamic-vat-unmetered.js | 2 +- .../SwingSet/test/metering/test-metering.js | 18 +- .../SwingSet/test/metering/test-within-vat.js | 2 +- packages/SwingSet/test/test-comms.js | 38 +- packages/SwingSet/test/test-controller.js | 14 +- packages/SwingSet/test/test-demos-comms.js | 10 +- packages/SwingSet/test/test-demos.js | 6 +- packages/SwingSet/test/test-device-bridge.js | 6 +- packages/SwingSet/test/test-devices.js | 30 +- packages/SwingSet/test/test-exomessages.js | 20 +- packages/SwingSet/test/test-kernel.js | 34 +- packages/SwingSet/test/test-liveslots.js | 12 +- packages/SwingSet/test/test-marshal.js | 20 +- .../SwingSet/test/test-message-patterns.js | 6 +- packages/SwingSet/test/test-network.js | 100 +- packages/SwingSet/test/test-node-version.js | 6 +- packages/SwingSet/test/test-promises.js | 20 +- packages/SwingSet/test/test-queue-priority.js | 4 +- packages/SwingSet/test/test-state.js | 158 +-- packages/SwingSet/test/test-tildot.js | 4 +- packages/SwingSet/test/test-timer-device.js | 120 +- .../SwingSet/test/test-transcript-light.js | 6 +- packages/SwingSet/test/test-transcript.js | 6 +- packages/SwingSet/test/test-vattp.js | 18 +- packages/SwingSet/test/test-vpid-kernel.js | 42 +- packages/SwingSet/test/test-vpid-liveslots.js | 20 +- .../SwingSet/test/timer-device/test-device.js | 10 +- .../SwingSet/test/vat-admin/test-innerVat.js | 10 +- packages/SwingSet/test/workers/test-worker.js | 8 +- .../acorn-eventual-send/test/test-rollup.js | 8 +- packages/acorn-eventual-send/test/test.js | 2 +- .../integration-tests/test-workflow.js | 20 +- packages/agoric-cli/test/test-main.js | 14 +- packages/assert/test/test-assert.js | 22 +- packages/bundle-source/test/circular.js | 2 +- packages/bundle-source/test/sanity.js | 2 +- packages/bundle-source/test/test-comment.js | 16 +- .../bundle-source/test/test-external-fs.js | 8 +- .../bundle-source/test/tildot-transform.js | 2 +- packages/captp/test/crosstalk.js | 2 +- packages/captp/test/disco.js | 2 +- packages/captp/test/loopback.js | 2 +- packages/cosmic-swingset/test/test-home.js | 42 +- packages/cosmic-swingset/test/test-make.js | 8 +- .../test/test-scenario3-setup.js | 8 +- .../test/unitTests/test-lib-board.js | 20 +- .../test/unitTests/test-repl.js | 140 +-- .../api/test/test-lib-dehydrate.js | 50 +- .../api/test/test-lib-wallet.js | 84 +- .../contract/test/test-contract.js | 24 +- .../test/utility/test-bundler.js | 6 +- packages/eventual-send/test/test-e.js | 40 +- packages/eventual-send/test/test-hp.js | 8 +- packages/eventual-send/test/test-thenable.js | 14 +- packages/eventual-send/test/test.js | 2 +- .../test/test-compartment-wrapper.js | 28 +- .../import-bundle/test/test-import-bundle.js | 8 +- .../test/unitTests/test-importsA.js | 18 +- .../test-install-metering-and-ses.js | 12 +- packages/install-ses/test-install-ses.js | 8 +- packages/marshal/test/test-marshal.js | 82 +- .../notifier/test/test-notifier-adaptor.js | 32 +- packages/notifier/test/test-notifier.js | 42 +- .../test/unitTests/test-registrar.js | 30 +- .../sharingService/test-sharing.js | 14 +- .../test/unitTests/test-sharing.js | 18 +- .../contractHost/test-contractHost.js | 34 +- .../test/swingsetTests/escrow/test-escrow.js | 22 +- packages/spawner/test/test-function-bundle.js | 6 +- packages/swing-store-lmdb/test/test-state.js | 26 +- .../swing-store-simple/test/test-state.js | 28 +- packages/swingset-runner/test/test-demo.js | 8 +- packages/tame-metering/test/test-istamed.js | 10 +- packages/tame-metering/test/test-sanity.js | 8 +- .../test/test-transformer.js | 22 +- .../transform-metering/test/test-meter.js | 22 +- packages/transform-metering/test/test-tame.js | 12 +- .../transform-metering/test/test-transform.js | 10 +- .../transform-metering/test/test-zzz-eval.js | 44 +- .../brokenContracts/test-crashingContract.js | 30 +- .../zoe-metering/test-zoe-metering.js | 18 +- .../zoe/test/swingsetTests/zoe/test-zoe.js | 32 +- .../contractSupport/test-bondingCurves.js | 18 +- .../contractSupport/test-stateMachine.js | 10 +- .../contractSupport/test-zoeHelpers.js | 94 +- .../unitTests/contracts/test-atomicSwap.js | 60 +- .../contracts/test-automaticRefund.js | 54 +- .../test/unitTests/contracts/test-autoswap.js | 22 +- .../test/unitTests/contracts/test-barter.js | 10 +- .../contracts/test-brokenContract.js | 4 +- .../unitTests/contracts/test-coveredCall.js | 180 +-- .../unitTests/contracts/test-escrowToVote.js | 32 +- .../test/unitTests/contracts/test-grifter.js | 6 +- .../unitTests/contracts/test-mintPayments.js | 12 +- .../contracts/test-multipoolAutoswap.js | 48 +- .../unitTests/contracts/test-publicAuction.js | 114 +- .../unitTests/contracts/test-sellTickets.js | 62 +- .../contracts/test-simpleExchange.js | 32 +- .../test/unitTests/contracts/test-useObj.js | 8 +- .../zoe/test/unitTests/contracts/test-zcf.js | 4 +- .../zoe/test/unitTests/test-cleanProposal.js | 12 +- .../test/unitTests/test-objArrayConversion.js | 14 +- .../zoe/test/unitTests/test-offerSafety.js | 22 +- .../test/unitTests/test-rightsConservation.js | 8 +- test/explicit.js | 2 +- yarn.lock | 1015 ++++++++++++++++- 115 files changed, 2934 insertions(+), 2021 deletions(-) diff --git a/package.json b/package.json index 48faff4f999..358339f0ed2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "packages/notifier" ], "devDependencies": { + "ava": "^3.11.1", "eslint": "^6.8.0", "eslint-config-airbnb": "^18.0.1", "eslint-config-airbnb-base": "^14.0.0", @@ -70,5 +71,24 @@ }, "dependencies": { "@agoric/store": "^0.2.0" + }, + "ava": { + "files": [ + "packages/**/test/**/test-*" + ], + "concurrency": 12, + "failFast": false, + "failWithoutAssertions": false, + "environmentVariables": { + "MY_ENVIRONMENT_VARIABLE": "some value" + }, + "verbose": false, + "require": [ + "esm" + ], + "nodeArguments": [ + "--trace-deprecation", + "--napi-modules" + ] } } diff --git a/packages/ERTP/test/swingsetTests/splitPayments/test-splitPayments.js b/packages/ERTP/test/swingsetTests/splitPayments/test-splitPayments.js index cbb75c65fc3..77243a620a6 100644 --- a/packages/ERTP/test/swingsetTests/splitPayments/test-splitPayments.js +++ b/packages/ERTP/test/swingsetTests/splitPayments/test-splitPayments.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import { loadBasedir, buildVatController } from '@agoric/swingset-vat'; import path from 'path'; @@ -27,6 +27,6 @@ const expectedTapFaucetLog = [ test('test splitPayments', async t => { const dump = await main('splitPayments', ['splitPayments']); - t.deepEquals(dump.log, expectedTapFaucetLog); - t.end(); + t.deepEqual(dump.log, expectedTapFaucetLog); + return; // t.end(); }); diff --git a/packages/ERTP/test/unitTests/mathHelpers/test-natMathHelpers.js b/packages/ERTP/test/unitTests/mathHelpers/test-natMathHelpers.js index 08712b28629..4fbabfd6b06 100644 --- a/packages/ERTP/test/unitTests/mathHelpers/test-natMathHelpers.js +++ b/packages/ERTP/test/unitTests/mathHelpers/test-natMathHelpers.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeAmountMath from '../../../src/amountMath'; @@ -17,106 +17,100 @@ const mockBrand = harden({ const amountMath = makeAmountMath(mockBrand, 'nat'); test('natMathHelpers', t => { - try { - const { - getBrand, - getMathHelpersName, - make, - coerce, - getValue, - getEmpty, - isEmpty, - isGTE, - isEqual, - add, - subtract, - } = amountMath; - - // getBrand - t.deepEquals(getBrand(), mockBrand, 'brand is brand'); - - // getMathHelpersName - t.deepEquals(getMathHelpersName(), 'nat', 'mathHelpersName is nat'); - - // make - t.deepEquals(make(4), { brand: mockBrand, value: 4 }); - t.throws( - () => make('abc'), - /RangeError: not a safe integer/, - `'abc' is not a nat`, - ); - t.throws(() => make(-1), /RangeError: negative/, `- 1 is not a valid Nat`); - - // coerce - t.deepEquals( - coerce(harden({ brand: mockBrand, value: 4 })), - { - brand: mockBrand, - value: 4, - }, - `coerce can take an amount`, - ); - t.throws( - () => - coerce( - harden({ brand: { getAllegedName: () => 'somename' }, value: 4 }), - ), - /the brand in the allegedAmount in 'coerce' didn't match the amountMath brand/, - `coerce can't take the wrong brand`, - ); - t.throws( - () => coerce(3), - /alleged brand is undefined/, - `coerce needs a brand`, - ); - - // getValue - t.equals(getValue(make(4)), 4); - - // getEmpty - t.deepEquals(getEmpty(), make(0), `empty is 0`); - - // isEmpty - t.ok(isEmpty({ brand: mockBrand, value: 0 }), `isEmpty(0) is true`); - t.notOk(isEmpty({ brand: mockBrand, value: 6 }), `isEmpty(6) is false`); - t.ok(isEmpty(make(0)), `isEmpty(0) is true`); - t.notOk(isEmpty(make(6)), `isEmpty(6) is false`); - t.throws( - () => isEmpty('abc'), - /alleged brand is undefined/, - `isEmpty('abc') throws because it cannot be coerced`, - ); - t.throws( - () => isEmpty({ brand: mockBrand, value: 'abc' }), - /RangeError: not a safe integer/, - `isEmpty('abc') throws because it cannot be coerced`, - ); - t.throws( - () => isEmpty(0), - /alleged brand is undefined/, - `isEmpty(0) throws because it cannot be coerced`, - ); - - // isGTE - t.ok(isGTE(make(5), make(3)), `5 >= 3`); - t.ok(isGTE(make(3), make(3)), `3 >= 3`); - t.notOk( - isGTE({ brand: mockBrand, value: 3 }, { brand: mockBrand, value: 4 }), - `3 < 4`, - ); - - // isEqual - t.ok(isEqual(make(4), make(4)), `4 equals 4`); - t.notOk(isEqual(make(4), make(5)), `4 does not equal 5`); - - // add - t.deepEquals(add(make(5), make(9)), make(14), `5 + 9 = 14`); - - // subtract - t.deepEquals(subtract(make(6), make(1)), make(5), `6 - 1 = 5`); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + const { + getBrand, + getMathHelpersName, + make, + coerce, + getValue, + getEmpty, + isEmpty, + isGTE, + isEqual, + add, + subtract, + } = amountMath; + + // getBrand + t.deepEqual(getBrand(), mockBrand, 'brand is brand'); + + // getMathHelpersName + t.deepEqual(getMathHelpersName(), 'nat', 'mathHelpersName is nat'); + + // make + t.deepEqual(make(4), { brand: mockBrand, value: 4 }); + t.throws( + () => make('abc'), + { instanceOf: RangeError, message: 'not a safe integer' }, + `'abc' is not a nat`, + ); + t.throws(() => make(-1), { instanceOf: RangeError, message: 'negative' }, `- 1 is not a valid Nat`); + + // coerce + t.deepEqual( + coerce(harden({ brand: mockBrand, value: 4 })), + { + brand: mockBrand, + value: 4, + }, + `coerce can take an amount`, + ); + t.throws( + () => + coerce( + harden({ brand: { getAllegedName: () => 'somename' }, value: 4 }), + ), + { message: /the brand in the allegedAmount in 'coerce' didn't match the amountMath brand/ }, + `coerce can't take the wrong brand`, + ); + t.throws( + () => coerce(3), + { message: /alleged brand is undefined/ }, + `coerce needs a brand`, + ); + + // getValue + t.is(getValue(make(4)), 4); + + // getEmpty + t.deepEqual(getEmpty(), make(0), `empty is 0`); + + // isEmpty + t.assert(isEmpty({ brand: mockBrand, value: 0 }), `isEmpty(0) is true`); + t.falsy(isEmpty({ brand: mockBrand, value: 6 }), `isEmpty(6) is false`); + t.assert(isEmpty(make(0)), `isEmpty(0) is true`); + t.falsy(isEmpty(make(6)), `isEmpty(6) is false`); + t.throws( + () => isEmpty('abc'), + { message: /alleged brand is undefined/ }, + `isEmpty('abc') throws because it cannot be coerced`, + ); + t.throws( + () => isEmpty({ brand: mockBrand, value: 'abc' }), + { instanceOf: RangeError, message: 'not a safe integer' }, + `isEmpty('abc') throws because it cannot be coerced`, + ); + t.throws( + () => isEmpty(0), + { message: /alleged brand is undefined/ }, + `isEmpty(0) throws because it cannot be coerced`, + ); + + // isGTE + t.assert(isGTE(make(5), make(3)), `5 >= 3`); + t.assert(isGTE(make(3), make(3)), `3 >= 3`); + t.falsy( + isGTE({ brand: mockBrand, value: 3 }, { brand: mockBrand, value: 4 }), + `3 < 4`, + ); + + // isEqual + t.assert(isEqual(make(4), make(4)), `4 equals 4`); + t.falsy(isEqual(make(4), make(5)), `4 does not equal 5`); + + // add + t.deepEqual(add(make(5), make(9)), make(14), `5 + 9 = 14`); + + // subtract + t.deepEqual(subtract(make(6), make(1)), make(5), `6 - 1 = 5`); }); diff --git a/packages/ERTP/test/unitTests/mathHelpers/test-setMathHelpers.js b/packages/ERTP/test/unitTests/mathHelpers/test-setMathHelpers.js index b849c07b1b6..bffa560ea27 100644 --- a/packages/ERTP/test/unitTests/mathHelpers/test-setMathHelpers.js +++ b/packages/ERTP/test/unitTests/mathHelpers/test-setMathHelpers.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeAmountMath from '../../../src/amountMath'; @@ -36,124 +36,124 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { } = amountMath; // getBrand - t.deepEquals(getBrand(), mockBrand, 'brand is brand'); + t.deepEqual(getBrand(), mockBrand, 'brand is brand'); // getMathHelpersName - t.deepEquals(getMathHelpersName(), 'set', 'mathHelpersName is set'); + t.deepEqual(getMathHelpersName(), 'set', 'mathHelpersName is set'); // make - t.deepEquals( + t.deepEqual( make(harden([a])), { brand: mockBrand, value: [a] }, `[a] is a valid set`, ); - t.deepEquals( + t.deepEqual( make(harden([a, b])), { brand: mockBrand, value: [a, b] }, `[a, b] is a valid set`, ); - t.deepEquals( + t.deepEqual( make(harden([])), { brand: mockBrand, value: [] }, `[] is a valid set`, ); t.throws( () => make(harden([a, a])), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in make should throw`, ); - t.deepEquals( + t.deepEqual( make(harden(['a', 'b'])), { brand: mockBrand, value: ['a', 'b'] }, 'anything comparable is a valid element', ); t.throws( () => make(harden('a')), - /list must be an array/, + { message: /list must be an array/ }, 'strings are not valid', ); if (a2 !== undefined) { t.throws( () => make(harden([a, a2])), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } // coerce - t.deepEquals( + t.deepEqual( coerce(harden({ brand: mockBrand, value: [a] })), harden({ brand: mockBrand, value: [a] }), `[a] is a valid set`, ); - t.deepEquals( + t.deepEqual( coerce(harden({ brand: mockBrand, value: [a, b] })), harden({ brand: mockBrand, value: [a, b] }), `[a, b] is a valid set`, ); - t.deepEquals( + t.deepEqual( coerce(harden({ brand: mockBrand, value: [] })), harden({ brand: mockBrand, value: [] }), `[] is a valid set`, ); t.throws( () => coerce(make(harden([a, a]))), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in coerce should throw`, ); - t.deepEquals( + t.deepEqual( coerce(make(harden(['a', 'b']))), { brand: mockBrand, value: ['a', 'b'] }, 'anything comparable is a valid element', ); t.throws( () => coerce(harden({ brand: mockBrand, value: 'a' })), - /list must be an array/, + { message: /list must be an array/ }, 'strings are not valid', ); if (a2 !== undefined) { t.throws( () => coerce(harden({ brand: mockBrand, value: [a, a2] })), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } // getValue - t.deepEquals( + t.deepEqual( getValue(harden({ brand: mockBrand, value: [a] })), [a], `getValue of make([a]) is [a]`, ); // getEmpty - t.deepEquals( + t.deepEqual( getEmpty(), harden({ brand: mockBrand, value: [] }), `empty is []`, ); // isEmpty - t.ok(isEmpty(make(harden([]))), `isEmpty([]) is true`); + t.assert(isEmpty(make(harden([]))), `isEmpty([]) is true`); t.throws( () => isEmpty(harden({ brand: mockBrand, value: {} })), - /list must be an array/, + { message: /list must be an array/ }, `isEmpty({}) throws`, ); - t.notOk(isEmpty(make(harden(['abc']))), `isEmpty(['abc']) is false`); - t.notOk(isEmpty(make(harden([a]))), `isEmpty([a]) is false`); + t.falsy(isEmpty(make(harden(['abc']))), `isEmpty(['abc']) is false`); + t.falsy(isEmpty(make(harden([a]))), `isEmpty([a]) is false`); t.throws( () => isEmpty(harden({ brand: mockBrand, value: [a, a] })), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in value in isEmpty throw because of coercion`, ); - t.ok(isEmpty(make(harden([]))), `isEmpty([]) is true`); - t.notOk(isEmpty(make(harden(['abc']))), `isEmpty(['abc']) is false`); - t.notOk(isEmpty(make(harden([a]))), `isEmpty([a]) is false`); + t.assert(isEmpty(make(harden([]))), `isEmpty([]) is true`); + t.falsy(isEmpty(make(harden(['abc']))), `isEmpty(['abc']) is false`); + t.falsy(isEmpty(make(harden([a]))), `isEmpty([a]) is false`); if (a2 !== undefined) { t.throws( () => isEmpty(harden({ brand: mockBrand, value: [a, a2] })), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } @@ -165,7 +165,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a] }), harden({ brand: mockBrand, value: [b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in the left of isGTE should throw`, ); t.throws( @@ -174,24 +174,24 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [b, b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in the right of isGTE should throw`, ); - t.ok( + t.assert( isGTE( harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [a] }), ), `overlap between left and right of isGTE should not throw`, ); - t.ok( + t.assert( isGTE( harden({ brand: mockBrand, value: [a, b] }), harden({ brand: mockBrand, value: [b] }), ), '[a, b] is GTE [b]', ); - t.notOk( + t.falsy( isGTE( harden({ brand: mockBrand, value: [b] }), harden({ brand: mockBrand, value: [b, a] }), @@ -205,7 +205,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a2] }), harden({ brand: mockBrand, value: [b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } @@ -217,7 +217,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a] }), harden({ brand: mockBrand, value: [a] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in left of isEqual should throw`, ); t.throws( @@ -226,24 +226,24 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [a, a] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in right of isEqual should throw`, ); - t.ok( + t.assert( isEqual( harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [a] }), ), `overlap between left and right of isEqual is ok`, ); - t.ok( + t.assert( isEqual( harden({ brand: mockBrand, value: [b, a, c] }), harden({ brand: mockBrand, value: [a, c, b] }), ), `order doesn't matter`, ); - t.notOk( + t.falsy( isEqual( harden({ brand: mockBrand, value: [b, c] }), harden({ brand: mockBrand, value: [b, a] }), @@ -257,7 +257,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a2] }), harden({ brand: mockBrand, value: [a] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } @@ -269,7 +269,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a] }), harden({ brand: mockBrand, value: [b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in left of add should throw`, ); t.throws( @@ -278,7 +278,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [b, b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in right of add should throw`, ); t.throws( @@ -287,10 +287,10 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [a] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `overlap between left and right of add should throw`, ); - t.deepEquals( + t.deepEqual( add( harden({ brand: mockBrand, value: [] }), harden({ brand: mockBrand, value: [b, c] }), @@ -298,7 +298,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [b, c] }), `anything + identity stays same`, ); - t.deepEquals( + t.deepEqual( add( harden({ brand: mockBrand, value: [b, c] }), harden({ brand: mockBrand, value: [] }), @@ -313,7 +313,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a2] }), harden({ brand: mockBrand, value: [b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } @@ -325,7 +325,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a] }), harden({ brand: mockBrand, value: [b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in left of subtract should throw`, ); t.throws( @@ -334,10 +334,10 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [b, b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `duplicates in right of subtract should throw`, ); - t.deepEquals( + t.deepEqual( subtract( harden({ brand: mockBrand, value: [a] }), harden({ brand: mockBrand, value: [a] }), @@ -351,10 +351,10 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, b] }), harden({ brand: mockBrand, value: [c] }), ), - /was not in left/, + { message: /was not in left/ }, `elements in right but not in left of subtract should throw`, ); - t.deepEquals( + t.deepEqual( subtract( harden({ brand: mockBrand, value: [b, c] }), harden({ brand: mockBrand, value: [] }), @@ -362,7 +362,7 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [b, c] }), `anything - identity stays same`, ); - t.deepEquals( + t.deepEqual( subtract( harden({ brand: mockBrand, value: [b, c] }), harden({ brand: mockBrand, value: [b] }), @@ -377,54 +377,36 @@ const runSetMathHelpersTests = (t, [a, b, c], a2 = undefined) => { harden({ brand: mockBrand, value: [a, a2] }), harden({ brand: mockBrand, value: [b] }), ), - /value has duplicates/, + { message: /value has duplicates/ }, `data identity throws`, ); } }; test('setMathHelpers with handles', t => { - try { - const a = harden({}); - const b = harden({}); - const c = harden({}); + const a = harden({}); + const b = harden({}); + const c = harden({}); - runSetMathHelpersTests(t, harden([a, b, c])); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + runSetMathHelpersTests(t, harden([a, b, c])); }); test('setMathHelpers with basic objects', t => { - try { - const a = harden({ name: 'a' }); - const b = harden({ name: 'b' }); - const c = harden({ name: 'c' }); + const a = harden({ name: 'a' }); + const b = harden({ name: 'b' }); + const c = harden({ name: 'c' }); - const a2 = harden({ ...a }); + const a2 = harden({ ...a }); - runSetMathHelpersTests(t, harden([a, b, c]), a2); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + runSetMathHelpersTests(t, harden([a, b, c]), a2); }); test('setMathHelpers with complex objects', t => { - try { - const a = { handle: {}, instanceHandle: {}, name: 'a' }; - const b = { handle: {}, instanceHandle: a.instanceHandle, name: 'b' }; - const c = { handle: {}, instanceHandle: {}, name: 'c' }; + const a = { handle: {}, instanceHandle: {}, name: 'a' }; + const b = { handle: {}, instanceHandle: a.instanceHandle, name: 'b' }; + const c = { handle: {}, instanceHandle: {}, name: 'c' }; - const a2 = harden({ ...a }); + const a2 = harden({ ...a }); - runSetMathHelpersTests(t, harden([a, b, c]), a2); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + runSetMathHelpersTests(t, harden([a, b, c]), a2); }); diff --git a/packages/ERTP/test/unitTests/mathHelpers/test-strSetMathHelpers.js b/packages/ERTP/test/unitTests/mathHelpers/test-strSetMathHelpers.js index 3699a7547ef..71b74cac86e 100644 --- a/packages/ERTP/test/unitTests/mathHelpers/test-strSetMathHelpers.js +++ b/packages/ERTP/test/unitTests/mathHelpers/test-strSetMathHelpers.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeAmountMath from '../../../src/amountMath'; @@ -17,265 +17,260 @@ const mockBrand = harden({ const amountMath = makeAmountMath(mockBrand, 'strSet'); test('strSetMathHelpers', t => { - try { - const { - getBrand, - getMathHelpersName, - make, - coerce, - getValue, - getEmpty, - isEmpty, - isGTE, - isEqual, - add, - subtract, - } = amountMath; + const { + getBrand, + getMathHelpersName, + make, + coerce, + getValue, + getEmpty, + isEmpty, + isGTE, + isEqual, + add, + subtract, + } = amountMath; - // getBrand - t.deepEquals(getBrand(), mockBrand, 'brand is brand'); + // getBrand + t.deepEqual(getBrand(), mockBrand, 'brand is brand'); - // getMathHelpersName - t.deepEquals(getMathHelpersName(), 'strSet', 'mathHelpersName is strSet'); + // getMathHelpersName + t.deepEqual(getMathHelpersName(), 'strSet', 'mathHelpersName is strSet'); - // make - t.doesNotThrow( - () => make(harden(['1'])), - undefined, - `['1'] is a valid string array`, - ); - t.throws( - () => make(4), - /value must be an array/, - `4 is not a valid string array`, - ); - t.throws( - () => make(harden([6])), - /must be a string/, - `[6] is not a valid string array`, - ); - t.throws( - () => make('abc'), - /value must be an array/, - `'abc' is not a valid string array`, - ); - t.throws( - () => make(harden(['a', 'a'])), - /value has duplicates/, - `duplicates in make throw`, - ); + // make + t.notThrows( + () => make(harden(['1'])), + `['1'] is a valid string array`, + ); + t.throws( + () => make(4), + { message: /value must be an array/ }, + `4 is not a valid string array`, + ); + t.throws( + () => make(harden([6])), + { message: /must be a string/ }, + `[6] is not a valid string array`, + ); + t.throws( + () => make('abc'), + { message: /value must be an array/ }, + `'abc' is not a valid string array`, + ); + t.throws( + () => make(harden(['a', 'a'])), + { message: /value has duplicates/ }, + `duplicates in make throw`, + ); - // coerce - t.deepEquals( - coerce(harden({ brand: mockBrand, value: ['1'] })), - harden({ brand: mockBrand, value: ['1'] }), - `coerce({ brand, value: ['1']}) is a valid amount`, - ); - t.throws( - () => coerce(harden({ brand: mockBrand, value: [6] })), - /must be a string/, - `[6] is not a valid string array`, - ); - t.throws( - () => coerce(harden({ brand: mockBrand, value: '6' })), - /value must be an array/, - `'6' is not a valid array`, - ); - t.throws( - () => coerce(harden({ brand: mockBrand, value: ['a', 'a'] })), - /value has duplicates/, - `duplicates should throw`, - ); + // coerce + t.deepEqual( + coerce(harden({ brand: mockBrand, value: ['1'] })), + harden({ brand: mockBrand, value: ['1'] }), + `coerce({ brand, value: ['1']}) is a valid amount`, + ); + t.throws( + () => coerce(harden({ brand: mockBrand, value: [6] })), + { message: /must be a string/ }, + `[6] is not a valid string array`, + ); + t.throws( + () => coerce(harden({ brand: mockBrand, value: '6' })), + { message: /value must be an array/ }, + `'6' is not a valid array`, + ); + t.throws( + () => coerce(harden({ brand: mockBrand, value: ['a', 'a'] })), + { message: /value has duplicates/ }, + `duplicates should throw`, + ); - // getValue - t.deepEquals(getValue(harden({ brand: mockBrand, value: ['1'] })), ['1']); - t.deepEquals(getValue(make(harden(['1']))), ['1']); + // getValue + t.deepEqual(getValue(harden({ brand: mockBrand, value: ['1'] })), ['1']); + t.deepEqual(getValue(make(harden(['1']))), ['1']); - // getEmpty - t.deepEquals( - getEmpty(), - harden({ brand: mockBrand, value: [] }), - `empty is []`, - ); + // getEmpty + t.deepEqual( + getEmpty(), + harden({ brand: mockBrand, value: [] }), + `empty is []`, + ); - t.ok( - isEmpty(harden({ brand: mockBrand, value: [] })), - `isEmpty([]) is true`, - ); - t.notOk( - isEmpty(harden({ brand: mockBrand, value: ['abc'] })), - `isEmpty(['abc']) is false`, - ); - t.throws( - () => isEmpty(harden({ brand: mockBrand, value: ['a', 'a'] })), - /value has duplicates/, - `duplicates in isEmpty throw because coerce throws`, - ); + t.assert( + isEmpty(harden({ brand: mockBrand, value: [] })), + `isEmpty([]) is true`, + ); + t.falsy( + isEmpty(harden({ brand: mockBrand, value: ['abc'] })), + `isEmpty(['abc']) is false`, + ); + t.throws( + () => isEmpty(harden({ brand: mockBrand, value: ['a', 'a'] })), + { message: /value has duplicates/ }, + `duplicates in isEmpty throw because coerce throws`, + ); - // isGTE - t.throws( - () => - isGTE( - harden({ brand: mockBrand, value: ['a', 'a'] }), - harden({ brand: mockBrand, value: ['b'] }), - ), - `duplicates in the left of isGTE should throw`, - ); - t.throws( - () => - isGTE( - harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['b', 'b'] }), - ), - `duplicates in the right of isGTE should throw`, - ); - t.ok( + // isGTE + t.throws( + () => isGTE( - harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['a'] }), - ), - `overlap between left and right of isGTE should not throw`, - ); - t.ok( - isGTE( - harden({ brand: mockBrand, value: ['a', 'b'] }), - harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['a', 'a'] }), + harden({ brand: mockBrand, value: ['b'] }), ), - `['a', 'b'] is gte to ['a']`, - ); - t.notOk( + null, + `duplicates in the left of isGTE should throw`, + ); + t.throws( + () => isGTE( harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['b'] }), + harden({ brand: mockBrand, value: ['b', 'b'] }), ), - `['a'] is not gte to ['b']`, - ); + null, + `duplicates in the right of isGTE should throw`, + ); + t.assert( + isGTE( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['a'] }), + ), + `overlap between left and right of isGTE should not throw`, + ); + t.assert( + isGTE( + harden({ brand: mockBrand, value: ['a', 'b'] }), + harden({ brand: mockBrand, value: ['a'] }), + ), + `['a', 'b'] is gte to ['a']` , + ); + t.falsy( + isGTE( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['b'] }), + ), + `['a'] is not gte to ['b']` , + ); - // isEqual - t.throws( - () => - isEqual( - harden({ brand: mockBrand, value: ['a', 'a'] }), - harden({ brand: mockBrand, value: ['a'] }), - ), - /value has duplicates/, - `duplicates in left of isEqual should throw`, - ); - t.throws( - () => - isEqual( - harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['a', 'a'] }), - ), - /value has duplicates/, - `duplicates in right of isEqual should throw`, - ); - t.ok( + // isEqual + t.throws( + () => isEqual( + harden({ brand: mockBrand, value: ['a', 'a'] }), harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['a'] }), - ), - `overlap between left and right of isEqual is ok`, - ); - t.ok( - isEqual( - harden({ brand: mockBrand, value: ['a', 'b'] }), - harden({ brand: mockBrand, value: ['b', 'a'] }), ), - `['a', 'b'] equals ['b', 'a']`, - ); - t.notOk( + { message: /value has duplicates/ }, + `duplicates in left of isEqual should throw`, + ); + t.throws( + () => isEqual( harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['b'] }), + harden({ brand: mockBrand, value: ['a', 'a'] }), ), - `['a'] does not equal ['b']`, - ); + { message: /value has duplicates/ }, + `duplicates in right of isEqual should throw`, + ); + t.assert( + isEqual( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['a'] }), + ), + `overlap between left and right of isEqual is ok`, + ); + t.assert( + isEqual( + harden({ brand: mockBrand, value: ['a', 'b'] }), + harden({ brand: mockBrand, value: ['b', 'a'] }), + ), + `['a', 'b'] equals ['b', 'a']`, + ); + t.falsy( + isEqual( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['b'] }), + ), + `['a'] does not equal ['b']`, + ); - // add - t.throws( - () => - add( - harden({ brand: mockBrand, value: ['a', 'a'] }), - harden({ brand: mockBrand, value: ['b'] }), - ), - /value has duplicates/, - `duplicates in left of add should throw`, - ); - t.throws( - () => - add( - harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['b', 'b'] }), - ), - /value has duplicates/, - `duplicates in right of add should throw`, - ); - t.throws( - () => - add( - harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['a'] }), - ), - /left and right have same element/, - `overlap between left and right of add should throw`, - ); - t.deepEquals( + // add + t.throws( + () => add( - harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['a', 'a'] }), harden({ brand: mockBrand, value: ['b'] }), ), - harden({ brand: mockBrand, value: ['a', 'b'] }), - `['a'] + ['b'] = ['a', 'b']`, - ); + { message: /value has duplicates/ }, + `duplicates in left of add should throw`, + ); + t.throws( + () => + add( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['b', 'b'] }), + ), + { message: /value has duplicates/ }, + `duplicates in right of add should throw`, + ); + t.throws( + () => + add( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['a'] }), + ), + { message: /left and right have same element/ }, + `overlap between left and right of add should throw`, + ); + t.deepEqual( + add( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['b'] }), + ), + harden({ brand: mockBrand, value: ['a', 'b'] }), + `['a'] + ['b'] = ['a', 'b']`, + ); - // subtract - t.throws( - () => - subtract( - harden({ brand: mockBrand, value: ['a', 'a'] }), - harden({ brand: mockBrand, value: ['b'] }), - ), - /value has duplicates/, - `duplicates in left of subtract should throw`, - ); - t.throws( - () => - subtract( - harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['b', 'b'] }), - ), - /value has duplicates/, - `duplicates in right of subtract should throw`, - ); - t.deepEquals( + // subtract + t.throws( + () => + subtract( + harden({ brand: mockBrand, value: ['a', 'a'] }), + harden({ brand: mockBrand, value: ['b'] }), + ), + { message: /value has duplicates/ }, + `duplicates in left of subtract should throw`, + ); + t.throws( + () => subtract( harden({ brand: mockBrand, value: ['a'] }), - harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['b', 'b'] }), ), - harden({ brand: mockBrand, value: [] }), - `overlap between left and right of subtract should not throw`, - ); - t.throws( - () => - subtract( - harden({ brand: mockBrand, value: ['a', 'b'] }), - harden({ brand: mockBrand, value: ['c'] }), - ), - /some of the elements in right .* were not present in left/, - `elements in right but not in left of subtract should throw`, - ); - t.deepEquals( + { message: /value has duplicates/ }, + `duplicates in right of subtract should throw`, + ); + t.deepEqual( + subtract( + harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['a'] }), + ), + harden({ brand: mockBrand, value: [] }), + `overlap between left and right of subtract should not throw`, + ); + t.throws( + () => subtract( harden({ brand: mockBrand, value: ['a', 'b'] }), - harden({ brand: mockBrand, value: ['a'] }), + harden({ brand: mockBrand, value: ['c'] }), ), - harden({ brand: mockBrand, value: ['b'] }), - `['a', 'b'] - ['a'] = ['a']`, - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + { message: /some of the elements in right .* were not present in left/ }, + `elements in right but not in left of subtract should throw`, + ); + t.deepEqual( + subtract( + harden({ brand: mockBrand, value: ['a', 'b'] }), + harden({ brand: mockBrand, value: ['a'] }), + ), + harden({ brand: mockBrand, value: ['b'] }), + `['a', 'b'] - ['a'] = ['a']`, + ); }); diff --git a/packages/ERTP/test/unitTests/test-issuerObj.js b/packages/ERTP/test/unitTests/test-issuerObj.js index 134d1abf6d8..83fb6279e3e 100644 --- a/packages/ERTP/test/unitTests/test-issuerObj.js +++ b/packages/ERTP/test/unitTests/test-issuerObj.js @@ -1,79 +1,67 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import test from 'tape-promise/tape'; +import test from 'ava'; import { E } from '@agoric/eventual-send'; import makeIssuerKit from '../../src/issuer'; test('issuer.getBrand, brand.isMyIssuer', t => { - try { + t.notThrows(() => { const { issuer, brand } = makeIssuerKit('fungible'); const myBrand = issuer.getBrand(); - t.ok(myBrand.isMyIssuer(issuer)); - t.equals( + t.assert(myBrand.isMyIssuer(issuer)); + t.is( brand, myBrand, 'brand returned from `makeIssuerKit` and from `getBrand` the same', ); - t.equals(issuer.getAllegedName(), myBrand.getAllegedName()); - t.equals(issuer.getAllegedName(), 'fungible'); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + t.is(issuer.getAllegedName(), myBrand.getAllegedName()); + t.is(issuer.getAllegedName(), 'fungible'); + }); }); test('issuer.getAmountMath', t => { - try { + t.notThrows(() => { const { issuer, amountMath, brand } = makeIssuerKit('fungible'); - t.equals(issuer.getAmountMath(), amountMath); + t.is(issuer.getAmountMath(), amountMath); const fungible = amountMath.make; - t.ok( + t.assert( amountMath.isEqual( amountMath.add(fungible(100), fungible(50)), fungible(150), ), ); - t.equals(fungible(4000).value, 4000); - t.equals(fungible(0).brand, brand); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + t.is(fungible(4000).value, 4000); + t.is(fungible(0).brand, brand); + }); }); test('issuer.getMathHelpersName', t => { - try { + t.notThrows(() => { const { issuer } = makeIssuerKit('fungible'); - t.equals(issuer.getMathHelpersName(), 'nat'); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + t.is(issuer.getMathHelpersName(), 'nat'); + }); }); test('issuer.makeEmptyPurse', t => { - t.plan(6); + t.plan(7); const { issuer, mint, amountMath, brand } = makeIssuerKit('fungible'); const purse = issuer.makeEmptyPurse(); const payment = mint.mintPayment(amountMath.make(837)); - t.ok( + t.assert( amountMath.isEqual(purse.getCurrentAmount(), amountMath.getEmpty()), `empty purse is empty`, ); - t.equals(purse.getAllegedBrand(), brand, `purse's brand is correct`); + t.is(purse.getAllegedBrand(), brand, `purse's brand is correct`); const fungible837 = amountMath.make(837); const checkDeposit = newPurseBalance => { - t.ok( + t.assert( amountMath.isEqual(newPurseBalance, fungible837), `the balance returned is the purse balance`, ); - t.ok( + t.assert( amountMath.isEqual(purse.getCurrentAmount(), fungible837), `the new purse balance is the payment's old balance`, ); @@ -83,23 +71,23 @@ test('issuer.makeEmptyPurse', t => { const checkWithdrawal = newPayment => { issuer.getAmountOf(newPayment).then(amount => { - t.ok( + t.assert( amountMath.isEqual(amount, fungible837), `the withdrawn payment has the right balance`, ); }); - t.ok( + t.assert( amountMath.isEqual(purse.getCurrentAmount(), amountMath.getEmpty()), `the purse is empty again`, ); }; - E(purse) - .deposit(payment, fungible837) - .then(checkDeposit) - .then(performWithdrawal) - .then(checkWithdrawal) - .catch(e => t.assert(false, e)); + return t.notThrowsAsync(() => + E(purse) + .deposit(payment, fungible837) + .then(checkDeposit) + .then(performWithdrawal) + .then(checkWithdrawal)); }); test('purse.deposit', async t => { @@ -119,11 +107,11 @@ test('purse.deposit', async t => { expectedNewBalance, ) => depositResult => { const delta = amountMath.subtract(expectedNewBalance, expectedOldBalance); - t.ok( + t.assert( amountMath.isEqual(depositResult, delta), `the balance changes by the deposited amount: ${delta.value}`, ); - t.ok( + t.assert( amountMath.isEqual(purse.getCurrentAmount(), expectedNewBalance), `the new purse balance ${depositResult.value} is the expected amount: ${expectedNewBalance.value}`, ); @@ -146,9 +134,9 @@ test('purse.deposit promise', t => { const payment = mint.mintPayment(fungible25); const exclusivePaymentP = E(issuer).claim(payment); - t.rejects( + return t.throwsAsync( () => E(purse).deposit(exclusivePaymentP, fungible25), - /deposit does not accept promises/, + { message: /deposit does not accept promises/ }, 'failed to reject a promise for a payment', ); }); @@ -162,17 +150,17 @@ test('purse.makeDepositFacet', t => { const payment = mint.mintPayment(fungible25); const checkDeposit = newPurseBalance => { - t.ok( + t.assert( amountMath.isEqual(newPurseBalance, fungible25), `the balance returned is the purse balance`, ); - t.ok( + t.assert( amountMath.isEqual(purse.getCurrentAmount(), fungible25), `the new purse balance is the payment's old balance`, ); }; - E(purse) + return E(purse) .makeDepositFacet() .then(({ receive }) => receive(payment)) .then(checkDeposit); @@ -183,126 +171,112 @@ test('issuer.burn', t => { const { issuer, mint, amountMath } = makeIssuerKit('fungible'); const payment1 = mint.mintPayment(amountMath.make(837)); - E(issuer) + return E(issuer) .burn(payment1, amountMath.make(837)) .then(burntBalance => { - t.ok( + t.assert( amountMath.isEqual(burntBalance, amountMath.make(837)), `entire minted payment was burnt`, ); - t.rejects(() => issuer.getAmountOf(payment1), /payment not found/); + return t.throwsAsync(() => issuer.getAmountOf(payment1), { message: /payment not found/ }); }) .catch(e => t.assert(false, e)); }); -test('issuer.claim', t => { +test('issuer.claim', async t => { t.plan(3); const { issuer, amountMath, mint } = makeIssuerKit('fungible'); const payment1 = mint.mintPayment(amountMath.make(2)); - E(issuer) + return E(issuer) .claim(payment1, amountMath.make(2)) - .then(newPayment1 => { - issuer.getAmountOf(newPayment1).then(amount => { - t.ok( + .then(async newPayment1 => { + await issuer.getAmountOf(newPayment1).then(amount => { + t.assert( amountMath.isEqual(amount, amountMath.make(2)), `new payment has equal balance to old payment`, ); - t.notEqual( + t.not( newPayment1, payment1, `old payment is different than new payment`, ); }); - t.rejects(() => issuer.getAmountOf(payment1), /payment not found/); + return t.throwsAsync(() => issuer.getAmountOf(payment1), { message: /payment not found/}); }); }); test('issuer.splitMany bad amount', t => { - try { - const { mint, issuer, amountMath } = makeIssuerKit('fungible'); - const payment = mint.mintPayment(amountMath.make(1000)); - const badAmounts = Array(2).fill(amountMath.make(10)); - t.rejects( - _ => E(issuer).splitMany(payment, badAmounts), - /rights were not conserved/, - 'successfully throw if rights are not conserved in proposed new payments', - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + const { mint, issuer, amountMath } = makeIssuerKit('fungible'); + const payment = mint.mintPayment(amountMath.make(1000)); + const badAmounts = Array(2).fill(amountMath.make(10)); + return t.throwsAsync( + _ => E(issuer).splitMany(payment, badAmounts), + { message: /rights were not conserved/ }, + 'successfully throw if rights are not conserved in proposed new payments', + ); }); -test('issuer.splitMany good amount', t => { +test('issuer.splitMany good amount', async t => { t.plan(11); const { mint, issuer, amountMath } = makeIssuerKit('fungible'); const oldPayment = mint.mintPayment(amountMath.make(100)); const goodAmounts = Array(10).fill(amountMath.make(10)); - const checkPayments = splitPayments => { + const checkPayments = async splitPayments => { for (const payment of splitPayments) { - issuer.getAmountOf(payment).then(amount => { - t.deepEqual( - amount, - amountMath.make(10), - `split payment has right balance`, - ); - }); + const amount = await issuer.getAmountOf(payment); + t.deepEqual( + amount, + amountMath.make(10), + `split payment has right balance`, + ); } - t.rejects( + await t.throwsAsync( () => issuer.getAmountOf(oldPayment), - /payment not found/, + { message: /payment not found/ }, `oldPayment no longer exists`, ); }; - E(issuer) + return E(issuer) .splitMany(oldPayment, goodAmounts) .then(checkPayments); }); test('issuer.split bad amount', t => { - try { - const { mint, issuer, amountMath } = makeIssuerKit('fungible'); - const { amountMath: otherUnitOps } = makeIssuerKit('other fungible'); - const payment = mint.mintPayment(amountMath.make(1000)); - t.rejects( - _ => E(issuer).split(payment, otherUnitOps.make(10)), - /the brand in the allegedAmount in 'coerce' didn't match the amountMath brand/, - 'throws for bad amount', - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + const { mint, issuer, amountMath } = makeIssuerKit('fungible'); + const { amountMath: otherUnitOps } = makeIssuerKit('other fungible'); + const payment = mint.mintPayment(amountMath.make(1000)); + return t.throwsAsync( + _ => E(issuer).split(payment, otherUnitOps.make(10)), + { message: /the brand in the allegedAmount in 'coerce' didn't match the amountMath brand/ }, + 'throws for bad amount', + ); }); -test('issuer.split good amount', t => { +test('issuer.split good amount', async t => { t.plan(3); const { mint, issuer, amountMath } = makeIssuerKit('fungible'); const oldPayment = mint.mintPayment(amountMath.make(20)); - const checkPayments = splitPayments => { + const checkPayments = async splitPayments => { for (const payment of splitPayments) { - issuer.getAmountOf(payment).then(amount => { - t.deepEqual( - amount, - amountMath.make(10), - `split payment has right balance`, - ); - }); + const amount = await issuer.getAmountOf(payment); + t.deepEqual( + amount, + amountMath.make(10), + `split payment has right balance`, + ); } - t.rejects( + await t.throwsAsync( () => E(issuer).getAmountOf(oldPayment), - /payment not found/, + { message: /payment not found/ }, `oldPayment no longer exists`, ); }; - E(issuer) + return E(issuer) .split(oldPayment, amountMath.make(10)) .then(checkPayments); }); @@ -315,26 +289,24 @@ test('issuer.combine good payments', t => { payments.push(mint.mintPayment(amountMath.make(1))); } - const checkCombinedPayment = combinedPayment => { - issuer.getAmountOf(combinedPayment).then(amount => { - t.deepEqual( - amount, - amountMath.make(100), - `combined payment equal to the original payments total`, + const checkCombinedPayment = async combinedPayment => { + const amount = await issuer.getAmountOf(combinedPayment); + t.deepEqual( + amount, + amountMath.make(100), + `combined payment equal to the original payments total`, + ); + for (const payment of payments) { + await t.throwsAsync( + () => issuer.getAmountOf(payment), + { message: /payment not found/ }, + `original payments no longer exist`, ); - for (const payment of payments) { - t.rejects( - () => issuer.getAmountOf(payment), - /payment not found/, - `original payments no longer exist`, - ); - } - }); + } }; - E(issuer) + return E(issuer) .combine(payments) - .then(checkCombinedPayment) - .catch(e => t.assert(false, e)); + .then(checkCombinedPayment); }); test('issuer.combine array of promises', t => { @@ -349,18 +321,18 @@ test('issuer.combine array of promises', t => { const checkCombinedResult = paymentP => { issuer.getAmountOf(paymentP).then(pAmount => { - t.equals(pAmount.value, 100); + t.is(pAmount.value, 100); }); }; - E(issuer) + return E(issuer) .combine(paymentsP) .then(checkCombinedResult) .catch(e => t.assert(false, e)); }); -test('issuer.combine bad payments', t => { - try { +test('issuer.combine bad payments', async t => { + return t.notThrowsAsync(() => { const { mint, issuer, amountMath } = makeIssuerKit('fungible'); const { mint: otherMint, amountMath: otherAmountMath } = makeIssuerKit( 'other fungible', @@ -372,14 +344,10 @@ test('issuer.combine bad payments', t => { const otherPayment = otherMint.mintPayment(otherAmountMath.make(10)); payments.push(otherPayment); - t.rejects( + return t.throwsAsync( () => E(issuer).combine(payments), - /"payment" not found/, + { message: /"payment" not found/ }, 'payment from other mint is not found', ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + }); }); diff --git a/packages/ERTP/test/unitTests/test-mintObj.js b/packages/ERTP/test/unitTests/test-mintObj.js index aabb78b7bd7..02ff729b74a 100644 --- a/packages/ERTP/test/unitTests/test-mintObj.js +++ b/packages/ERTP/test/unitTests/test-mintObj.js @@ -1,80 +1,63 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeIssuerKit from '../../src/issuer'; test('mint.getIssuer', t => { - try { - const { mint, issuer } = makeIssuerKit('fungible'); - t.equals(mint.getIssuer(), issuer); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } + const { mint, issuer } = makeIssuerKit('fungible'); + t.is(mint.getIssuer(), issuer); }); -test('mint.mintPayment default natMathHelper', t => { - t.plan(2); +test('mint.mintPayment default natMathHelper', async t => { const { mint, issuer, amountMath } = makeIssuerKit('fungible'); const fungible1000 = amountMath.make(1000); const payment1 = mint.mintPayment(fungible1000); - issuer.getAmountOf(payment1).then(paymentBalance1 => { - t.ok(amountMath.isEqual(paymentBalance1, fungible1000)); - }); + const paymentBalance1 = await issuer.getAmountOf(payment1); + t.assert(amountMath.isEqual(paymentBalance1, fungible1000)); const payment2 = mint.mintPayment(amountMath.make(1000)); - issuer.getAmountOf(payment2).then(paymentBalance2 => { - t.ok(amountMath.isEqual(paymentBalance2, fungible1000)); - }); + const paymentBalance2 = await issuer.getAmountOf(payment2); + t.assert(amountMath.isEqual(paymentBalance2, fungible1000)); }); -test('mint.mintPayment strSetMathHelpers', t => { - t.plan(2); +test('mint.mintPayment strSetMathHelpers', async t => { const { mint, issuer, amountMath } = makeIssuerKit('items', 'strSet'); const items1and2and4 = amountMath.make(harden(['1', '2', '4'])); const payment1 = mint.mintPayment(items1and2and4); - issuer.getAmountOf(payment1).then(paymentBalance1 => { - t.ok(amountMath.isEqual(paymentBalance1, items1and2and4)); - }); + const paymentBalance1 = await issuer.getAmountOf(payment1); + t.assert(amountMath.isEqual(paymentBalance1, items1and2and4)); const items5and6 = amountMath.make(harden(['5', '6'])); const payment2 = mint.mintPayment(items5and6); - issuer.getAmountOf(payment2).then(paymentBalance2 => { - t.ok(amountMath.isEqual(paymentBalance2, items5and6)); - }); + const paymentBalance2 = await issuer.getAmountOf(payment2); + t.assert(amountMath.isEqual(paymentBalance2, items5and6)); }); -test('mint.mintPayment setMathHelpers', t => { - t.plan(3); +test('mint.mintPayment setMathHelpers', async t => { const { mint, issuer, amountMath } = makeIssuerKit('items', 'set'); const item1handle = {}; const item2handle = {}; const item3handle = {}; const items1and2 = amountMath.make(harden([item1handle, item2handle])); const payment1 = mint.mintPayment(items1and2); - issuer.getAmountOf(payment1).then(paymentBalance1 => { - t.ok(amountMath.isEqual(paymentBalance1, items1and2)); - }); + const paymentBalance1 = await issuer.getAmountOf(payment1); + t.assert(amountMath.isEqual(paymentBalance1, items1and2)); const item3 = amountMath.make(harden([item3handle])); const payment2 = mint.mintPayment(item3); - issuer.getAmountOf(payment2).then(paymentBalance2 => { - t.ok(amountMath.isEqual(paymentBalance2, item3)); - }); + const paymentBalance2 = await issuer.getAmountOf(payment2); + t.assert(amountMath.isEqual(paymentBalance2, item3)); // TODO: prevent reminting the same non-fungible amounts // https://github.com/Agoric/agoric-sdk/issues/552 const payment3 = mint.mintPayment(item3); - issuer.getAmountOf(payment3).then(paymentBalance3 => { - t.ok(amountMath.isEqual(paymentBalance3, item3)); - }); + const paymentBalance3 = await issuer.getAmountOf(payment3); + t.assert(amountMath.isEqual(paymentBalance3, item3)); }); -test('mint.mintPayment setMathHelpers with invites', t => { - t.plan(2); +test('mint.mintPayment setMathHelpers with invites', async t => { const { mint, issuer, amountMath } = makeIssuerKit('items', 'set'); const instanceHandle1 = {}; const invite1Value = { handle: {}, instanceHandle: instanceHandle1 }; @@ -82,20 +65,18 @@ test('mint.mintPayment setMathHelpers with invites', t => { const invite3Value = { handle: {}, instanceHandle: {} }; const invites1and2 = amountMath.make(harden([invite1Value, invite2Value])); const payment1 = mint.mintPayment(invites1and2); - issuer.getAmountOf(payment1).then(paymentBalance1 => { - t.ok(amountMath.isEqual(paymentBalance1, invites1and2)); - }); + const paymentBalance1 = await issuer.getAmountOf(payment1); + t.assert(amountMath.isEqual(paymentBalance1, invites1and2)); const invite3 = amountMath.make(harden([invite3Value])); const payment2 = mint.mintPayment(invite3); - issuer.getAmountOf(payment2).then(paymentBalance2 => { - t.ok(amountMath.isEqual(paymentBalance2, invite3)); - }); + const paymentBalance2 = await issuer.getAmountOf(payment2); + t.assert(amountMath.isEqual(paymentBalance2, invite3)); }); // Tests related to non-fungible tokens // This test models ballet tickets -test('non-fungible tokens example', t => { +test('non-fungible tokens example', async t => { t.plan(11); const { mint: balletTicketMint, @@ -131,31 +112,25 @@ test('non-fungible tokens example', t => { // ALICE SIDE // Alice bought ticket 1 and has access to the balletTicketIssuer, because it's public - balletTicketIssuer.claim(paymentForAlice).then(myTicketPaymentAlice => { + const myTicketPaymentAlice = await balletTicketIssuer.claim(paymentForAlice); // the call to claim() hasn't thrown, so Alice knows myTicketPaymentAlice // is a genuine 'Agoric Ballet Opera tickets' payment and she has exclusive access // to its handle - balletTicketIssuer - .getAmountOf(myTicketPaymentAlice) - .then(paymentAmountAlice => { - t.equals(paymentAmountAlice.value.length, 1); - t.equals(paymentAmountAlice.value[0].seat, 1); - t.equals(paymentAmountAlice.value[0].show, 'The Sofa'); - t.equals(paymentAmountAlice.value[0].start, startDateString); - }); - }); + const paymentAmountAlice = await balletTicketIssuer.getAmountOf(myTicketPaymentAlice); + t.is(paymentAmountAlice.value.length, 1); + t.is(paymentAmountAlice.value[0].seat, 1); + t.is(paymentAmountAlice.value[0].show, 'The Sofa'); + t.is(paymentAmountAlice.value[0].start, startDateString); // BOB SIDE // Bob bought ticket 3 and 4 and has access to the balletTicketIssuer, because it's public - balletTicketIssuer.claim(paymentForBob).then(bobTicketPayment => { - balletTicketIssuer.getAmountOf(bobTicketPayment).then(paymentAmountBob => { - t.equals(paymentAmountBob.value.length, 2); - t.equals(paymentAmountBob.value[0].seat, 3); - t.equals(paymentAmountBob.value[1].seat, 4); - t.equals(paymentAmountBob.value[0].show, 'The Sofa'); - t.equals(paymentAmountBob.value[1].show, 'The Sofa'); - t.equals(paymentAmountBob.value[0].start, startDateString); - t.equals(paymentAmountBob.value[1].start, startDateString); - }); - }); + const bobTicketPayment = await balletTicketIssuer.claim(paymentForBob); + const paymentAmountBob = await balletTicketIssuer.getAmountOf(bobTicketPayment); + t.is(paymentAmountBob.value.length, 2); + t.is(paymentAmountBob.value[0].seat, 3); + t.is(paymentAmountBob.value[1].seat, 4); + t.is(paymentAmountBob.value[0].show, 'The Sofa'); + t.is(paymentAmountBob.value[1].show, 'The Sofa'); + t.is(paymentAmountBob.value[0].start, startDateString); + t.is(paymentAmountBob.value[1].start, startDateString); }); diff --git a/packages/SwingSet/test/metering/test-dynamic-vat-metered.js b/packages/SwingSet/test/metering/test-dynamic-vat-metered.js index aa82f2a5b03..97243a28b1c 100644 --- a/packages/SwingSet/test/metering/test-dynamic-vat-metered.js +++ b/packages/SwingSet/test/metering/test-dynamic-vat-metered.js @@ -74,5 +74,5 @@ tap.test('metering dynamic vats', async t => { 'stay dead', ); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js b/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js index 4780d030510..2f9083ef93e 100644 --- a/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js +++ b/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js @@ -97,5 +97,5 @@ tap.test('metering dynamic vat which imports bundle', async t => { 'whole dynamic vat is dead', ); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js b/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js index 077747ee92b..f3ad0870e73 100644 --- a/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js +++ b/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js @@ -67,5 +67,5 @@ tap.test('unmetered dynamic vat', async t => { await c.run(); t.deepEqual(nextLog(), ['failed to explode'], 'metering disabled'); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/metering/test-metering.js b/packages/SwingSet/test/metering/test-metering.js index 62b5c2c9f62..aa562dbe8ce 100644 --- a/packages/SwingSet/test/metering/test-metering.js +++ b/packages/SwingSet/test/metering/test-metering.js @@ -106,18 +106,18 @@ tap.test('metering a single bundle', async function testSingleBundle(t) { let ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); t.deepEqual(log2, ['started', 'done'], 'computation completed'); log2.splice(0); - t.equal(ok, true, 'meter should not be exhausted'); + t.is(ok, true, 'meter should not be exhausted'); ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'compute')); t.deepEqual(log2, ['started'], 'computation started but halted'); log2.splice(0); - t.equal(ok, false, 'meter should be exhausted (compute)'); + t.is(ok, false, 'meter should be exhausted (compute)'); // Run the same code (without an infinite loop) against the old exhausted // meter. It should halt right away. ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); - t.equal(log2.length, 0, 'computation did not start'); - t.equal(ok, false, 'meter should be exhausted (still compute)'); + t.is(log2.length, 0, 'computation did not start'); + t.is(ok, false, 'meter should be exhausted (still compute)'); // Refill the meter, and the code should run again. // refillFacet.combined(10000000); @@ -126,13 +126,13 @@ tap.test('metering a single bundle', async function testSingleBundle(t) { ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); t.deepEqual(log2, ['started', 'done'], 'computation completed'); log2.splice(0); - t.equal(ok, true, 'meter should not be exhausted'); + t.is(ok, true, 'meter should not be exhausted'); // now check that metering catches infinite stack ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'stack')); t.deepEqual(log2, ['started'], 'computation started but halted'); log2.splice(0); - t.equal(ok, false, 'meter should be exhausted (stack)'); + t.is(ok, false, 'meter should be exhausted (stack)'); // Refill the meter, and the code should run again. // refillFacet.combined(10000000); @@ -141,18 +141,18 @@ tap.test('metering a single bundle', async function testSingleBundle(t) { ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); t.deepEqual(log2, ['started', 'done'], 'computation completed'); log2.splice(0); - t.equal(ok, true, 'meter should not be exhausted'); + t.is(ok, true, 'meter should not be exhausted'); // metering should catch primordial allocation too ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'allocate')); t.deepEqual(log2, ['started'], 'computation started but halted'); log2.splice(0); - t.equal(ok, false, 'meter should be exhausted (allocate)'); + t.is(ok, false, 'meter should be exhausted (allocate)'); // Refill the meter, and the code should run again. refillFacet.allocate(10000000); ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); t.deepEqual(log2, ['started', 'done'], 'computation completed'); log2.splice(0); - t.equal(ok, true, 'meter should not be exhausted'); + t.is(ok, true, 'meter should not be exhausted'); }); diff --git a/packages/SwingSet/test/metering/test-within-vat.js b/packages/SwingSet/test/metering/test-within-vat.js index bc9644262ae..d8f129cce20 100644 --- a/packages/SwingSet/test/metering/test-within-vat.js +++ b/packages/SwingSet/test/metering/test-within-vat.js @@ -144,5 +144,5 @@ tap.test('metering within a vat', async t => { 'compute meter refilled', ); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-comms.js b/packages/SwingSet/test/test-comms.js index 13cc962b51f..e48cfdd37d0 100644 --- a/packages/SwingSet/test/test-comms.js +++ b/packages/SwingSet/test/test-comms.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import buildCommsDispatch from '../src/vats/comms'; import { flipRemoteSlot } from '../src/vats/comms/parseRemoteSlot'; import { makeState } from '../src/vats/comms/state'; @@ -17,14 +17,14 @@ import { debugState } from '../src/vats/comms/dispatch'; test('mapOutbound', t => { const s = makeState(); const { remoteID } = addRemote(s, 'remote1', 'o-1'); - t.equal(mapOutbound(s, remoteID, 'o-4'), 'ro-20'); - t.equal(mapOutbound(s, remoteID, 'o-4'), 'ro-20'); - t.equal(mapOutbound(s, remoteID, 'o-5'), 'ro-21'); + t.is(mapOutbound(s, remoteID, 'o-4'), 'ro-20'); + t.is(mapOutbound(s, remoteID, 'o-4'), 'ro-20'); + t.is(mapOutbound(s, remoteID, 'o-5'), 'ro-21'); t.throws( () => mapOutbound(s, remoteID, 'o+5'), - /sending non-remote object o\+5 to remote machine/, + { message: /sending non-remote object o\+5 to remote machine/ }, ); - t.end(); + return; // t.end(); }); function mockSyscall() { @@ -62,7 +62,7 @@ test('transmit', t => { // now tell the comms vat to send a message to a remote machine, the // equivalent of bob!foo() d.deliver(bob, 'foo', capdata('argsbytes', []), null); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ transmitterID, 'transmit', encodeArgs('deliver:ro+23:foo:;argsbytes'), @@ -70,16 +70,16 @@ test('transmit', t => { // bob!bar(alice, bob) d.deliver(bob, 'bar', capdata('argsbytes', [alice, bob]), null); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ transmitterID, 'transmit', encodeArgs('deliver:ro+23:bar::ro-20:ro+23;argsbytes'), ]); // the outbound ro-20 should match an inbound ro+20, both represent 'alice' - t.equal(getInbound(state, remoteID, 'ro+20'), alice); + t.is(getInbound(state, remoteID, 'ro+20'), alice); // do it again, should use same values d.deliver(bob, 'bar', capdata('argsbytes', [alice, bob]), null); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ transmitterID, 'transmit', encodeArgs('deliver:ro+23:bar::ro-20:ro+23;argsbytes'), @@ -88,13 +88,13 @@ test('transmit', t => { // bob!cat(alice, bob, ayana) const ayana = 'o-11'; d.deliver(bob, 'cat', capdata('argsbytes', [alice, bob, ayana]), null); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ transmitterID, 'transmit', encodeArgs('deliver:ro+23:cat::ro-20:ro+23:ro-21;argsbytes'), ]); - t.end(); + return; // t.end(); }); test('receive', t => { @@ -108,7 +108,7 @@ test('receive', t => { const bob = 'o-10'; const { remoteID, receiverID } = addRemote(state, 'remote1', transmitterID); const remoteBob = flipRemoteSlot(mapOutbound(state, remoteID, bob)); - t.equal(remoteBob, 'ro+20'); + t.is(remoteBob, 'ro+20'); // now pretend the transport layer received a message from remote1, as if // the remote machine had performed bob!foo() @@ -118,7 +118,7 @@ test('receive', t => { encodeArgs(`deliver:${remoteBob}:foo:;argsbytes`), null, ); - t.deepEquals(sends.shift(), [bob, 'foo', capdata('argsbytes')]); + t.deepEqual(sends.shift(), [bob, 'foo', capdata('argsbytes')]); // bob!bar(alice, bob) d.deliver( @@ -127,13 +127,13 @@ test('receive', t => { encodeArgs(`deliver:${remoteBob}:bar::ro-20:${remoteBob};argsbytes`), null, ); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ bob, 'bar', capdata('argsbytes', ['o+11', bob]), ]); // if we were to send o+11, the other side should get ro+20, which is alice - t.equal(getOutbound(state, remoteID, 'o+11'), 'ro+20'); + t.is(getOutbound(state, remoteID, 'o+11'), 'ro+20'); // bob!bar(alice, bob) d.deliver( @@ -142,7 +142,7 @@ test('receive', t => { encodeArgs(`deliver:${remoteBob}:bar::ro-20:${remoteBob};argsbytes`), null, ); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ bob, 'bar', capdata('argsbytes', ['o+11', bob]), @@ -155,11 +155,11 @@ test('receive', t => { encodeArgs(`deliver:${remoteBob}:cat::ro-20:${remoteBob}:ro-21;argsbytes`), null, ); - t.deepEquals(sends.shift(), [ + t.deepEqual(sends.shift(), [ bob, 'cat', capdata('argsbytes', ['o+11', bob, 'o+12']), ]); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-controller.js b/packages/SwingSet/test/test-controller.js index c651019125d..86d1a9150a8 100644 --- a/packages/SwingSet/test/test-controller.js +++ b/packages/SwingSet/test/test-controller.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; import { buildVatController, loadBasedir } from '../src/index'; import { checkKT } from './util'; @@ -24,8 +24,8 @@ test('load empty', async t => { const config = {}; const controller = await buildVatController(config); await controller.run(); - t.ok(true); - t.end(); + t.assert(true); + return; // t.end(); }); async function simpleCall(t) { @@ -77,9 +77,9 @@ async function simpleCall(t) { }); controller.log('2'); - t.equal(controller.dump().log[1], '2'); + t.is(controller.dump().log[1], '2'); - t.end(); + return; // t.end(); } test('simple call', async t => { @@ -95,7 +95,7 @@ test('bootstrap', async t => { // left[0].bootstrap const c = await buildVatController(config); t.deepEqual(c.dump().log, ['bootstrap called']); - t.end(); + return; // t.end(); }); test('bootstrap export', async t => { @@ -260,5 +260,5 @@ test('bootstrap export', async t => { checkKT(t, c, kt); t.deepEqual(c.dump().runQueue, []); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-demos-comms.js b/packages/SwingSet/test/test-demos-comms.js index 676127964a7..4c8ac76dd44 100644 --- a/packages/SwingSet/test/test-demos-comms.js +++ b/packages/SwingSet/test/test-demos-comms.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { loadBasedir, buildVatController } from '../src/index'; async function main(basedir, argv) { @@ -28,12 +28,12 @@ const encouragementBotCommsGolden = [ test('run encouragementBotComms Demo', async t => { const dump = await main('demo/encouragementBotComms', []); - t.deepEquals(dump.log, encouragementBotCommsGolden); - t.end(); + t.deepEqual(dump.log, encouragementBotCommsGolden); + return; // t.end(); }); test('run encouragementBotCommsWavyDot Demo', async t => { const dump = await main('demo/encouragementBotCommsWavyDot', []); - t.deepEquals(dump.log, encouragementBotCommsGolden); - t.end(); + t.deepEqual(dump.log, encouragementBotCommsGolden); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-demos.js b/packages/SwingSet/test/test-demos.js index 9c385edb3f8..dfc0f651d75 100644 --- a/packages/SwingSet/test/test-demos.js +++ b/packages/SwingSet/test/test-demos.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { loadBasedir, buildVatController } from '../src/index'; async function main(basedir, argv) { @@ -21,6 +21,6 @@ const encouragementBotGolden = [ test('run encouragementBot Demo', async t => { const dump = await main('demo/encouragementBot', []); - t.deepEquals(dump.log, encouragementBotGolden); - t.end(); + t.deepEqual(dump.log, encouragementBotGolden); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-device-bridge.js b/packages/SwingSet/test/test-device-bridge.js index eecf8c76d93..05a6abb3f8e 100644 --- a/packages/SwingSet/test/test-device-bridge.js +++ b/packages/SwingSet/test/test-device-bridge.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { initSwingStore } from '@agoric/swing-store-simple'; import { buildVatController, buildBridge } from '../src/index'; @@ -106,7 +106,7 @@ test('bridge device', async t => { JSON.stringify([inboundArg2, inboundArg3]), ]); - t.end(); + return; // t.end(); }); test('bridge device can return undefined', async t => { @@ -140,5 +140,5 @@ test('bridge device can return undefined', async t => { t.deepEqual(outboundLog, argv); t.deepEqual(c.dump().log, ['outbound retval', '', 'true']); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-devices.js b/packages/SwingSet/test/test-devices.js index 690c5ff2d1a..e8fb0931ba3 100644 --- a/packages/SwingSet/test/test-devices.js +++ b/packages/SwingSet/test/test-devices.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { initSwingStore, getAllState } from '@agoric/swing-store-simple'; import { buildVatController } from '../src/index'; @@ -53,7 +53,7 @@ test('d0', async t => { 'd-70', 'd-71', ]); - t.end(); + return; // t.end(); }); test('d1', async t => { @@ -86,7 +86,7 @@ test('d1', async t => { JSON.stringify(capargs({ ret: 3 })), ]); t.deepEqual(sharedArray, ['pushed']); - t.end(); + return; // t.end(); }); async function test2(t, mode) { @@ -150,7 +150,7 @@ async function test2(t, mode) { 'ret done', ]); } - t.end(); + return; // t.end(); } test('d2.1', async t => { @@ -197,7 +197,7 @@ test('device state', async t => { t.deepEqual(JSON.parse(s[`${d3}.deviceState`]), capargs({ s: 'new' })); t.deepEqual(JSON.parse(s[`${d3}.o.nextID`]), 10); - t.end(); + return; // t.end(); }); test('mailbox outbound', async t => { @@ -237,7 +237,7 @@ test('mailbox outbound', async t => { s2.populateFromData(s.exportToData()); t.deepEqual(s.exportToData(), s2.exportToData()); - t.end(); + return; // t.end(); }); test('mailbox inbound', async t => { @@ -265,7 +265,7 @@ test('mailbox inbound', async t => { ], 0, ); - t.ok(rc); + t.assert(rc); await c.run(); t.deepEqual(c.dump().log, ['dm-peer1', 'm-1-msg1', 'm-2-msg2']); @@ -278,7 +278,7 @@ test('mailbox inbound', async t => { ], 3, ); - t.ok(rc); + t.assert(rc); await c.run(); t.deepEqual(c.dump().log, ['dm-peer1', 'm-1-msg1', 'm-2-msg2', 'da-peer1-3']); @@ -291,7 +291,7 @@ test('mailbox inbound', async t => { ], 3, ); - t.notOk(rc); + t.falsy(rc); await c.run(); t.deepEqual(c.dump().log, ['dm-peer1', 'm-1-msg1', 'm-2-msg2', 'da-peer1-3']); @@ -305,7 +305,7 @@ test('mailbox inbound', async t => { ], 3, ); - t.ok(rc); + t.assert(rc); await c.run(); t.deepEqual(c.dump().log, [ 'dm-peer1', @@ -326,7 +326,7 @@ test('mailbox inbound', async t => { ], 4, ); - t.ok(rc); + t.assert(rc); await c.run(); t.deepEqual(c.dump().log, [ 'dm-peer1', @@ -339,7 +339,7 @@ test('mailbox inbound', async t => { ]); rc = mb.deliverInbound('peer2', [[4, 'msg4']], 5); - t.ok(rc); + t.assert(rc); await c.run(); t.deepEqual(c.dump().log, [ 'dm-peer1', @@ -354,7 +354,7 @@ test('mailbox inbound', async t => { 'da-peer2-5', ]); - t.end(); + return; // t.end(); }); test('command broadcast', async t => { @@ -374,7 +374,7 @@ test('command broadcast', async t => { await c.run(); t.deepEqual(broadcasts, [{ hello: 'everybody' }]); - t.end(); + return; // t.end(); }); test('command deliver', async t => { @@ -409,5 +409,5 @@ test('command deliver', async t => { t.deepEqual(c.dump().log, ['handle-0-missing', 'handle-1-errory']); t.deepEqual(rejection, { response: 'body' }); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-exomessages.js b/packages/SwingSet/test/test-exomessages.js index cc09b04f85d..f6c978397c1 100644 --- a/packages/SwingSet/test/test-exomessages.js +++ b/packages/SwingSet/test/test-exomessages.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { buildVatController } from '../src/index'; async function beginning(t, mode) { @@ -12,19 +12,19 @@ async function beginning(t, mode) { }, }; const controller = await buildVatController(config, [mode]); - t.equal(controller.bootstrapResult.status(), 'pending'); + t.is(controller.bootstrapResult.status(), 'pending'); return controller; } async function bootstrapSuccessfully(t, mode, body, slots) { const controller = await beginning(t, mode); await controller.run(); - t.equal(controller.bootstrapResult.status(), 'fulfilled'); + t.is(controller.bootstrapResult.status(), 'fulfilled'); t.deepEqual(controller.bootstrapResult.resolution(), { body, slots, }); - t.end(); + return; // t.end(); } test('bootstrap returns data', async t => { @@ -57,15 +57,15 @@ async function testFailure(t) { await controller.run(); } catch (e) { failureHappened = true; - t.equal(e.message, 'kernel panic bootstrap failure'); + t.is(e.message, 'kernel panic bootstrap failure'); } - t.ok(failureHappened); - t.equal(controller.bootstrapResult.status(), 'rejected'); + t.assert(failureHappened); + t.is(controller.bootstrapResult.status(), 'rejected'); t.deepEqual(controller.bootstrapResult.resolution(), { body: '{"@qclass":"error","name":"Error","message":"gratuitous error"}', slots: [], }); - t.end(); + return; // t.end(); } test('bootstrap failure', async t => { @@ -84,12 +84,12 @@ async function extraMessage(t, mode, status, body, slots) { 'ignore', ); await controller.run(); - t.equal(extraResult.status(), status); + t.is(extraResult.status(), status); t.deepEqual(extraResult.resolution(), { body, slots, }); - t.end(); + return; // t.end(); } test('extra message returns data', async t => { diff --git a/packages/SwingSet/test/test-kernel.js b/packages/SwingSet/test/test-kernel.js index 5c0a362b682..a9e53510e51 100644 --- a/packages/SwingSet/test/test-kernel.js +++ b/packages/SwingSet/test/test-kernel.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { initSwingStore } from '@agoric/swing-store-simple'; import { waitUntilQuiescent } from '../src/waitUntilQuiescent'; @@ -48,7 +48,7 @@ test('build kernel', async t => { const data = kernel.dump(); t.deepEqual(data.vatTables, []); t.deepEqual(data.kernelTable, []); - t.end(); + return; // t.end(); }); test('simple call', async t => { @@ -87,14 +87,14 @@ test('simple call', async t => { t.deepEqual(log, [['o+1', 'foo', capdata('args')]]); data = kernel.dump(); - t.equal(data.log.length, 1); + t.is(data.log.length, 1); t.deepEqual(JSON.parse(data.log[0]), { facetID: 'o+1', method: 'foo', args: capdata('args'), }); - t.end(); + return; // t.end(); }); test('map inbound', async t => { @@ -144,7 +144,7 @@ test('map inbound', async t => { ['kp40', vat1, 'p-60'], ]); - t.end(); + return; // t.end(); }); test('addImport', async t => { @@ -165,7 +165,7 @@ test('addImport', async t => { ['ko20', vat1, 'o-50'], ['ko20', vat2, 'o+5'], ]); - t.end(); + return; // t.end(); }); test('outbound call', async t => { @@ -364,7 +364,7 @@ test('outbound call', async t => { }, ]); - t.end(); + return; // t.end(); }); test('three-party', async t => { @@ -495,7 +495,7 @@ test('three-party', async t => { kt.push(['kp42', vatB, 'p-60']); checkKT(t, kernel, kt); - t.end(); + return; // t.end(); }); test('transfer promise', async t => { @@ -599,7 +599,7 @@ test('transfer promise', async t => { checkPromises(t, kernel, kp); checkKT(t, kernel, kt); - t.end(); + return; // t.end(); }); test('subscribe to promise', async t => { @@ -642,7 +642,7 @@ test('subscribe to promise', async t => { t.deepEqual(kernel.dump().runQueue, []); t.deepEqual(log, []); - t.end(); + return; // t.end(); }); test('promise resolveToData', async t => { @@ -719,7 +719,7 @@ test('promise resolveToData', async t => { } t.deepEqual(kernel.dump().runQueue, []); - t.end(); + return; // t.end(); }); test('promise resolveToPresence', async t => { @@ -799,7 +799,7 @@ test('promise resolveToPresence', async t => { ]); } t.deepEqual(kernel.dump().runQueue, []); - t.end(); + return; // t.end(); }); test('promise reject', async t => { @@ -876,7 +876,7 @@ test('promise reject', async t => { } t.deepEqual(kernel.dump().runQueue, []); - t.end(); + return; // t.end(); }); test('transcript', async t => { @@ -911,7 +911,7 @@ test('transcript', async t => { // the transcript records vat-specific import/export slots const tr = kernel.dump().vatTables[0].state.transcript; - t.equal(tr.length, 1); + t.is(tr.length, 1); t.deepEqual(tr[0], { d: [ 'deliver', @@ -929,7 +929,7 @@ test('transcript', async t => { crankNumber: 1, }); - t.end(); + return; // t.end(); }); // p1=x!foo(); p2=p1!bar(); p3=p2!urgh(); no pipelining. p1 will have a @@ -1047,7 +1047,7 @@ test('non-pipelined promise queueing', async t => { }, ]); - t.end(); + return; // t.end(); }); // p1=x!foo(); p2=p1!bar(); p3=p2!urgh(); with pipelining. All three should @@ -1156,5 +1156,5 @@ test('pipelined promise queueing', async t => { }, ]); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index a2cd2f82fad..83975326998 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { E } from '@agoric/eventual-send'; import { waitUntilQuiescent } from '../src/waitUntilQuiescent'; import { makeLiveSlots } from '../src/kernel/liveSlots'; @@ -101,7 +101,7 @@ test('calls', async t => { // TODO: more calls, more slot types - t.end(); + return; // t.end(); }); test('liveslots pipelines to syscall.send', async t => { @@ -164,7 +164,7 @@ test('liveslots pipelines to syscall.send', async t => { }); t.deepEqual(log.shift(), { type: 'subscribe', target: p3 }); - t.end(); + return; // t.end(); }); test('liveslots pipeline/non-pipeline calls', async t => { @@ -240,7 +240,7 @@ test('liveslots pipeline/non-pipeline calls', async t => { t.deepEqual(log.shift(), { type: 'subscribe', target: 'p+7' }); t.deepEqual(log, []); - t.end(); + return; // t.end(); }); async function doOutboundPromise(t, mode) { @@ -341,7 +341,7 @@ async function doOutboundPromise(t, mode) { t.deepEqual(log, []); - t.end(); + return; // t.end(); } test('liveslots does not retire outbound promise IDs after fulfillToPresence', async t => { @@ -459,7 +459,7 @@ async function doResultPromise(t, mode) { // instead we get a send to p+5 t.deepEqual(log, []); - t.end(); + return; // t.end(); } test('liveslots does not retire result promise IDs after fulfillToPresence', async t => { diff --git a/packages/SwingSet/test/test-marshal.js b/packages/SwingSet/test/test-marshal.js index a1c1aac5933..91b7045e174 100644 --- a/packages/SwingSet/test/test-marshal.js +++ b/packages/SwingSet/test/test-marshal.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makePromiseKit } from '@agoric/promise-kit'; import { makeMarshaller } from '../src/kernel/liveSlots'; @@ -37,7 +37,7 @@ test('serialize exports', t => { slots: ['o+2', 'o+1'], }); - t.end(); + return; // t.end(); }); test('deserialize imports', async t => { @@ -48,8 +48,8 @@ test('deserialize imports', async t => { slots: ['o-1'], }); // a should be a proxy/presence. For now these are obvious. - t.equal(a.toString(), '[Presence o-1]'); - t.ok(Object.isFrozen(a)); + t.is(a.toString(), '[Presence o-1]'); + t.assert(Object.isFrozen(a)); // m now remembers the proxy const b = m.unserialize({ @@ -65,7 +65,7 @@ test('deserialize imports', async t => { }); t.is(a, c); - t.end(); + return; // t.end(); }); test('deserialize exports', t => { @@ -78,7 +78,7 @@ test('deserialize exports', t => { }); t.is(a, o1); - t.end(); + return; // t.end(); }); test('serialize imports', async t => { @@ -93,7 +93,7 @@ test('serialize imports', async t => { slots: ['o-1'], }); - t.end(); + return; // t.end(); }); test('serialize promise', async t => { @@ -130,7 +130,7 @@ test('serialize promise', async t => { await pauseP; t.deepEqual(log, [{ result: 'p+5', data: { body: '5', slots: [] } }]); - t.end(); + return; // t.end(); }); test('unserialize promise', async t => { @@ -148,7 +148,7 @@ test('unserialize promise', async t => { slots: ['p-1'], }); t.deepEqual(log, ['subscribe-p-1']); - t.ok(p instanceof Promise); + t.assert(p instanceof Promise); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-message-patterns.js b/packages/SwingSet/test/test-message-patterns.js index b31545496c0..ce8260d0d53 100644 --- a/packages/SwingSet/test/test-message-patterns.js +++ b/packages/SwingSet/test/test-message-patterns.js @@ -3,7 +3,7 @@ /* eslint object-shorthand: "off" */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; import { buildVatController, loadBasedir } from '../src/index'; import { buildPatterns } from './message-patterns'; @@ -62,7 +62,7 @@ function testLocalPatterns() { modes[mode](`test pattern ${name} locally`, async t => { const logs = await runVatsLocally(t, name); t.deepEqual(logs, bp.expected[name]); - t.end(); + return; // t.end(); }); } } @@ -114,7 +114,7 @@ function testCommsPatterns() { expected = bp.expected_pipelined[name]; } t.deepEqual(logs, expected); - t.end(); + return; // t.end(); }); } } diff --git a/packages/SwingSet/test/test-network.js b/packages/SwingSet/test/test-network.js index fa31ea134f9..0d81f572bd3 100644 --- a/packages/SwingSet/test/test-network.js +++ b/packages/SwingSet/test/test-network.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; // adds 'harden' to global -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makePromiseKit } from '@agoric/promise-kit'; import { @@ -63,7 +63,7 @@ const makeProtocolHandler = t => { async onListenRemove(port, localAddr, listenHandler) { t.assert(port, `port is tracked in onListen`); t.assert(localAddr, `local address is supplied to onListen`); - t.equals(listenHandler, l, `listenHandler is tracked in onListenRemove`); + t.is(listenHandler, l, `listenHandler is tracked in onListenRemove`); l = undefined; lp = undefined; log('port done listening', port.getLocalAddress()); @@ -88,15 +88,15 @@ test('handled protocol', async t => { async onOpen(connection, _localAddr, _remoteAddr) { const ack = await connection.send('ping'); // log(ack); - t.equals(`${ack}`, 'ping', 'received pong'); + t.is(`${ack}`, 'ping', 'received pong'); connection.close(); }, async onClose(_connection, reason) { - t.equals(reason, undefined, 'no close reason'); + t.is(reason, undefined, 'no close reason'); closed.resolve(); }, async onReceive(_connection, bytes) { - t.equals(`${bytes}`, 'ping'); + t.is(`${bytes}`, 'ping'); return 'pong'; }, }), @@ -104,9 +104,9 @@ test('handled protocol', async t => { await closed.promise; await port.revoke(); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -123,14 +123,14 @@ test('protocol connection listen', async t => { */ const listener = harden({ async onListen(p, listenHandler) { - t.equals(p, port, `port is tracked in onListen`); + t.is(p, port, `port is tracked in onListen`); t.assert(listenHandler, `listenHandler is tracked in onListen`); }, async onAccept(p, localAddr, remoteAddr, listenHandler) { t.assert(localAddr, `local address is passed to onAccept`); t.assert(remoteAddr, `remote address is passed to onAccept`); - t.equals(p, port, `port is tracked in onAccept`); - t.equals( + t.is(p, port, `port is tracked in onAccept`); + t.is( listenHandler, listener, `listenHandler is tracked in onAccept`, @@ -144,48 +144,48 @@ test('protocol connection listen', async t => { ); handler = connectionHandler; const ack = await connection.send('ping'); - t.equals(`${ack}`, 'ping', 'received pong'); + t.is(`${ack}`, 'ping', 'received pong'); connection.close(); }, async onClose(c, reason, connectionHandler) { - t.equals( + t.is( connectionHandler, handler, `connectionHandler is tracked in onClose`, ); handler = undefined; t.assert(c, 'connection is passed to onClose'); - t.equals(reason, undefined, 'no close reason'); + t.is(reason, undefined, 'no close reason'); closed.resolve(); }, async onReceive(c, packet, connectionHandler) { - t.equals( + t.is( connectionHandler, handler, `connectionHandler is tracked in onReceive`, ); t.assert(c, 'connection is passed to onReceive'); - t.equals(`${packet}`, 'ping', 'expected ping'); + t.is(`${packet}`, 'ping', 'expected ping'); return 'pong'; }, }); }, async onError(p, rej, listenHandler) { - t.equals(p, port, `port is tracked in onError`); - t.equals( + t.is(p, port, `port is tracked in onError`); + t.is( listenHandler, listener, `listenHandler is tracked in onError`, ); - t.isNot(rej, rej, 'unexpected error'); + t.not(rej, rej, 'unexpected error'); }, async onRemove(p, listenHandler) { - t.equals( + t.is( listenHandler, listener, `listenHandler is tracked in onRemove`, ); - t.equals(p, port, `port is passed to onReset`); + t.is(p, port, `port is passed to onReset`); }, }); @@ -216,9 +216,9 @@ test('protocol connection listen', async t => { await port.removeListener(listener); await port.revoke(); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -237,7 +237,7 @@ test('loopback protocol', async t => { async onAccept(_p, _localAddr, _remoteAddr, _listenHandler) { return harden({ async onReceive(c, packet, _connectionHandler) { - t.equals(`${packet}`, 'ping', 'expected ping'); + t.is(`${packet}`, 'ping', 'expected ping'); return 'pingack'; }, }); @@ -250,7 +250,7 @@ test('loopback protocol', async t => { port.getLocalAddress(), harden({ async onOpen(c, _localAddr, _remoteAddr, _connectionHandler) { - t.equals(`${await c.send('ping')}`, 'pingack', 'expected pingack'); + t.is(`${await c.send('ping')}`, 'pingack', 'expected pingack'); closed.resolve(); }, }), @@ -260,24 +260,24 @@ test('loopback protocol', async t => { await port.removeListener(listener); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); test('routing', async t => { try { const router = makeRouter(); - t.deepEquals(router.getRoutes('/if/local'), [], 'get routes matches none'); + t.deepEqual(router.getRoutes('/if/local'), [], 'get routes matches none'); router.register('/if/', 'a'); - t.deepEquals( + t.deepEqual( router.getRoutes('/if/foo'), [['/if/', 'a']], 'get routes matches prefix', ); router.register('/if/foo', 'b'); - t.deepEquals( + t.deepEqual( router.getRoutes('/if/foo'), [ ['/if/foo', 'b'], @@ -285,13 +285,13 @@ test('routing', async t => { ], 'get routes matches all', ); - t.deepEquals( + t.deepEqual( router.getRoutes('/if/foob'), [['/if/', 'a']], 'get routes needs separator', ); router.register('/ibc/*/ordered', 'c'); - t.deepEquals( + t.deepEqual( router.getRoutes('/if/foo'), [ ['/if/foo', 'b'], @@ -299,41 +299,41 @@ test('routing', async t => { ], 'get routes avoids nonmatching paths', ); - t.deepEquals( + t.deepEqual( router.getRoutes('/ibc/*/ordered'), [['/ibc/*/ordered', 'c']], 'direct match', ); - t.deepEquals( + t.deepEqual( router.getRoutes('/ibc/*/ordered/zot'), [['/ibc/*/ordered', 'c']], 'prefix matches', ); - t.deepEquals(router.getRoutes('/ibc/*/barfo'), [], 'no match'); + t.deepEqual(router.getRoutes('/ibc/*/barfo'), [], 'no match'); t.throws( () => router.unregister('/ibc/*/ordered', 'a'), - /Router is not registered/, + { message: /Router is not registered/ }, 'unregister fails for no match', ); router.unregister('/ibc/*/ordered', 'c'); - t.deepEquals( + t.deepEqual( router.getRoutes('/ibc/*/ordered'), [], 'no match after unregistration', ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); test('multiaddr', async t => { try { - t.deepEquals(parse('/if/local'), [['if', 'local']]); - t.deepEquals(parse('/zot'), [['zot']]); - t.deepEquals(parse('/zot/foo/bar/baz/bot'), [ + t.deepEqual(parse('/if/local'), [['if', 'local']]); + t.deepEqual(parse('/zot'), [['zot']]); + t.deepEqual(parse('/zot/foo/bar/baz/bot'), [ ['zot', 'foo'], ['bar', 'baz'], ['bot'], @@ -341,7 +341,7 @@ test('multiaddr', async t => { for (const str of ['', 'foobar']) { t.throws( () => parse(str), - /Error parsing Multiaddr/, + { message: /Error parsing Multiaddr/ }, `expected failure of ${str}`, ); } @@ -352,16 +352,16 @@ test('multiaddr', async t => { '/foobib/bar', '/k1/v1/k2/v2/k3/v3', ]) { - t.equals( + t.is( unparse(parse(str)), str, `round-trip of ${JSON.stringify(str)} matches`, ); } } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -377,8 +377,8 @@ test('bytes conversions', t => { ['foobar', 'Zm9vYmFy'], ]; for (const [inp, outp] of insouts) { - t.equals(dataToBase64(inp), outp, `${inp} encodes`); - t.equals(base64ToBytes(outp), inp, `${outp} decodes`); + t.is(dataToBase64(inp), outp, `${inp} encodes`); + t.is(base64ToBytes(outp), inp, `${outp} decodes`); } const inputs = [ 'a', @@ -389,11 +389,11 @@ test('bytes conversions', t => { 'other--+iadtedata', ]; for (const str of inputs) { - t.equals(base64ToBytes(dataToBase64(str)), str, `${str} round trips`); + t.is(base64ToBytes(dataToBase64(str)), str, `${str} round trips`); } } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/SwingSet/test/test-node-version.js b/packages/SwingSet/test/test-node-version.js index 42223971ba9..18451fd1d04 100644 --- a/packages/SwingSet/test/test-node-version.js +++ b/packages/SwingSet/test/test-node-version.js @@ -1,14 +1,14 @@ // eslint-disable-next-line no-redeclare /* global process */ import semver from 'semver'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; test('Node version for IO queue priority', t => { t.true( semver.satisfies(process.version, '>=11.0'), 'we need Node 11 where the IO queue is higher priority than the Promise queue', ); - t.end(); + return; // t.end(); }); test('Node version', t => { @@ -16,5 +16,5 @@ test('Node version', t => { semver.satisfies(process.version, '>=12.16.1'), 'we only test against Node 12.16.1 (LTS)', ); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-promises.js b/packages/SwingSet/test/test-promises.js index b2627b98339..e25a2fc8b4e 100644 --- a/packages/SwingSet/test/test-promises.js +++ b/packages/SwingSet/test/test-promises.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; import { buildVatController, loadBasedir } from '../src/index'; @@ -11,7 +11,7 @@ test('flush', async t => { // all promises should settle before c.step() fires await c.step(); t.deepEqual(c.dump().log, ['then1', 'then2']); - t.end(); + return; // t.end(); }); test('E() resolve', async t => { @@ -25,7 +25,7 @@ test('E() resolve', async t => { 'b.resolved 3', 'left.then 4', ]); - t.end(); + return; // t.end(); }); test('E(E(x).foo()).bar()', async t => { @@ -48,7 +48,7 @@ test('E(E(x).foo()).bar()', async t => { 'left.call3 2', 'b.resolved 3', ]); - t.end(); + return; // t.end(); }); test('E(Promise.resolve(presence)).foo()', async t => { @@ -62,7 +62,7 @@ test('E(Promise.resolve(presence)).foo()', async t => { 'left.call3 2', 'b.resolved 3', ]); - t.end(); + return; // t.end(); }); test('E(local).foo()', async t => { @@ -71,7 +71,7 @@ test('E(local).foo()', async t => { await c.run(); t.deepEqual(c.dump().log, ['b.local1.finish', 'local.foo 1', 'b.resolved 2']); - t.end(); + return; // t.end(); }); test('resolve-to-local', async t => { @@ -85,7 +85,7 @@ test('resolve-to-local', async t => { 'local.foo 2', 'b.resolved 3', ]); - t.end(); + return; // t.end(); }); test('send-promise-resolve-to-local', async t => { @@ -100,7 +100,7 @@ test('send-promise-resolve-to-local', async t => { 'local.foo 1', 'b.resolved 4', ]); - t.end(); + return; // t.end(); }); test('send-harden-promise-1', async t => { @@ -122,7 +122,7 @@ test('send-harden-promise-1', async t => { 'o1 frozen true', 'b.harden-promise-1.finish', ]); - t.end(); + return; // t.end(); }); test('circular promise resolution data', async t => { @@ -162,5 +162,5 @@ test('circular promise resolution data', async t => { }); } t.deepEqual(c.dump().promises, expectedPromises); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-queue-priority.js b/packages/SwingSet/test/test-queue-priority.js index d9210a4eedb..c764105eb04 100644 --- a/packages/SwingSet/test/test-queue-priority.js +++ b/packages/SwingSet/test/test-queue-priority.js @@ -1,6 +1,6 @@ // eslint-disable-next-line no-redeclare /* global setImmediate setTimeout */ -import { test } from 'tape-promise/tape'; +import test from 'ava'; test('Promise queue should be higher priority than IO/timer queue', async t => { const log = []; @@ -19,5 +19,5 @@ test('Promise queue should be higher priority than IO/timer queue', async t => { await p; t.deepEqual(log, [1, 2, 3, 4, 5, 6]); - return t.end(); + returnreturn; // t.end(); }); diff --git a/packages/SwingSet/test/test-state.js b/packages/SwingSet/test/test-state.js index dbff0752602..f6793ee50ee 100644 --- a/packages/SwingSet/test/test-state.js +++ b/packages/SwingSet/test/test-state.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { initSwingStore, getAllState, @@ -36,12 +36,12 @@ function checkState(t, getState, expected) { } function testStorage(t, s, getState, commit) { - t.notOk(s.has('missing')); - t.equal(s.get('missing'), undefined); + t.falsy(s.has('missing')); + t.is(s.get('missing'), undefined); s.set('foo', 'f'); - t.ok(s.has('foo')); - t.equal(s.get('foo'), 'f'); + t.assert(s.has('foo')); + t.is(s.get('foo'), 'f'); s.set('foo2', 'f2'); s.set('foo1', 'f1'); @@ -50,8 +50,8 @@ function testStorage(t, s, getState, commit) { t.deepEqual(Array.from(s.getKeys('foo1', 'foo4')), ['foo1', 'foo2', 'foo3']); s.delete('foo2'); - t.notOk(s.has('foo2')); - t.equal(s.get('foo2'), undefined); + t.falsy(s.has('foo2')); + t.is(s.get('foo2'), undefined); t.deepEqual(Array.from(s.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); if (commit) { @@ -68,7 +68,7 @@ function testStorage(t, s, getState, commit) { test('storageInMemory', t => { const { storage } = initSwingStore(); testStorage(t, storage, () => getAllState(storage), null); - t.end(); + return; // t.end(); }); function buildHostDBAndGetState() { @@ -80,12 +80,12 @@ function buildHostDBAndGetState() { test('hostDBInMemory', t => { const { hostDB, getState } = buildHostDBAndGetState(); - t.notOk(hostDB.has('missing')); - t.equal(hostDB.get('missing'), undefined); + t.falsy(hostDB.has('missing')); + t.is(hostDB.get('missing'), undefined); hostDB.applyBatch([{ op: 'set', key: 'foo', value: 'f' }]); - t.ok(hostDB.has('foo')); - t.equal(hostDB.get('foo'), 'f'); + t.assert(hostDB.has('foo')); + t.is(hostDB.get('foo'), 'f'); hostDB.applyBatch([ { op: 'set', key: 'foo2', value: 'f2' }, @@ -100,8 +100,8 @@ test('hostDBInMemory', t => { ]); hostDB.applyBatch([{ op: 'delete', key: 'foo2' }]); - t.notOk(hostDB.has('foo2')); - t.equal(hostDB.get('foo2'), undefined); + t.falsy(hostDB.has('foo2')); + t.is(hostDB.get('foo2'), undefined); t.deepEqual(Array.from(hostDB.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); checkState(t, getState, [ @@ -109,28 +109,28 @@ test('hostDBInMemory', t => { ['foo1', 'f1'], ['foo3', 'f3'], ]); - t.end(); + return; // t.end(); }); test('blockBuffer fulfills storage API', t => { const { hostDB, getState } = buildHostDBAndGetState(); const { blockBuffer, commitBlock } = buildBlockBuffer(hostDB); testStorage(t, blockBuffer, getState, commitBlock); - t.end(); + return; // t.end(); }); test('guardStorage fulfills storage API', t => { const { storage } = initSwingStore(); const guardedHostStorage = guardStorage(storage); testStorage(t, guardedHostStorage, () => getAllState(storage), null); - t.end(); + return; // t.end(); }); test('crankBuffer fulfills storage API', t => { const { storage } = initSwingStore(); const { crankBuffer, commitCrank } = buildCrankBuffer(storage); testStorage(t, crankBuffer, () => getAllState(storage), commitCrank); - t.end(); + return; // t.end(); }); test('crankBuffer can abortCrank', t => { @@ -141,8 +141,8 @@ test('crankBuffer can abortCrank', t => { ); s.set('foo', 'f'); - t.ok(s.has('foo')); - t.equal(s.get('foo'), 'f'); + t.assert(s.has('foo')); + t.is(s.get('foo'), 'f'); s.set('foo2', 'f2'); s.set('foo1', 'f1'); @@ -151,8 +151,8 @@ test('crankBuffer can abortCrank', t => { t.deepEqual(Array.from(s.getKeys('foo1', 'foo4')), ['foo1', 'foo2', 'foo3']); s.delete('foo2'); - t.notOk(s.has('foo2')); - t.equal(s.get('foo2'), undefined); + t.falsy(s.has('foo2')); + t.is(s.get('foo2'), undefined); t.deepEqual(Array.from(s.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); commitBlock(); @@ -187,7 +187,7 @@ test('crankBuffer can abortCrank', t => { ['foo5', 'f5'], ]); - t.end(); + return; // t.end(); }); test('storage helpers', t => { @@ -228,18 +228,18 @@ test('storage helpers', t => { t.deepEqual(Array.from(s.getPrefixedValues('foo.', 1)), ['f1', 'f2', 'f3']); s.deletePrefixedKeys('foo.', 1); - t.ok(s.has('foo.0')); - t.notOk(s.has('foo.1')); - t.notOk(s.has('foo.2')); - t.notOk(s.has('foo.3')); - t.notOk(s.has('foo.4')); - t.ok(s.has('foo.5')); + t.assert(s.has('foo.0')); + t.falsy(s.has('foo.1')); + t.falsy(s.has('foo.2')); + t.falsy(s.has('foo.3')); + t.falsy(s.has('foo.4')); + t.assert(s.has('foo.5')); checkState(t, () => getAllState(storage), [ ['foo.0', 'f0'], ['foo.5', 'f5'], ]); - t.end(); + return; // t.end(); }); function buildKeeperStorageInMemory() { @@ -262,7 +262,7 @@ function duplicateKeeper(getState) { test('kernel state', async t => { const { kstorage, getState, commitCrank } = buildKeeperStorageInMemory(); const k = makeKernelKeeper(kstorage); - t.ok(!k.getInitialized()); + t.assert(!k.getInitialized()); k.createStartingKernelState(); k.setInitialized(); @@ -279,7 +279,7 @@ test('kernel state', async t => { ['kd.nextID', '30'], ['kp.nextID', '40'], ]); - t.end(); + return; // t.end(); }); test('kernelKeeper vat names', async t => { @@ -289,8 +289,8 @@ test('kernelKeeper vat names', async t => { const v1 = k.allocateVatIDForNameIfNeeded('vatname5'); const v2 = k.allocateVatIDForNameIfNeeded('Frank'); - t.equal(v1, 'v1'); - t.equal(v2, 'v2'); + t.is(v1, 'v1'); + t.is(v2, 'v2'); commitCrank(); checkState(t, getState, [ @@ -307,14 +307,14 @@ test('kernelKeeper vat names', async t => { ['vat.name.Frank', 'v2'], ]); t.deepEqual(k.getAllVatNames(), ['Frank', 'vatname5']); - t.equal(k.getVatIDForName('Frank'), v2); - t.equal(k.allocateVatIDForNameIfNeeded('Frank'), v2); + t.is(k.getVatIDForName('Frank'), v2); + t.is(k.allocateVatIDForNameIfNeeded('Frank'), v2); const k2 = duplicateKeeper(getState); t.deepEqual(k2.getAllVatNames(), ['Frank', 'vatname5']); - t.equal(k2.getVatIDForName('Frank'), v2); - t.equal(k2.allocateVatIDForNameIfNeeded('Frank'), v2); - t.end(); + t.is(k2.getVatIDForName('Frank'), v2); + t.is(k2.allocateVatIDForNameIfNeeded('Frank'), v2); + return; // t.end(); }); test('kernelKeeper device names', async t => { @@ -324,8 +324,8 @@ test('kernelKeeper device names', async t => { const d7 = k.allocateDeviceIDForNameIfNeeded('devicename5'); const d8 = k.allocateDeviceIDForNameIfNeeded('Frank'); - t.equal(d7, 'd7'); - t.equal(d8, 'd8'); + t.is(d7, 'd7'); + t.is(d8, 'd8'); commitCrank(); checkState(t, getState, [ @@ -342,14 +342,14 @@ test('kernelKeeper device names', async t => { ['device.name.Frank', 'd8'], ]); t.deepEqual(k.getAllDeviceNames(), ['Frank', 'devicename5']); - t.equal(k.getDeviceIDForName('Frank'), d8); - t.equal(k.allocateDeviceIDForNameIfNeeded('Frank'), d8); + t.is(k.getDeviceIDForName('Frank'), d8); + t.is(k.allocateDeviceIDForNameIfNeeded('Frank'), d8); const k2 = duplicateKeeper(getState); t.deepEqual(k2.getAllDeviceNames(), ['Frank', 'devicename5']); - t.equal(k2.getDeviceIDForName('Frank'), d8); - t.equal(k2.allocateDeviceIDForNameIfNeeded('Frank'), d8); - t.end(); + t.is(k2.getDeviceIDForName('Frank'), d8); + t.is(k2.allocateDeviceIDForNameIfNeeded('Frank'), d8); + return; // t.end(); }); test('kernelKeeper runQueue', async t => { @@ -357,37 +357,37 @@ test('kernelKeeper runQueue', async t => { const k = makeKernelKeeper(kstorage); k.createStartingKernelState(); - t.ok(k.isRunQueueEmpty()); - t.equal(k.getRunQueueLength(), 0); + t.assert(k.isRunQueueEmpty()); + t.is(k.getRunQueueLength(), 0); k.addToRunQueue({ type: 'send', stuff: 'awesome' }); - t.notOk(k.isRunQueueEmpty()); - t.equal(k.getRunQueueLength(), 1); + t.falsy(k.isRunQueueEmpty()); + t.is(k.getRunQueueLength(), 1); k.addToRunQueue({ type: 'notify', stuff: 'notifawesome' }); - t.notOk(k.isRunQueueEmpty()); - t.equal(k.getRunQueueLength(), 2); + t.falsy(k.isRunQueueEmpty()); + t.is(k.getRunQueueLength(), 2); commitCrank(); const k2 = duplicateKeeper(getState); t.deepEqual(k.getNextMsg(), { type: 'send', stuff: 'awesome' }); - t.notOk(k.isRunQueueEmpty()); - t.equal(k.getRunQueueLength(), 1); + t.falsy(k.isRunQueueEmpty()); + t.is(k.getRunQueueLength(), 1); t.deepEqual(k.getNextMsg(), { type: 'notify', stuff: 'notifawesome' }); - t.ok(k.isRunQueueEmpty()); - t.equal(k.getRunQueueLength(), 0); + t.assert(k.isRunQueueEmpty()); + t.is(k.getRunQueueLength(), 0); t.deepEqual(k2.getNextMsg(), { type: 'send', stuff: 'awesome' }); - t.notOk(k2.isRunQueueEmpty()); - t.equal(k2.getRunQueueLength(), 1); + t.falsy(k2.isRunQueueEmpty()); + t.is(k2.getRunQueueLength(), 1); t.deepEqual(k2.getNextMsg(), { type: 'notify', stuff: 'notifawesome' }); - t.ok(k2.isRunQueueEmpty()); - t.equal(k2.getRunQueueLength(), 0); + t.assert(k2.isRunQueueEmpty()); + t.is(k2.getRunQueueLength(), 0); - t.end(); + return; // t.end(); }); test('kernelKeeper promises', async t => { @@ -403,8 +403,8 @@ test('kernelKeeper promises', async t => { subscribers: [], decider: 'v4', }); - t.ok(k.hasKernelPromise(p1)); - t.notOk(k.hasKernelPromise('kp99')); + t.assert(k.hasKernelPromise(p1)); + t.falsy(k.hasKernelPromise('kp99')); commitCrank(); let k2 = duplicateKeeper(getState); @@ -416,7 +416,7 @@ test('kernelKeeper promises', async t => { subscribers: [], decider: 'v4', }); - t.ok(k2.hasKernelPromise(p1)); + t.assert(k2.hasKernelPromise(p1)); k.clearDecider(p1); t.deepEqual(k.getKernelPromise(p1), { @@ -474,7 +474,7 @@ test('kernelKeeper promises', async t => { refCount: 0, slot: 'ko44', }); - t.ok(k.hasKernelPromise(p1)); + t.assert(k.hasKernelPromise(p1)); // all the subscriber/queue stuff should be gone commitCrank(); checkState(t, getState, [ @@ -491,7 +491,7 @@ test('kernelKeeper promises', async t => { ['kp40.state', 'fulfilledToPresence'], ['kp40.refCount', '0'], ]); - t.end(); + return; // t.end(); }); test('kernelKeeper promise resolveToData', async t => { @@ -510,7 +510,7 @@ test('kernelKeeper promise resolveToData', async t => { slots: ['ko22', 'kp24', 'kd25'], }, }); - t.end(); + return; // t.end(); }); test('kernelKeeper promise reject', async t => { @@ -529,7 +529,7 @@ test('kernelKeeper promise reject', async t => { slots: ['ko22', 'kp24', 'kd25'], }, }); - t.end(); + return; // t.end(); }); test('vatKeeper', async t => { @@ -543,25 +543,25 @@ test('vatKeeper', async t => { const vatExport1 = 'o+4'; const kernelExport1 = vk.mapVatSlotToKernelSlot(vatExport1); - t.equal(kernelExport1, 'ko20'); - t.equal(vk.mapVatSlotToKernelSlot(vatExport1), kernelExport1); - t.equal(vk.mapKernelSlotToVatSlot(kernelExport1), vatExport1); + t.is(kernelExport1, 'ko20'); + t.is(vk.mapVatSlotToKernelSlot(vatExport1), kernelExport1); + t.is(vk.mapKernelSlotToVatSlot(kernelExport1), vatExport1); commitCrank(); let vk2 = duplicateKeeper(getState).allocateVatKeeperIfNeeded(v1); - t.equal(vk2.mapVatSlotToKernelSlot(vatExport1), kernelExport1); - t.equal(vk2.mapKernelSlotToVatSlot(kernelExport1), vatExport1); + t.is(vk2.mapVatSlotToKernelSlot(vatExport1), kernelExport1); + t.is(vk2.mapKernelSlotToVatSlot(kernelExport1), vatExport1); const kernelImport2 = 'ko25'; const vatImport2 = vk.mapKernelSlotToVatSlot(kernelImport2); - t.equal(vatImport2, 'o-50'); - t.equal(vk.mapKernelSlotToVatSlot(kernelImport2), vatImport2); - t.equal(vk.mapVatSlotToKernelSlot(vatImport2), kernelImport2); + t.is(vatImport2, 'o-50'); + t.is(vk.mapKernelSlotToVatSlot(kernelImport2), vatImport2); + t.is(vk.mapVatSlotToKernelSlot(vatImport2), kernelImport2); commitCrank(); vk2 = duplicateKeeper(getState).allocateVatKeeperIfNeeded(v1); - t.equal(vk2.mapKernelSlotToVatSlot(kernelImport2), vatImport2); - t.equal(vk2.mapVatSlotToKernelSlot(vatImport2), kernelImport2); + t.is(vk2.mapKernelSlotToVatSlot(kernelImport2), vatImport2); + t.is(vk2.mapVatSlotToKernelSlot(vatImport2), kernelImport2); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-tildot.js b/packages/SwingSet/test/test-tildot.js index af4649f4352..622af6bbd59 100644 --- a/packages/SwingSet/test/test-tildot.js +++ b/packages/SwingSet/test/test-tildot.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { buildVatController } from '../src/index'; test('vat code can use tildot', async t => { @@ -19,5 +19,5 @@ test('vat code can use tildot', async t => { 'HandledPromise.applyMethod(x, "foo", [y]);', 'ok', ]); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-timer-device.js b/packages/SwingSet/test/test-timer-device.js index 8f270159a4a..4cf50444b45 100644 --- a/packages/SwingSet/test/test-timer-device.js +++ b/packages/SwingSet/test/test-timer-device.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeTimerMap, curryPollFn } from '../src/devices/timer-src'; test('multiMap multi store', t => { @@ -7,24 +7,24 @@ test('multiMap multi store', t => { mm.add(3, 'threeA'); mm.add(3, 'threeB'); const threes = mm.removeEventsThrough(4); - t.equal(threes.length, 1); + t.is(threes.length, 1); t.deepEqual(threes[0], { time: 3, handlers: [{ handler: 'threeA' }, { handler: 'threeB' }], }); - t.equal(mm.removeEventsThrough(10).length, 0); - t.end(); + t.is(mm.removeEventsThrough(10).length, 0); + return; // t.end(); }); test('multiMap store multiple keys', t => { const mm = makeTimerMap(); mm.add(3, 'threeA'); mm.add(13, 'threeB'); - t.equal(mm.removeEventsThrough(4).length, 1); - t.equal(mm.removeEventsThrough(10).length, 0); + t.is(mm.removeEventsThrough(4).length, 1); + t.is(mm.removeEventsThrough(10).length, 0); const thirteens = mm.removeEventsThrough(13); - t.equal(thirteens.length, 1, thirteens); - t.end(); + t.is(thirteens.length, 1, thirteens); + return; // t.end(); }); test('multiMap remove key', t => { @@ -34,11 +34,11 @@ test('multiMap remove key', t => { t.deepEqual(mm.remove('not There'), []); t.deepEqual(mm.remove('threeA'), [3]); mm.remove(3, 'threeA'); - t.equal(mm.removeEventsThrough(10).length, 0); + t.is(mm.removeEventsThrough(10).length, 0); const thirteens = mm.removeEventsThrough(13); - t.equal(thirteens.length, 1); + t.is(thirteens.length, 1); t.deepEqual(thirteens[0], { time: 13, handlers: [{ handler: 'threeB' }] }); - t.end(); + return; // t.end(); }); function fakeSO(o) { @@ -83,14 +83,14 @@ test('Timer schedule single event', t => { const fakeTimer = makeFakeTimer(1); const lastPolled = fakeTimer.getLastPolled; const poll = curryPollFn(fakeSO, [], schedule, lastPolled, _ => {}); - t.notOk(poll(1)); // false when nothing is woken + t.falsy(poll(1)); // false when nothing is woken const handler = makeHandler(); schedule.add(2, handler); - t.equals(fakeTimer.getLastPolled(), 1); - t.ok(poll(4)); - t.equals(handler.getCalls(), 1); - t.equals(handler.getArgs()[0], 2); - t.end(); + t.is(fakeTimer.getLastPolled(), 1); + t.assert(poll(4)); + t.is(handler.getCalls(), 1); + t.is(handler.getArgs()[0], 2); + return; // t.end(); }); test('Timer schedule multiple events', t => { @@ -98,19 +98,19 @@ test('Timer schedule multiple events', t => { const fakeTimer = makeFakeTimer(1); const lastPolled = fakeTimer.getLastPolled; const poll = curryPollFn(fakeSO, [], schedule, lastPolled, _ => {}); - t.notOk(poll(1)); // false when nothing is woken + t.falsy(poll(1)); // false when nothing is woken const handler1 = makeHandler(); const handler2 = makeHandler(); schedule.add(3, handler1); schedule.add(4, handler1); schedule.add(2, handler2); - t.equals(lastPolled(), 1); - t.ok(poll(4)); - t.equals(handler1.getCalls(), 2); - t.equals(handler2.getCalls(), 1); + t.is(lastPolled(), 1); + t.assert(poll(4)); + t.is(handler1.getCalls(), 2); + t.is(handler2.getCalls(), 1); t.deepEqual(handler1.getArgs(), [3, 4]); t.deepEqual(handler2.getArgs(), [2]); - t.end(); + return; // t.end(); }); test('Timer schedule repeated event first', t => { @@ -120,16 +120,16 @@ test('Timer schedule repeated event first', t => { const lastPolled = fakeTimer.getLastPolled; const repeater = { startTime: 3, interval: 4 }; const poll = curryPollFn(fakeSO, [repeater], schedule, lastPolled, _ => {}); - t.notOk(poll(1)); // false when nothing is woken + t.falsy(poll(1)); // false when nothing is woken const handler = makeHandler(); schedule.add(5, handler, repeaterIndex); - t.notOk(poll(4)); - t.ok(poll(5)); - t.equals(handler.getCalls(), 1); + t.falsy(poll(4)); + t.assert(poll(5)); + t.is(handler.getCalls(), 1); t.deepEqual(handler.getArgs(), [5]); const expected = [{ time: 7, handlers: [{ handler, index: repeaterIndex }] }]; t.deepEqual(schedule.removeEventsThrough(8), expected); - t.end(); + return; // t.end(); }); test('multiMap remove repeater key', t => { @@ -140,11 +140,11 @@ test('multiMap remove repeater key', t => { const lastPolled = fakeTimer.getLastPolled; const repeater = { startTime: 2, interval: 4 }; const poll = curryPollFn(fakeSO, [repeater], schedule, lastPolled, _ => {}); - t.notOk(poll(1)); // false when nothing is woken + t.falsy(poll(1)); // false when nothing is woken const handler = makeHandler(); schedule.add(scheduleTime, handler, repeaterIndex); t.deepEqual(schedule.remove(handler), [scheduleTime]); - t.end(); + return; // t.end(); }); test('Timer schedule repeated event, repeatedly', t => { @@ -154,24 +154,24 @@ test('Timer schedule repeated event, repeatedly', t => { const lastPolled = fakeTimer.getLastPolled; const repeater = { startTime: 6, interval: 3 }; const poll = curryPollFn(fakeSO, [repeater], schedule, lastPolled, _ => {}); - t.notOk(poll(4)); // false when nothing is woken + t.falsy(poll(4)); // false when nothing is woken const handler = makeHandler(); schedule.add(9, handler, repeaterIndex); - t.equals(handler.getCalls(), 0); + t.is(handler.getCalls(), 0); fakeTimer.setTime(8); - t.notOk(poll(8)); - t.equals(handler.getCalls(), 0); + t.falsy(poll(8)); + t.is(handler.getCalls(), 0); fakeTimer.setTime(10); - t.ok(poll(10)); - t.equals(handler.getCalls(), 1); + t.assert(poll(10)); + t.is(handler.getCalls(), 1); fakeTimer.setTime(12); - t.ok(poll(12)); - t.equals(handler.getCalls(), 2); - t.deepEquals(handler.getArgs(), [9, 12]); - t.end(); + t.assert(poll(12)); + t.is(handler.getCalls(), 2); + t.deepEqual(handler.getArgs(), [9, 12]); + return; // t.end(); }); test('Timer schedule multiple repeaters', t => { @@ -195,39 +195,39 @@ test('Timer schedule multiple repeaters', t => { fakeTimer.setTime(7); poll(7); - t.equals(handler0.getCalls(), 0); - t.equals(handler1.getCalls(), 0); - t.equals(handler2.getCalls(), 0); + t.is(handler0.getCalls(), 0); + t.is(handler1.getCalls(), 0); + t.is(handler2.getCalls(), 0); fakeTimer.setTime(10); - t.ok(poll(10)); - t.equals(handler0.getCalls(), 1); // 9; next is 12 - t.equals(handler1.getCalls(), 0); // first is 12 - t.equals(handler2.getCalls(), 1); // 9; next is 13 + t.assert(poll(10)); + t.is(handler0.getCalls(), 1); // 9; next is 12 + t.is(handler1.getCalls(), 0); // first is 12 + t.is(handler2.getCalls(), 1); // 9; next is 13 repeaters[repeaterIndex0] = undefined; fakeTimer.setTime(12); - t.ok(poll(12)); - t.equals(handler0.getCalls(), 2); // 12; next won't happen - t.equals(handler1.getCalls(), 1); // 12; next is 17 - t.equals(handler2.getCalls(), 1); // next is 13 + t.assert(poll(12)); + t.is(handler0.getCalls(), 2); // 12; next won't happen + t.is(handler1.getCalls(), 1); // 12; next is 17 + t.is(handler2.getCalls(), 1); // next is 13 fakeTimer.setTime(14); - t.ok(poll(14)); - t.equals(handler0.getCalls(), 2); // next is not scheduled - t.equals(handler1.getCalls(), 1); // next is 17 - t.equals(handler2.getCalls(), 2); // 13; next is 17 + t.assert(poll(14)); + t.is(handler0.getCalls(), 2); // next is not scheduled + t.is(handler1.getCalls(), 1); // next is 17 + t.is(handler2.getCalls(), 2); // 13; next is 17 fakeTimer.setTime(16); - t.notOk(poll(16)); // false when nothing is woken - t.equals(handler0.getCalls(), 2); // next didn't happen - t.equals(handler1.getCalls(), 1); // next is 17 - t.equals(handler2.getCalls(), 2); // next is 17 + t.falsy(poll(16)); // false when nothing is woken + t.is(handler0.getCalls(), 2); // next didn't happen + t.is(handler1.getCalls(), 1); // next is 17 + t.is(handler2.getCalls(), 2); // next is 17 const h = [ { handler: handler1, index: repeaterIndex1 }, { handler: handler2, index: repeaterIndex2 }, ]; t.deepEqual(schedule.cloneSchedule(), [{ time: 17, handlers: h }]); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-transcript-light.js b/packages/SwingSet/test/test-transcript-light.js index ca6c5f83303..09ea4336678 100644 --- a/packages/SwingSet/test/test-transcript-light.js +++ b/packages/SwingSet/test/test-transcript-light.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; import { initSwingStore, @@ -15,7 +15,7 @@ test('transcript-light load', async t => { const { storage } = initSwingStore(); const c = await buildVatController(config, ['one'], { hostStorage: storage }); const state0 = getAllState(storage); - t.equal(state0.initialized, 'true'); + t.is(state0.initialized, 'true'); t.notEqual(state0.runQueue, '[]'); await c.step(); @@ -95,5 +95,5 @@ test('transcript-light load', async t => { await c2.step(); t.deepEqual(state5, getAllState(storage2), `p14`); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-transcript.js b/packages/SwingSet/test/test-transcript.js index cdf24a4bed4..1314a4beccc 100644 --- a/packages/SwingSet/test/test-transcript.js +++ b/packages/SwingSet/test/test-transcript.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; // import fs from 'fs'; import { @@ -46,7 +46,7 @@ test('transcript-one save', async t => { states1.forEach((s, i) => { t.deepEqual(s, states2[i]); }); - t.end(); + return; // t.end(); }); test('transcript-one load', async t => { @@ -76,5 +76,5 @@ test('transcript-one load', async t => { // JSON.stringify(newstates[j]))); t.deepEqual(states.slice(i), newstates); } - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-vattp.js b/packages/SwingSet/test/test-vattp.js index 6dd3aa45754..ad0b1c6d11f 100644 --- a/packages/SwingSet/test/test-vattp.js +++ b/packages/SwingSet/test/test-vattp.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { buildVatController } from '../src/index'; import { buildMailboxStateMap, buildMailbox } from '../src/devices/mailbox'; @@ -20,7 +20,7 @@ test('vattp', async t => { await c.run(); t.deepEqual(s.exportToData(), {}); - t.equal( + t.is( mb.deliverInbound( 'remote1', [ @@ -39,7 +39,7 @@ test('vattp', async t => { ]); t.deepEqual(s.exportToData(), { remote1: { outbox: [], inboundAck: 2 } }); - t.equal( + t.is( mb.deliverInbound( 'remote1', [ @@ -53,7 +53,7 @@ test('vattp', async t => { await c.run(); t.deepEqual(s.exportToData(), { remote1: { outbox: [], inboundAck: 2 } }); - t.end(); + return; // t.end(); }); test('vattp 2', async t => { @@ -75,19 +75,19 @@ test('vattp 2', async t => { remote1: { outbox: [[1, 'out1']], inboundAck: 0 }, }); - t.equal(mb.deliverInbound('remote1', [], 1), true); + t.is(mb.deliverInbound('remote1', [], 1), true); await c.run(); t.deepEqual(c.dump().log, []); t.deepEqual(s.exportToData(), { remote1: { outbox: [], inboundAck: 0 } }); - t.equal(mb.deliverInbound('remote1', [[1, 'msg1']], 1), true); + t.is(mb.deliverInbound('remote1', [[1, 'msg1']], 1), true); await c.run(); t.deepEqual(c.dump().log, ['ch.receive msg1']); t.deepEqual(s.exportToData(), { remote1: { outbox: [], inboundAck: 1 } }); - t.equal(mb.deliverInbound('remote1', [[1, 'msg1']], 1), false); + t.is(mb.deliverInbound('remote1', [[1, 'msg1']], 1), false); - t.equal( + t.is( mb.deliverInbound( 'remote1', [ @@ -102,5 +102,5 @@ test('vattp 2', async t => { t.deepEqual(c.dump().log, ['ch.receive msg1', 'ch.receive msg2']); t.deepEqual(s.exportToData(), { remote1: { outbox: [], inboundAck: 2 } }); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/test-vpid-kernel.js b/packages/SwingSet/test/test-vpid-kernel.js index c3ef37f7a72..1f2d3ca44a5 100644 --- a/packages/SwingSet/test/test-vpid-kernel.js +++ b/packages/SwingSet/test/test-vpid-kernel.js @@ -2,7 +2,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { initSwingStore } from '@agoric/swing-store-simple'; import { waitUntilQuiescent } from '../src/waitUntilQuiescent'; @@ -238,7 +238,7 @@ async function doTest123(t, which, mode) { dataPromiseB = 'p-61'; syscallA.send(rootBvatA, 'one', capargs([slot0arg], [exportedP1VatA])); p1kernel = clistVatToKernel(kernel, vatA, exportedP1VatA); - t.equal(p1kernel, expectedP1kernel); + t.is(p1kernel, expectedP1kernel); await kernel.run(); t.deepEqual(logB.shift(), { @@ -253,7 +253,7 @@ async function doTest123(t, which, mode) { syscallB.subscribe(importedP1VatB); await kernel.run(); t.deepEqual(logB, []); - t.equal(inCList(kernel, vatB, p1kernel, importedP1VatB), true); + t.is(inCList(kernel, vatB, p1kernel, importedP1VatB), true); } else if (which === 2) { // 2: Bob sends a message to Alice, Alice resolves the result promise // B: alice~.one() @@ -314,7 +314,7 @@ async function doTest123(t, which, mode) { } // before resolution, A's c-list should have the promise - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), true); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), true); const targetsA = { target2: rootBvatA, @@ -334,15 +334,15 @@ async function doTest123(t, which, mode) { if (expectRetirement) { // after resolution, A's c-list should *not* have the promise - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), false); - t.equal(clistKernelToVat(kernel, vatA, p1kernel), undefined); - t.equal(clistVatToKernel(kernel, vatA, p1VatA), undefined); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), false); + t.is(clistKernelToVat(kernel, vatA, p1kernel), undefined); + t.is(clistVatToKernel(kernel, vatA, p1VatA), undefined); } else { - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), true); - t.equal(clistKernelToVat(kernel, vatA, p1kernel), p1VatA); - t.equal(clistVatToKernel(kernel, vatA, p1VatA), p1kernel); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), true); + t.is(clistKernelToVat(kernel, vatA, p1kernel), p1VatA); + t.is(clistVatToKernel(kernel, vatA, p1VatA), p1kernel); } - t.end(); + return; // t.end(); } // uncomment this when debugging specific problems // test.only(`XX`, async t => { @@ -411,7 +411,7 @@ async function doTest4567(t, which, mode) { dataPromiseA = 'p-61'; syscallB.send(rootAvatB, 'one', capargs([slot0arg], [exportedP1VatB])); p1kernel = clistVatToKernel(kernel, vatB, exportedP1VatB); - t.equal(p1kernel, expectedP1kernel); + t.is(p1kernel, expectedP1kernel); await kernel.run(); t.deepEqual(logA.shift(), { @@ -507,7 +507,7 @@ async function doTest4567(t, which, mode) { } // before resolution, A's c-list should have the promise - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), true); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), true); // Now bob resolves it. We want to examine the kernel's c-lists at the // moment the notification is delivered to Alice. We only expect one @@ -519,7 +519,7 @@ async function doTest4567(t, which, mode) { }; onDispatchCallback = function odc1(d) { t.deepEqual(d, resolutionOf(p1VatA, mode, targetsA)); - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), !expectRetirement); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), !expectRetirement); }; const targetsB = { target2: rootAvatB, @@ -535,15 +535,15 @@ async function doTest4567(t, which, mode) { if (expectRetirement) { // after resolution, A's c-list should *not* have the promise - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), false); - t.equal(clistKernelToVat(kernel, vatA, p1kernel), undefined); - t.equal(clistVatToKernel(kernel, vatA, p1VatA), undefined); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), false); + t.is(clistKernelToVat(kernel, vatA, p1kernel), undefined); + t.is(clistVatToKernel(kernel, vatA, p1VatA), undefined); } else { - t.equal(inCList(kernel, vatA, p1kernel, p1VatA), true); - t.equal(clistKernelToVat(kernel, vatA, p1kernel), p1VatA); - t.equal(clistVatToKernel(kernel, vatA, p1VatA), p1kernel); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), true); + t.is(clistKernelToVat(kernel, vatA, p1kernel), p1VatA); + t.is(clistVatToKernel(kernel, vatA, p1VatA), p1kernel); } - t.end(); + return; // t.end(); } for (const caseNum of [4, 5, 6, 7]) { diff --git a/packages/SwingSet/test/test-vpid-liveslots.js b/packages/SwingSet/test/test-vpid-liveslots.js index 2b51f260f93..82635b25794 100644 --- a/packages/SwingSet/test/test-vpid-liveslots.js +++ b/packages/SwingSet/test/test-vpid-liveslots.js @@ -2,7 +2,7 @@ /* global setImmediate harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { E } from '@agoric/eventual-send'; import { makePromiseKit } from '@agoric/promise-kit'; @@ -262,7 +262,7 @@ async function doVatResolveCase1(t, mode) { } t.deepEqual(log, []); - t.end(); + return; // t.end(); } for (const mode of modes) { @@ -457,7 +457,7 @@ async function doVatResolveCase23(t, which, mode, stalls) { // for(let l of log) { // console.log(l); // } - // return t.end(); + // returnreturn; // t.end(); // Now liveslots processes the callback ("notifySuccess", mapped to a // function returned by "thenResolve") that got pushed when p0 was @@ -539,19 +539,19 @@ async function doVatResolveCase23(t, which, mode, stalls) { // assert that the vat saw the local promise being resolved too if (mode === 'presence') { - t.equal(resolutionOfP1.toString(), `[Presence ${target2}]`); + t.is(resolutionOfP1.toString(), `[Presence ${target2}]`); } else if (mode === 'data') { - t.equal(resolutionOfP1, 4); + t.is(resolutionOfP1, 4); } else if (mode === 'promise-data') { - t.equal(Array.isArray(resolutionOfP1), true); - t.equal(resolutionOfP1.length, 1); + t.is(Array.isArray(resolutionOfP1), true); + t.is(resolutionOfP1.length, 1); t.is(resolutionOfP1[0], Promise.resolve(resolutionOfP1[0])); t.is(resolutionOfP1[0], stashP1); } else if (mode === 'reject') { - t.equal(resolutionOfP1, 'rejected'); + t.is(resolutionOfP1, 'rejected'); } - t.end(); + return; // t.end(); } // uncomment this when debugging specific problems @@ -705,7 +705,7 @@ async function doVatResolveCase4(t, mode) { // if p1 rejects or resolves to data, the kernel never hears about four() t.deepEqual(log, []); - t.end(); + return; // t.end(); } for (const mode of modes) { diff --git a/packages/SwingSet/test/timer-device/test-device.js b/packages/SwingSet/test/timer-device/test-device.js index 9a8c96f6ff5..59a2e1db432 100644 --- a/packages/SwingSet/test/timer-device/test-device.js +++ b/packages/SwingSet/test/timer-device/test-device.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { buildVatController } from '../../src/index'; import { buildTimer } from '../../src/devices/timer'; @@ -22,7 +22,7 @@ test('wake', async t => { timer.poll(5); await c.step(); t.deepEqual(c.dump().log, ['starting wake test', 'handler.wake()']); - t.end(); + return; // t.end(); }); test('repeater', async t => { @@ -45,7 +45,7 @@ test('repeater', async t => { 'starting repeater test', 'handler.wake(3) called 1 times.', ]); - t.end(); + return; // t.end(); }); test('repeater2', async t => { @@ -71,7 +71,7 @@ test('repeater2', async t => { 'handler.wake(3) called 1 times.', 'handler.wake(7) called 2 times.', ]); - t.end(); + return; // t.end(); }); test('repeaterZero', async t => { @@ -112,5 +112,5 @@ test('repeaterZero', async t => { 'handler.wake(6) called 2 times.', 'handler.wake(9) called 3 times.', ]); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/vat-admin/test-innerVat.js b/packages/SwingSet/test/vat-admin/test-innerVat.js index d8aed23fe6d..bb728f03511 100644 --- a/packages/SwingSet/test/vat-admin/test-innerVat.js +++ b/packages/SwingSet/test/vat-admin/test-innerVat.js @@ -29,7 +29,7 @@ test('VatAdmin inner vat creation', async t => { await c.step(); } t.deepEqual(c.dump().log, ['starting newVat test', '13']); - t.end(); + return; // t.end(); }); test('VatAdmin counter test', async t => { @@ -37,7 +37,7 @@ test('VatAdmin counter test', async t => { await c.run(); await c.run(); t.deepEqual(c.dump().log, ['starting counter test', '4', '9', '2']); - t.end(); + return; // t.end(); }); test('VatAdmin broken vat creation', async t => { @@ -47,7 +47,7 @@ test('VatAdmin broken vat creation', async t => { 'starting brokenVat test', 'yay, rejected: Error: Vat Creation Error: ReferenceError: missing is not defined', ]); - t.end(); + return; // t.end(); }); test('error creating vat from non-bundle', async t => { @@ -58,7 +58,7 @@ test('error creating vat from non-bundle', async t => { 'yay, rejected: Error: Vat Creation Error: Error: createVatDynamically() requires bundle, not a plain string', ]); await c.run(); - t.end(); + return; // t.end(); }); test('VatAdmin get vat stats', async t => { @@ -71,5 +71,5 @@ test('VatAdmin get vat stats', async t => { '{"objectCount":0,"promiseCount":2,"deviceCount":0,"transcriptCount":2}', ]); await c.run(); - t.end(); + return; // t.end(); }); diff --git a/packages/SwingSet/test/workers/test-worker.js b/packages/SwingSet/test/workers/test-worker.js index 316119b5d16..17e7a93f815 100644 --- a/packages/SwingSet/test/workers/test-worker.js +++ b/packages/SwingSet/test/workers/test-worker.js @@ -8,10 +8,10 @@ tap.test('nodeWorker vat manager', async t => { const c = await buildVatController(config, []); await c.run(); - t.equal(c.bootstrapResult.status(), 'fulfilled'); + t.is(c.bootstrapResult.status(), 'fulfilled'); await c.shutdown(); - t.end(); + return; // t.end(); }); tap.test('node-subprocess vat manager', async t => { @@ -20,8 +20,8 @@ tap.test('node-subprocess vat manager', async t => { const c = await buildVatController(config, []); await c.run(); - t.equal(c.bootstrapResult.status(), 'fulfilled'); + t.is(c.bootstrapResult.status(), 'fulfilled'); await c.shutdown(); - t.end(); + return; // t.end(); }); diff --git a/packages/acorn-eventual-send/test/test-rollup.js b/packages/acorn-eventual-send/test/test-rollup.js index 54d36d6e70f..0415afe961b 100644 --- a/packages/acorn-eventual-send/test/test-rollup.js +++ b/packages/acorn-eventual-send/test/test-rollup.js @@ -1,4 +1,4 @@ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { rollup } from 'rollup/dist/rollup.es'; import * as acorn from 'acorn'; import eventualSend from '..'; @@ -11,10 +11,10 @@ test('SwingSet bug', async t => { external: [], acornInjectPlugins: [eventualSend(acorn)], }); - t.ok(bundle); + t.assert(bundle); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/acorn-eventual-send/test/test.js b/packages/acorn-eventual-send/test/test.js index bba5661a4fd..07629edad58 100644 --- a/packages/acorn-eventual-send/test/test.js +++ b/packages/acorn-eventual-send/test/test.js @@ -1,4 +1,4 @@ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import * as acorn from 'acorn'; import eventualSend from '..'; diff --git a/packages/agoric-cli/integration-tests/test-workflow.js b/packages/agoric-cli/integration-tests/test-workflow.js index f0b73c9627c..07ba4999e03 100644 --- a/packages/agoric-cli/integration-tests/test-workflow.js +++ b/packages/agoric-cli/integration-tests/test-workflow.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import fs from 'fs'; import tmp from 'tmp'; import { makePromiseKit } from '@agoric/promise-kit'; @@ -92,12 +92,12 @@ test('workflow', async t => { // ============== // agoric init dapp-foo - t.equals(await myMain(['init', 'dapp-foo']), 0, 'init dapp-foo works'); + t.is(await myMain(['init', 'dapp-foo']), 0, 'init dapp-foo works'); process.chdir('dapp-foo'); // ============== // agoric install - t.equals(await myMain(['install']), 0, 'install works'); + t.is(await myMain(['install']), 0, 'install works'); // ============== // agoric start --reset @@ -119,7 +119,7 @@ test('workflow', async t => { } let timeout = setTimeout(startResult.resolve, 60000, 'timeout'); - t.equals( + t.is( await startResult.promise, true, `swingset running before timeout`, @@ -139,7 +139,7 @@ test('workflow', async t => { timeout = setTimeout(deployResult.resolve, 60000, 'timeout'); const done = await Promise.race([deployResult.promise, deployP]); - t.equals(done, 0, `deploy successful before timeout`); + t.is(done, 0, `deploy successful before timeout`); clearTimeout(timeout); for (const [suffix, code] of [ @@ -161,7 +161,7 @@ test('workflow', async t => { // eslint-disable-next-line no-await-in-loop const urlDone = await urlP; clearTimeout(urlTimeout); - t.equals(urlDone, code, `${url} gave status ${code}`); + t.is(urlDone, code, `${url} gave status ${code}`); } // ============== @@ -171,7 +171,7 @@ test('workflow', async t => { cwd: 'ui', detached: true, }); - t.equals(instRet, 0, `cd ui && yarn install succeeded`); + t.is(instRet, 0, `cd ui && yarn install succeeded`); // ============== // cd ui && yarn start @@ -211,7 +211,7 @@ test('workflow', async t => { console.error('cannot make request', e); } }, 3000); - t.equals( + t.is( await Promise.race([uiStartP, uiListening.promise]), 'listening', `cd ui && yarn start succeeded`, @@ -224,8 +224,8 @@ test('workflow', async t => { removeCallback(); } } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/agoric-cli/test/test-main.js b/packages/agoric-cli/test/test-main.js index bb3268f8961..74db2f2fc43 100644 --- a/packages/agoric-cli/test/test-main.js +++ b/packages/agoric-cli/test/test-main.js @@ -1,5 +1,5 @@ /* global globalThis */ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import '@agoric/install-ses'; import fs from 'fs'; import anylogger from 'anylogger'; @@ -28,13 +28,13 @@ test('sanity', async t => { globalThis.console = oldConsole; } }; - t.equal(await myMain(['help']), 0, 'help exits zero'); - t.equal(await myMain(['--help']), 0, '--help exits zero'); - t.equal(await myMain(['--version']), 0, '--version exits zero'); - t.equal(await myMain(['zorgar']), 1, 'unknown command fails'); + t.is(await myMain(['help']), 0, 'help exits zero'); + t.is(await myMain(['--help']), 0, '--help exits zero'); + t.is(await myMain(['--version']), 0, '--version exits zero'); + t.is(await myMain(['zorgar']), 1, 'unknown command fails'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/assert/test/test-assert.js b/packages/assert/test/test-assert.js index 13aecc6d577..d8b2ae2581a 100644 --- a/packages/assert/test/test-assert.js +++ b/packages/assert/test/test-assert.js @@ -5,20 +5,20 @@ import { throwsAndLogs } from './throwsAndLogs'; test('an', t => { try { - t.equal(an('object'), 'an object'); - t.equal(an('function'), 'a function'); + t.is(an('object'), 'an object'); + t.is(an('function'), 'a function'); // does not treat an initial 'y' as a vowel - t.equal(an('yaml file'), 'a yaml file'); + t.is(an('yaml file'), 'a yaml file'); // recognize upper case vowels - t.equal(an('Object'), 'an Object'); + t.is(an('Object'), 'an Object'); // coerce non-objects to strings. // non-letters are treated as non-vowels - t.equal(an({}), 'a [object Object]'); + t.is(an({}), 'a [object Object]'); } catch (e) { console.log('unexpected exception', e); t.assert(false, e); } finally { - t.end(); + return; // t.end(); } }); @@ -39,7 +39,7 @@ test('throwsAndLogs', t => { console.log('unexpected exception', e); t.assert(false, e); } finally { - t.end(); + return; // t.end(); } }); @@ -64,7 +64,7 @@ test('assert', t => { console.log('unexpected exception', e); t.assert(false, e); } finally { - t.end(); + return; // t.end(); } }); @@ -98,7 +98,7 @@ test('assert equals', t => { console.log('unexpected exception', e); t.assert(false, e); } finally { - t.end(); + return; // t.end(); } }); @@ -118,7 +118,7 @@ test('assert typeof', t => { console.log('unexpected exception', e); t.assert(false, e); } finally { - t.end(); + return; // t.end(); } }); @@ -164,6 +164,6 @@ test('assert q', t => { console.log('unexpected exception', e); t.assert(false, e); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/bundle-source/test/circular.js b/packages/bundle-source/test/circular.js index 79d24f227fe..d6574a969bb 100644 --- a/packages/bundle-source/test/circular.js +++ b/packages/bundle-source/test/circular.js @@ -1,7 +1,7 @@ /* global Compartment */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '..'; function evaluate(src, endowments) { diff --git a/packages/bundle-source/test/sanity.js b/packages/bundle-source/test/sanity.js index f5e7c9ceba8..1f3865175fb 100644 --- a/packages/bundle-source/test/sanity.js +++ b/packages/bundle-source/test/sanity.js @@ -1,7 +1,7 @@ /* global Compartment */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '..'; function evaluate(src, endowments) { diff --git a/packages/bundle-source/test/test-comment.js b/packages/bundle-source/test/test-comment.js index 1272a528db2..4ca813aa70f 100644 --- a/packages/bundle-source/test/test-comment.js +++ b/packages/bundle-source/test/test-comment.js @@ -1,6 +1,6 @@ /* global Compartment */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '..'; function evaluate(src, endowments) { @@ -24,15 +24,15 @@ test('trailing comment', async t => { const ex1 = nestedEvaluate(srcMap1)(); // console.log(err.stack); - t.equals( + t.is( typeof ex1.buildRootObject, 'function', `buildRootObject is exported`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -54,9 +54,9 @@ test('comment block opener', async t => { const srcMap1 = `(${src1})`; nestedEvaluate(srcMap1)(); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -78,8 +78,8 @@ test('comment block closer', async t => { const srcMap1 = `(${src1})`; nestedEvaluate(srcMap1)(); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/bundle-source/test/test-external-fs.js b/packages/bundle-source/test/test-external-fs.js index e6d25d416c7..2835a820263 100644 --- a/packages/bundle-source/test/test-external-fs.js +++ b/packages/bundle-source/test/test-external-fs.js @@ -1,6 +1,6 @@ /* global Compartment */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '..'; function evaluate(src, endowments) { @@ -16,7 +16,7 @@ test(`external require('fs')`, async t => { 'nestedEvaluate', ); - const myRequire = mod => t.equals(mod, 'fs', 'required fs module'); + const myRequire = mod =>t.is(mod, 'fs', 'required fs module'); const nestedEvaluate = src => { // console.log('========== evaluating', src); @@ -26,8 +26,8 @@ test(`external require('fs')`, async t => { const srcMap1 = `(${src1})`; nestedEvaluate(srcMap1)(); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/bundle-source/test/tildot-transform.js b/packages/bundle-source/test/tildot-transform.js index fa006135e18..1136399ce7b 100644 --- a/packages/bundle-source/test/tildot-transform.js +++ b/packages/bundle-source/test/tildot-transform.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '..'; test('tildot transform', async t => { diff --git a/packages/captp/test/crosstalk.js b/packages/captp/test/crosstalk.js index 9a74ac89959..c97cf149914 100644 --- a/packages/captp/test/crosstalk.js +++ b/packages/captp/test/crosstalk.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeCapTP, E } from '../lib/captp'; test('prevent crosstalk', async t => { diff --git a/packages/captp/test/disco.js b/packages/captp/test/disco.js index da4f775a198..f843c3c0e1d 100644 --- a/packages/captp/test/disco.js +++ b/packages/captp/test/disco.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { E, makeCapTP } from '../lib/captp'; test('try disconnecting captp', async t => { diff --git a/packages/captp/test/loopback.js b/packages/captp/test/loopback.js index ce9f786a347..85dd3fefe19 100644 --- a/packages/captp/test/loopback.js +++ b/packages/captp/test/loopback.js @@ -1,7 +1,7 @@ /* global harden */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { E, makeCapTP } from '../lib/captp'; test('try loopback captp', async t => { diff --git a/packages/cosmic-swingset/test/test-home.js b/packages/cosmic-swingset/test/test-home.js index 57219c94ff6..61f5057a446 100644 --- a/packages/cosmic-swingset/test/test-home.js +++ b/packages/cosmic-swingset/test/test-home.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '@agoric/bundle-source'; import { makeFixture, E } from './captp-fixture'; @@ -13,9 +13,9 @@ test('setup', async t => { teardown = kill; home = await homeP; } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -26,42 +26,42 @@ test('home.registry', async t => { try { const { registry } = E.G(home); const regVal = await E(registry).get('foolobr_19191'); - t.equals(regVal, undefined, 'random registry name is undefined'); + t.is(regVal, undefined, 'random registry name is undefined'); const target = 'something'; const myRegKey = await E(registry).register('myname', target); - t.equals(typeof myRegKey, 'string', 'registry key is string'); + t.is(typeof myRegKey, 'string', 'registry key is string'); const registered = await E(registry).get(myRegKey); - t.equals(registered, target, 'registry registers target'); + t.is(registered, target, 'registry registers target'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); test('home.board', async t => { try { const { board } = E.G(home); - t.rejects( + t.throwsAsync( () => E(board).getValue('0000000000'), `getting a value for a fake id throws`, ); const myValue = {}; const myId = await E(board).getId(myValue); - t.equals(typeof myId, 'string', `board key is string`); + t.is(typeof myId, 'string', `board key is string`); const valueInBoard = await E(board).getValue(myId); - t.deepEquals(valueInBoard, myValue, `board contains myValue`); + t.deepEqual(valueInBoard, myValue, `board contains myValue`); const myId2 = await E(board).getId(myValue); - t.equals(myId2, myId, `board gives the same id for the same value`); + t.is(myId2, myId, `board gives the same id for the same value`); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -82,14 +82,14 @@ test('home.wallet - receive zoe invite', async t => { const zoeInviteIssuer = await E(zoe).getInvitationIssuer(); const issuers = await E(wallet).getIssuers(); const issuersMap = new Map(issuers); - t.deepEquals( + t.deepEqual( issuersMap.get('zoe invite'), zoeInviteIssuer, `wallet knows about the Zoe invite issuer`, ); const invitePurse = await E(wallet).getPurse('Default Zoe invite purse'); const zoeInviteBrand = await E(invitePurse).getAllegedBrand(); - t.equals( + t.is( zoeInviteBrand, await E(zoeInviteIssuer).getBrand(), `invite purse is actually a zoe invite purse`, @@ -107,15 +107,15 @@ test('home.wallet - receive zoe invite', async t => { // The invite was successfully received in the user's wallet. const invitePurseBalance = await E(invitePurse).getCurrentAmount(); - t.equals( + t.is( invitePurseBalance.value[0].description, 'getRefund', `invite successfully deposited`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -125,8 +125,8 @@ test('teardown', async t => { try { await teardown(); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/cosmic-swingset/test/test-make.js b/packages/cosmic-swingset/test/test-make.js index e722aeb8cc6..c51dad84272 100644 --- a/packages/cosmic-swingset/test/test-make.js +++ b/packages/cosmic-swingset/test/test-make.js @@ -1,4 +1,4 @@ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { spawn } from 'child_process'; test('make', async t => { @@ -8,13 +8,13 @@ test('make', async t => { cwd: `${__dirname}/..`, stdio: ['ignore', 'ignore', 'inherit'], }).addListener('exit', code => { - t.equal(code, 0, 'exits successfully'); + t.is(code, 0, 'exits successfully'); resolve(); }), ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/cosmic-swingset/test/test-scenario3-setup.js b/packages/cosmic-swingset/test/test-scenario3-setup.js index 2899650a77f..c8dc5232f68 100644 --- a/packages/cosmic-swingset/test/test-scenario3-setup.js +++ b/packages/cosmic-swingset/test/test-scenario3-setup.js @@ -1,4 +1,4 @@ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { spawn } from 'child_process'; test('make scenario3-setup', async t => { @@ -8,13 +8,13 @@ test('make scenario3-setup', async t => { cwd: `${__dirname}/..`, stdio: ['ignore', 'ignore', 'inherit'], }).addListener('exit', code => { - t.equal(code, 0, 'exits successfully'); + t.is(code, 0, 'exits successfully'); resolve(); }), ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/cosmic-swingset/test/unitTests/test-lib-board.js b/packages/cosmic-swingset/test/unitTests/test-lib-board.js index 768b36aee06..6185cfb8549 100644 --- a/packages/cosmic-swingset/test/unitTests/test-lib-board.js +++ b/packages/cosmic-swingset/test/unitTests/test-lib-board.js @@ -2,7 +2,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeBoard } from '../../lib/ag-solo/vats/lib-board'; @@ -13,22 +13,22 @@ test('makeBoard', async t => { const obj1 = harden({}); const obj2 = harden({}); - t.deepEquals(board.ids(), [], `board is empty to start`); + t.deepEqual(board.ids(), [], `board is empty to start`); const idObj1 = board.getId(obj1); - t.deepEquals(board.ids(), [idObj1], `board has one id`); + t.deepEqual(board.ids(), [idObj1], `board has one id`); const idObj2 = board.getId(obj2); - t.deepEquals(board.ids().length, 2, `board has two ids`); + t.deepEqual(board.ids().length, 2, `board has two ids`); - t.deepEquals(board.getValue(idObj1), obj1, `id matches value obj1`); - t.deepEquals(board.getValue(idObj2), obj2, `id matches value obj2`); + t.deepEqual(board.getValue(idObj1), obj1, `id matches value obj1`); + t.deepEqual(board.getValue(idObj2), obj2, `id matches value obj2`); - t.deepEquals(board.getId(obj1), idObj1, `value matches id obj1`); - t.deepEquals(board.getId(obj2), idObj2, `value matches id obj2`); + t.deepEqual(board.getId(obj1), idObj1, `value matches id obj1`); + t.deepEqual(board.getId(obj2), idObj2, `value matches id obj2`); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/cosmic-swingset/test/unitTests/test-repl.js b/packages/cosmic-swingset/test/unitTests/test-repl.js index db738d332aa..56a14a925b8 100644 --- a/packages/cosmic-swingset/test/unitTests/test-repl.js +++ b/packages/cosmic-swingset/test/unitTests/test-repl.js @@ -2,7 +2,7 @@ import '@agoric/install-ses'; import { makeTransform } from '@agoric/transform-eventual-send'; import * as babelParser from '@agoric/babel-parser'; import babelGenerate from '@babel/generator'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { getReplHandler } from '../../lib/ag-solo/vats/repl'; function make() { @@ -31,46 +31,46 @@ test('repl: basic eval, eventual promise resolution', async t => { const { doEval, sentMessages, getHighestHistory } = make(); let m = sentMessages.shift(); - t.deepEquals(m.type, 'updateHistory'); - t.equals(sentMessages.length, 0); + t.deepEqual(m.type, 'updateHistory'); + t.is(sentMessages.length, 0); - t.deepEquals(getHighestHistory(), { highestHistory: -1 }); - t.equals(sentMessages.length, 0); + t.deepEqual(getHighestHistory(), { highestHistory: -1 }); + t.is(sentMessages.length, 0); - t.deepEquals(doEval(0, '1+2'), {}); + t.deepEqual(doEval(0, '1+2'), {}); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 0); - t.equals(m.display, 'working on eval(1+2)'); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 0); + t.is(m.display, 'working on eval(1+2)'); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 0); - t.equals(m.display, '3'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 0); + t.is(m.display, '3'); + t.deepEqual(sentMessages, []); // exercise eventual promise resolution - t.deepEquals(doEval(1, 'Promise.resolve(3)'), {}); + t.deepEqual(doEval(1, 'Promise.resolve(3)'), {}); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, 'working on eval(Promise.resolve(3))'); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, 'working on eval(Promise.resolve(3))'); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, 'unresolved Promise'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, 'unresolved Promise'); + t.deepEqual(sentMessages, []); await Promise.resolve(); await Promise.resolve(); // I don't know why two stalls are needed m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, '3'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, '3'); + t.deepEqual(sentMessages, []); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); throw e; } finally { - t.end(); + return; // t.end(); } }); @@ -79,38 +79,38 @@ test('repl: sloppyGlobals, home, endowments', async t => { const { doEval, sentMessages } = make(); let m = sentMessages.shift(); - t.deepEquals(m.type, 'updateHistory'); - t.equals(sentMessages.length, 0); + t.deepEqual(m.type, 'updateHistory'); + t.is(sentMessages.length, 0); - t.deepEquals( + t.deepEqual( doEval(0, 'newGlobal = home.base + home.fries + home.cooking'), {}, ); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 0); - t.ok(m.display.startsWith('working on eval(newGlobal')); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 0); + t.assert(m.display.startsWith('working on eval(newGlobal')); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 0); - t.equals(m.display, '6'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 0); + t.is(m.display, '6'); + t.deepEqual(sentMessages, []); - t.deepEquals(doEval(1, 'newGlobal'), {}); + t.deepEqual(doEval(1, 'newGlobal'), {}); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.ok(m.display.startsWith('working on eval(newGlobal')); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.assert(m.display.startsWith('working on eval(newGlobal')); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, '6'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, '6'); + t.deepEqual(sentMessages, []); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); throw e; } finally { - t.end(); + return; // t.end(); } }); @@ -119,44 +119,44 @@ test('repl: tildot', async t => { const { doEval, sentMessages } = make(); let m = sentMessages.shift(); - t.deepEquals(m.type, 'updateHistory'); - t.equals(sentMessages.length, 0); + t.deepEqual(m.type, 'updateHistory'); + t.is(sentMessages.length, 0); - t.deepEquals(doEval(0, 'target = harden({ foo(x) { return x+1; } })'), {}); + t.deepEqual(doEval(0, 'target = harden({ foo(x) { return x+1; } })'), {}); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 0); - t.ok(m.display.startsWith('working on eval(target =')); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 0); + t.assert(m.display.startsWith('working on eval(target =')); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 0); - t.equals(m.display, '{"foo":[Function foo]}'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 0); + t.is(m.display, '{"foo":[Function foo]}'); + t.deepEqual(sentMessages, []); - t.deepEquals(doEval(1, 'target~.foo(2)'), {}); + t.deepEqual(doEval(1, 'target~.foo(2)'), {}); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, 'working on eval(target~.foo(2))'); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, 'working on eval(target~.foo(2))'); m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, 'unresolved Promise'); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, 'unresolved Promise'); await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); // I don't know why three stalls are needed m = sentMessages.shift(); - t.equals(m.type, 'updateHistory'); - t.equals(m.histnum, 1); - t.equals(m.display, '3'); - t.deepEquals(sentMessages, []); + t.is(m.type, 'updateHistory'); + t.is(m.histnum, 1); + t.is(m.display, '3'); + t.deepEqual(sentMessages, []); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); throw e; } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/dapp-svelte-wallet/api/test/test-lib-dehydrate.js b/packages/dapp-svelte-wallet/api/test/test-lib-dehydrate.js index f06575dda0e..89aecde4157 100644 --- a/packages/dapp-svelte-wallet/api/test/test-lib-dehydrate.js +++ b/packages/dapp-svelte-wallet/api/test/test-lib-dehydrate.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // calls lockdown() // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeDehydrator } from '../src/lib-dehydrate'; @@ -20,24 +20,24 @@ test('makeDehydrator', async t => { console.log(`ERROR EXPECTED 'already has a petname' >>>>`); t.throws( () => instanceHandleMapping.addPetname('simpleExchange2', handle1), - `cannot add a second petname for the same value`, + { message: `cannot add a second petname for the same value` }, ); console.log( `ERROR EXPECTED 'petname simpleExchange is already in use' >>>>`, ); t.throws( () => instanceHandleMapping.addPetname('simpleExchange', harden({})), - `cannot add another value for the same petname`, + { message: `cannot add another value for the same petname` }, ); // Test renaming. instanceHandleMapping.renamePetname('whatever', handle1); - t.equals( + t.is( instanceHandleMapping.valToPetname.get(handle1), 'whatever', `renaming is successful going from val to petname`, ); - t.deepEquals( + t.deepEqual( instanceHandleMapping.petnameToVal.get('whatever'), handle1, `renaming is successful going from val to petname`, @@ -47,17 +47,17 @@ test('makeDehydrator', async t => { ); t.throws( () => instanceHandleMapping.renamePetname('new value', harden({})), - /has not been previously named, would you like to add it instead\?/, + { message: /has not been previously named, would you like to add it instead\?/ }, `can't rename something that was never added`, ); // rename it back instanceHandleMapping.renamePetname('simpleExchange', handle1); - t.equals( + t.is( instanceHandleMapping.valToPetname.get(handle1), 'simpleExchange', `second renaming is successful going from val to petname`, ); - t.deepEquals( + t.deepEqual( instanceHandleMapping.petnameToVal.get('simpleExchange'), handle1, `second renaming is successful going from val to petname`, @@ -66,12 +66,12 @@ test('makeDehydrator', async t => { // Test deletion. const temp = harden({}); instanceHandleMapping.addPetname('to be deleted', temp); - t.equals( + t.is( instanceHandleMapping.valToPetname.get(temp), 'to be deleted', `'to be deleted' present going from val to petname`, ); - t.deepEquals( + t.deepEqual( instanceHandleMapping.petnameToVal.get('to be deleted'), handle1, `'to be deleted' present going from val to petname`, @@ -80,7 +80,7 @@ test('makeDehydrator', async t => { console.log(`ERROR EXPECTED '"petname" not found' >>>>`); t.throws( () => instanceHandleMapping.petnameToVal.get('to be deleted'), - /"petname" not found/, + { message: /"petname" not found/ }, `can't get what has been deleted`, ); @@ -95,7 +95,7 @@ test('makeDehydrator', async t => { const brand3 = makeMockBrand(); brandMapping.addPetname('moola', brand1); brandMapping.addPath(['agoric', 'Moola'], brand1); - t.deepEquals( + t.deepEqual( brandMapping.valToPaths.get(brand1), [['agoric', 'Moola']], `use valToPaths`, @@ -103,7 +103,7 @@ test('makeDehydrator', async t => { brandMapping.addPetname('simolean', brand2); brandMapping.addPetname('zoeInvite', brand3); - t.deepEquals( + t.deepEqual( dehydrate(harden({ handle: handle1 })), { body: '{"handle":{"@qclass":"slot","index":0}}', @@ -111,7 +111,7 @@ test('makeDehydrator', async t => { }, `serialize val with petname`, ); - t.deepEquals( + t.deepEqual( hydrate( harden({ body: '{"handle":{"@qclass":"slot","index":0}}', @@ -126,7 +126,7 @@ test('makeDehydrator', async t => { harden({ handle: handle1 }), `deserialize val with petname`, ); - t.deepEquals( + t.deepEqual( dehydrate(harden({ brand: brand1, value: 40 })), harden({ body: '{"brand":{"@qclass":"slot","index":0},"value":40}', @@ -134,7 +134,7 @@ test('makeDehydrator', async t => { }), `serialize brand with petname`, ); - t.deepEquals( + t.deepEqual( hydrate( harden({ body: '{"brand":{"@qclass":"slot","index":0},"value":40}', @@ -159,7 +159,7 @@ test('makeDehydrator', async t => { }, }, }); - t.deepEquals( + t.deepEqual( dehydrate(proposal), { body: @@ -174,7 +174,7 @@ test('makeDehydrator', async t => { }, `dehydrated proposal`, ); - t.deepEquals( + t.deepEqual( hydrate( harden({ body: @@ -192,7 +192,7 @@ test('makeDehydrator', async t => { `hydrated proposal`, ); const handle4 = harden({}); - t.deepEquals( + t.deepEqual( dehydrate(harden({ handle: handle4 })), { body: '{"handle":{"@qclass":"slot","index":0}}', @@ -200,7 +200,7 @@ test('makeDehydrator', async t => { }, `serialize val with no petname`, ); - t.deepEquals( + t.deepEqual( hydrate( harden({ body: '{"handle":{"@qclass":"slot","index":0}}', @@ -212,7 +212,7 @@ test('makeDehydrator', async t => { ); // Name a previously unnamed handle instanceHandleMapping.addPetname('autoswap', handle4); - t.deepEquals( + t.deepEqual( dehydrate(harden({ handle: handle4 })), { body: '{"handle":{"@qclass":"slot","index":0}}', @@ -220,7 +220,7 @@ test('makeDehydrator', async t => { }, `serialize val with new petname`, ); - t.deepEquals( + t.deepEqual( hydrate( harden({ body: '{"handle":{"@qclass":"slot","index":0}}', @@ -242,7 +242,7 @@ test('makeDehydrator', async t => { { handle: handle4 }, `deserialize with no slots does not produce the real object`, ); - t.deepEquals( + t.deepEqual( hydrate( harden({ body: '{"handle":{"kind":"instanceHandle","petname":"autoswap"}}', @@ -255,8 +255,8 @@ test('makeDehydrator', async t => { `deserialize with no slots does not produce the real object`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js index 3d5b894c6d6..1cb370568b0 100644 --- a/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/test/test-lib-wallet.js @@ -1,7 +1,7 @@ // @ts-check import '@agoric/install-ses'; // calls lockdown() // eslint-disable-next-line import/no-extraneous-dependencies -import test from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '@agoric/bundle-source'; import makeAmountMath from '@agoric/ertp/src/amountMath'; @@ -103,14 +103,14 @@ test('lib-wallet issuer and purse methods', async t => { pursesStateChangeLog, } = await setupTest(); const inviteIssuer = await E(zoe).getInvitationIssuer(); - t.deepEquals( + t.deepEqual( wallet.getIssuers(), [['zoe invite', inviteIssuer]], `wallet starts off with only the zoe invite issuer`, ); await wallet.addIssuer('moola', moolaBundle.issuer); await wallet.addIssuer('rpg', rpgBundle.issuer); - t.deepEquals( + t.deepEqual( wallet.getIssuers(), [ ['zoe invite', inviteIssuer], @@ -120,7 +120,7 @@ test('lib-wallet issuer and purse methods', async t => { `two issuers added`, ); const issuersMap = new Map(wallet.getIssuers()); - t.equals( + t.is( issuersMap.get('moola'), moolaBundle.issuer, `can get issuer by issuer petname`, @@ -129,19 +129,19 @@ test('lib-wallet issuer and purse methods', async t => { const ZOE_INVITE_PURSE_PETNAME = 'Default Zoe invite purse'; const invitePurse = wallet.getPurse(ZOE_INVITE_PURSE_PETNAME); - t.deepEquals( + t.deepEqual( wallet.getPurses(), [['Default Zoe invite purse', invitePurse]], `starts off with only the invite purse`, ); await wallet.makeEmptyPurse('moola', 'fun money'); const moolaPurse = wallet.getPurse('fun money'); - t.deepEquals( + t.deepEqual( await moolaPurse.getCurrentAmount(), moolaBundle.amountMath.getEmpty(), `empty purse is empty`, ); - t.deepEquals( + t.deepEqual( wallet.getPurses(), [ ['Default Zoe invite purse', invitePurse], @@ -149,7 +149,7 @@ test('lib-wallet issuer and purse methods', async t => { ], `two purses currently`, ); - t.deepEquals( + t.deepEqual( wallet.getPurseIssuer('fun money'), moolaBundle.issuer, `can get issuer from purse petname`, @@ -158,13 +158,13 @@ test('lib-wallet issuer and purse methods', async t => { moolaBundle.amountMath.make(100), ); await wallet.deposit('fun money', moolaPayment); - t.deepEquals( + t.deepEqual( await moolaPurse.getCurrentAmount(), moolaBundle.amountMath.make(100), `deposit successful`, ); - t.equals(pursesStateChangeLog.length, 4, `pursesStateChangeLog length`); - t.deepEquals( + t.is(pursesStateChangeLog.length, 4, `pursesStateChangeLog length`); + t.deepEqual( JSON.parse(pursesStateChangeLog[pursesStateChangeLog.length - 1]), [ { @@ -199,9 +199,9 @@ test('lib-wallet issuer and purse methods', async t => { ], `pursesStateChangeLog`, ); - t.deepEquals(inboxStateChangeLog, [], `inboxStateChangeLog`); + t.deepEqual(inboxStateChangeLog, [], `inboxStateChangeLog`); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -223,7 +223,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t const bucksIssuerBoardId = await E(board).getId(bucksIssuer); await wallet.suggestIssuer('bucks', bucksIssuerBoardId); - t.equals( + t.is( wallet.getIssuer('bucks'), bucksIssuer, `bucksIssuer is stored in wallet`, @@ -277,7 +277,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t wallet.deposit('Default Zoe invite purse', invite2); const currentAmount = await E(zoeInvitePurse).getCurrentAmount(); - t.deepEquals( + t.deepEqual( currentAmount.value, [ { @@ -293,7 +293,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t const zoeInvitePurseState = JSON.parse( pursesStateChangeLog[pursesStateChangeLog.length - 2], ); - t.deepEquals( + t.deepEqual( zoeInvitePurseState[0], { brandBoardId: '3812059', @@ -339,7 +339,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t automaticRefundInstanceBoardId, ); - t.equals( + t.is( wallet.getInstance('automaticRefund'), instance, `automaticRefund instance stored in wallet`, @@ -353,7 +353,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t automaticRefundInstallationBoardId, ); - t.equals( + t.is( wallet.getInstallation('automaticRefund'), installation, `automaticRefund installation stored in wallet`, @@ -364,7 +364,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t ); await wallet.suggestInstallation('autoswap', autoswapInstallationBoardId); - t.equals( + t.is( wallet.getInstallation('autoswap'), autoswapInstallationHandle, `autoswap installation stored in wallet`, @@ -374,7 +374,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t pursesStateChangeLog[pursesStateChangeLog.length - 1], )[0]; - t.deepEquals( + t.deepEqual( zoeInvitePurseState2, { brandBoardId: '3812059', @@ -420,7 +420,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t const inboxState1 = JSON.parse( inboxStateChangeLog[inboxStateChangeLog.length - 1], )[0]; - t.deepEquals( + t.deepEqual( inboxState1, { id: 'unknown#1588645041696', @@ -439,18 +439,18 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t console.log('EXPECTED ERROR ->>> "petname" not found'); t.throws( () => wallet.getInstallation('whatever'), - /"petname" not found/, + { message: /"petname" not found/ }, `using a petname that doesn't exist errors`, ); - await t.doesNotReject( + await t.notThrowsAsync( wallet.suggestInstallation('autoswap', autoswapInstallationBoardId), `resuggesting a petname doesn't error`, ); await wallet.renameInstallation('automaticRefund2', installation); - t.equals( + t.is( wallet.getInstallation('automaticRefund2'), installation, `automaticRefund installation renamed in wallet`, @@ -460,7 +460,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t // by the time we check it. // TODO: check the pursesState changes in a less fragile way. const currentAmount2 = await E(zoeInvitePurse).getCurrentAmount(); - t.deepEquals( + t.deepEqual( currentAmount2.value, [ { @@ -477,7 +477,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t pursesStateChangeLog[pursesStateChangeLog.length - 1], )[0]; - t.deepEquals( + t.deepEqual( zoeInvitePurseState3, { brandBoardId: '3812059', @@ -522,7 +522,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t const inboxState2 = JSON.parse( inboxStateChangeLog[inboxStateChangeLog.length - 1], )[0]; - t.deepEquals( + t.deepEqual( inboxState2, { id: 'unknown#1588645041696', @@ -538,7 +538,7 @@ test('lib-wallet dapp suggests issuer, instance, installation petnames', async t `inboxStateChangeLog with new names`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -599,7 +599,7 @@ test('lib-wallet offer methods', async t => { await wallet.addOffer(offer); - t.deepEquals( + t.deepEqual( wallet.getOffers(), [ { @@ -620,13 +620,13 @@ test('lib-wallet offer methods', async t => { const accepted = await wallet.acceptOffer(id); assert(accepted); const { outcome, depositedP } = accepted; - t.equals(await outcome, 'The offer was accepted', `offer was accepted`); + t.is(await outcome, 'The offer was accepted', `offer was accepted`); await depositedP; const seats = wallet.getSeats(harden([id])); const seat = wallet.getSeat(id); - t.equals(seat, seats[0], `both getSeat(s) methods work`); + t.is(seat, seats[0], `both getSeat(s) methods work`); const moolaPurse = wallet.getPurse('Fun budget'); - t.deepEquals( + t.deepEqual( await moolaPurse.getCurrentAmount(), moolaBundle.amountMath.make(100), `moolaPurse balance`, @@ -649,10 +649,10 @@ test('lib-wallet offer methods', async t => { // simpleExchange const zoeInvitePurse = await E(wallet).getPurse('Default Zoe invite purse'); const zoePurseAmount = await E(zoeInvitePurse).getCurrentAmount(); - t.deepEquals(zoePurseAmount.value, [], `zoeInvitePurse balance`); + t.deepEqual(zoePurseAmount.value, [], `zoeInvitePurse balance`); const lastPurseState = JSON.parse(pursesStateChangeLog.pop()); const [zoeInvitePurseState, moolaPurseState] = lastPurseState; - t.deepEquals( + t.deepEqual( zoeInvitePurseState, { brandBoardId: '3812059', @@ -671,7 +671,7 @@ test('lib-wallet offer methods', async t => { }, `zoeInvitePurseState`, ); - t.deepEquals( + t.deepEqual( moolaPurseState, { brandBoardId: '16679794', @@ -690,7 +690,7 @@ test('lib-wallet offer methods', async t => { `moolaPurseState`, ); const lastInboxState = JSON.parse(inboxStateChangeLog.pop()); - t.deepEquals( + t.deepEqual( lastInboxState, [ { @@ -757,7 +757,7 @@ test('lib-wallet offer methods', async t => { `inboxStateChangeLog`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -882,7 +882,7 @@ test('lib-wallet addOffer for autoswap swap', async t => { const accepted = await wallet.acceptOffer(id); assert(accepted); const { outcome, depositedP } = accepted; - t.equals( + t.is( await outcome, 'Swap successfully completed.', `offer was accepted`, @@ -890,18 +890,18 @@ test('lib-wallet addOffer for autoswap swap', async t => { await depositedP; const seats = wallet.getSeats(harden([id])); const seat = wallet.getSeat(id); - t.equals(seat, seats[0], `both getSeat(s) methods work`); - t.deepEquals( + t.is(seat, seats[0], `both getSeat(s) methods work`); + t.deepEqual( await moolaPurse.getCurrentAmount(), moolaBundle.amountMath.make(70), `moola purse balance`, ); - t.deepEquals( + t.deepEqual( await simoleanPurse.getCurrentAmount(), simoleanBundle.amountMath.make(516), `simolean purse balance`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); diff --git a/packages/dapp-svelte-wallet/contract/test/test-contract.js b/packages/dapp-svelte-wallet/contract/test/test-contract.js index 2bff01bf06c..bc30d9023b7 100644 --- a/packages/dapp-svelte-wallet/contract/test/test-contract.js +++ b/packages/dapp-svelte-wallet/contract/test/test-contract.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -46,7 +46,7 @@ test('contract with valid offers', async t => { // want to check more extensively, const installedBundle = await E(zoe).getInstallation(installationHandle); const code = installedBundle.source; - t.ok( + t.assert( code.includes(`This contract provides encouragement. `), `the code installed passes a quick check of what we intended to install`, ); @@ -74,7 +74,7 @@ test('contract with valid offers', async t => { // Check that we received an invite as the result of making the // contract instance. - t.ok( + t.assert( await E(inviteIssuer).isLive(adminInvite), `an valid invite (an ERTP payment) was created`, ); @@ -87,7 +87,7 @@ test('contract with valid offers', async t => { completeObj: { complete: completeAdmin }, } = await E(zoe).offer(adminInvite); - t.equals( + t.is( await adminOutcomeP, `admin invite redeemed`, `admin outcome is correct`, @@ -106,9 +106,9 @@ test('contract with valid offers', async t => { const nextUpdateP = E(notifier).getUpdateSince(updateCount); // Count starts at 0 - t.equals(value.count, 0, `count starts at 0`); + t.is(value.count, 0, `count starts at 0`); - t.deepEquals( + t.deepEqual( value.messages, harden({ basic: `You're doing great!`, @@ -122,7 +122,7 @@ test('contract with valid offers', async t => { const { outcome: encouragementP } = await E(zoe).offer(encouragementInvite); - t.equals( + t.is( await encouragementP, `You're doing great!`, `encouragement matches expected`, @@ -130,7 +130,7 @@ test('contract with valid offers', async t => { // Getting encouragement resolves the 'nextUpdateP' promise nextUpdateP.then(async result => { - t.equals(result.value.count, 1, 'count increments by 1'); + t.is(result.value.count, 1, 'count increments by 1'); // Now, let's get a premium encouragement message const encouragementInvite2 = await E(publicAPI).makeInvite(); @@ -144,24 +144,24 @@ test('contract with valid offers', async t => { paymentKeywordRecord, ); - t.equals( + t.is( await secondEncouragementP, `Wow, just wow. I have never seen such talent!`, `premium message is as expected`, ); const newResult = await E(notifier).getUpdateSince(); - t.deepEquals(newResult.value.count, 2, `count is now 2`); + t.deepEqual(newResult.value.count, 2, `count is now 2`); // Let's get our Tips completeAdmin(); Promise.resolve(E.G(adminPayoutP).Tip).then(tip => { bucksIssuer.getAmountOf(tip).then(tipAmount => { - t.deepEquals(tipAmount, bucks5, `payout is 5 bucks, all the tips`); + t.deepEqual(tipAmount, bucks5, `payout is 5 bucks, all the tips`); }); }); }); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); diff --git a/packages/eventual-send/integration-test/test/utility/test-bundler.js b/packages/eventual-send/integration-test/test/utility/test-bundler.js index d10bc94fcd4..5e94f4d6e05 100644 --- a/packages/eventual-send/integration-test/test/utility/test-bundler.js +++ b/packages/eventual-send/integration-test/test/utility/test-bundler.js @@ -1,7 +1,7 @@ /* eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies */ import puppeteer from 'puppeteer'; /* eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies */ -import test from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; @@ -42,8 +42,8 @@ const testBundler = (bundlerName, indexFile) => { test(`HandledPromise works with ${bundlerName}`, t => { runBrowserTests(indexFile).then(({ numTests, numPass }) => { t.notEqual(numTests, undefined); - t.equal(numTests, numPass); - t.end(); + t.is(numTests, numPass); + return; // t.end(); }); }); }; diff --git a/packages/eventual-send/test/test-e.js b/packages/eventual-send/test/test-e.js index e14ae4cdb9f..6479794165c 100644 --- a/packages/eventual-send/test/test-e.js +++ b/packages/eventual-send/test/test-e.js @@ -1,14 +1,14 @@ import '@agoric/install-ses'; -import test from 'tape-promise/tape'; +import test from 'ava'; import { E, HandledPromise } from '../src/index'; test('E reexports', async t => { try { - t.equals(E.resolve, HandledPromise.resolve, 'E reexports resolve'); + t.is(E.resolve, HandledPromise.resolve, 'E reexports resolve'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -16,7 +16,7 @@ test('E.when', async t => { try { let stash; await E.when(123, val => (stash = val)); - t.equals(stash, 123, `onfulfilled handler fires`); + t.is(stash, 123, `onfulfilled handler fires`); let raised; // eslint-disable-next-line prefer-promise-reject-errors await E.when(Promise.reject('foo'), undefined, val => (raised = val)); @@ -29,8 +29,8 @@ test('E.when', async t => { val => (ret = val), val => (exc = val), ); - t.equals(ret, 'foo', 'onfulfilled option fires'); - t.equals(exc, undefined, 'onrejected option does not fire'); + t.is(ret, 'foo', 'onfulfilled option fires'); + t.is(exc, undefined, 'onrejected option does not fire'); let ret2; let exc2; @@ -40,12 +40,12 @@ test('E.when', async t => { val => (ret2 = val), val => (exc2 = val), ); - t.equals(ret2, undefined, 'onfulfilled option does not fire'); - t.equals(exc2, 'foo', 'onrejected option fires'); + t.is(ret2, undefined, 'onfulfilled option does not fire'); + t.is(exc2, 'foo', 'onrejected option fires'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -57,12 +57,12 @@ test('E method calls', async t => { }, }; const d = E(x).double(6); - t.equal(typeof d.then, 'function', 'return is a thenable'); - t.equal(await d, 12, 'method call works'); + t.is(typeof d.then, 'function', 'return is a thenable'); + t.is(await d, 12, 'method call works'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -80,16 +80,16 @@ test('E shortcuts', async t => { return `${greeting}, ${this.name}!`; }, }; - t.equal(await E(x).hello('Hello'), 'Hello, buddy!', 'method call works'); - t.equal( + t.is(await E(x).hello('Hello'), 'Hello, buddy!', 'method call works'); + t.is( await E(await E.G(await E.G(x).y).fn)(4), 8, 'anonymous method works', ); - t.equal(await E.G(x).val, 123, 'property get'); + t.is(await E.G(x).val, 123, 'property get'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/eventual-send/test/test-hp.js b/packages/eventual-send/test/test-hp.js index d03adbc2cdb..7b1ef0a4185 100644 --- a/packages/eventual-send/test/test-hp.js +++ b/packages/eventual-send/test/test-hp.js @@ -1,4 +1,4 @@ -import test from 'tape-promise/tape'; +import test from 'ava'; import { HandledPromise } from '../src/index'; test('chained properties', async t => { @@ -40,9 +40,9 @@ test('chained properties', async t => { ); await pr.p; } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -71,7 +71,7 @@ test('no local stalls', async t => { log.push(`end of turn 3`); await Promise.resolve(); - t.deepEquals( + t.deepEqual( log, [ 'calling 1', diff --git a/packages/eventual-send/test/test-thenable.js b/packages/eventual-send/test/test-thenable.js index 39ed15fbb38..548756ff48d 100644 --- a/packages/eventual-send/test/test-thenable.js +++ b/packages/eventual-send/test/test-thenable.js @@ -1,4 +1,4 @@ -import test from 'tape-promise/tape'; +import test from 'ava'; import { E, HandledPromise } from '../src/index'; test('E.resolve is always asynchronous', async t => { @@ -9,11 +9,11 @@ test('E.resolve is always asynchronous', async t => { let thened = false; const p2 = E.resolve(p).then(ret => (thened = ret)); t.is(thened, false, `p2 is not yet resolved`); - t.equal(await p2, 'done', `p2 is resolved`); + t.is(await p2, 'done', `p2 is resolved`); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -25,10 +25,10 @@ test('HandledPromise.resolve is always asynchronous', async t => { let thened = false; const p2 = HandledPromise.resolve(p).then(ret => (thened = ret)); t.is(thened, false, `p2 is not yet resolved`); - t.equal(await p2, 'done', `p2 is resolved`); + t.is(await p2, 'done', `p2 is resolved`); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/eventual-send/test/test.js b/packages/eventual-send/test/test.js index e850402b575..4aa27ab6774 100644 --- a/packages/eventual-send/test/test.js +++ b/packages/eventual-send/test/test.js @@ -1,4 +1,4 @@ -import test from 'tape-promise/tape'; +import test from 'ava'; import { HandledPromise } from '../src/index'; const { getPrototypeOf } = Object; diff --git a/packages/import-bundle/test/test-compartment-wrapper.js b/packages/import-bundle/test/test-compartment-wrapper.js index f6eaf7cf1e5..0761ea0b436 100644 --- a/packages/import-bundle/test/test-compartment-wrapper.js +++ b/packages/import-bundle/test/test-compartment-wrapper.js @@ -72,52 +72,52 @@ const attemptResetByTransform = `() => { }`; function check(t, c, odometer, n) { - t.equal(odometer.read(), 0, `${n}.start`); + t.is(odometer.read(), 0, `${n}.start`); c.evaluate(doAdd); - t.equal(odometer.read(), 1, `${n}.doAdd`); + t.is(odometer.read(), 1, `${n}.doAdd`); odometer.reset(); c.evaluate(doAddInChild)(doAdd); - t.equal(odometer.read(), 1, `${n}.doAddInChild`); + t.is(odometer.read(), 1, `${n}.doAddInChild`); odometer.reset(); odometer.add(5); t.throws( () => c.evaluate(attemptReset)(), - /forbidden access/, + { message: /forbidden access/ }, `${n}.attemptReset`, ); - t.equal(odometer.read(), 5, `${n} not reset`); + t.is(odometer.read(), 5, `${n} not reset`); t.throws( () => c.evaluate(attemptResetInChild)(attemptReset), - /forbidden access/, + { message: /forbidden access/ }, `${n}.attemptResetInChild`, ); - t.equal(odometer.read(), 5, `${n} not reset`); + t.is(odometer.read(), 5, `${n} not reset`); odometer.reset(); const fakeCalled = c.evaluate(attemptResetByShadow)(doAdd); - t.notOk(fakeCalled, `${n}.attemptResetByShadow`); - t.equal(odometer.read(), 1, `${n} called anyway`); + t.falsy(fakeCalled, `${n}.attemptResetByShadow`); + t.is(odometer.read(), 1, `${n} called anyway`); odometer.reset(); odometer.add(5); t.throws( () => c.evaluate(attemptResetByTransform)(), - /forbidden access/, + { message: /forbidden access/ }, `${n}.attemptResetByTransform`, ); - t.equal(odometer.read(), 5, `${n} not reset`); + t.is(odometer.read(), 5, `${n} not reset`); odometer.reset(); - t.equal( + t.is( c.evaluate('Compartment.name'), 'Compartment', `${n}.Compartment.name`, ); - t.ok(c instanceof Compartment, `${n} instanceof`); + t.assert(c instanceof Compartment, `${n} instanceof`); const Con = Object.getPrototypeOf(c.globalThis.Compartment).constructor; t.throws(() => new Con(), /Not available/, `${n} .constructor is tamed`); @@ -146,5 +146,5 @@ test('wrap', t => { const c3 = c2.evaluate(createChild)(); check(t, c3, odometer, 'c3'); - t.end(); + return; // t.end(); }); diff --git a/packages/import-bundle/test/test-import-bundle.js b/packages/import-bundle/test/test-import-bundle.js index d76750b8210..7c06290463d 100644 --- a/packages/import-bundle/test/test-import-bundle.js +++ b/packages/import-bundle/test/test-import-bundle.js @@ -78,7 +78,7 @@ tap.test('test import', async function testImport(t) { ); await testBundle1(b1NestedEvaluate, 'nestedEvaluate', endowments); - t.end(); + return; // t.end(); }); tap.test('test missing sourceMap', async function testImport(t) { @@ -95,7 +95,7 @@ tap.test('test missing sourceMap', async function testImport(t) { delete b1.sourceMap; const ns1 = await importBundle(b1, { endowments }); tap.equal(ns1.f1(1), 2, `missing sourceMap ns.f1 ok`); - t.end(); + return; // t.end(); }); tap.test('inescapable transforms', async function testInescapableTransforms(t) { @@ -114,7 +114,7 @@ tap.test('inescapable transforms', async function testInescapableTransforms(t) { inescapableTransforms: [transform1], }); tap.equal(ns.f4('is ok'), 'substitution is ok', `iT ns.f4 ok`); - t.end(); + return; // t.end(); }); tap.test( @@ -136,6 +136,6 @@ tap.test( }); tap.equal(ns.f3(1), 4, `iGL ns.f3 ok`); - t.end(); + return; // t.end(); }, ); diff --git a/packages/import-manager/test/unitTests/test-importsA.js b/packages/import-manager/test/unitTests/test-importsA.js index 56e8b1b1b42..f11bfc8e879 100644 --- a/packages/import-manager/test/unitTests/test-importsA.js +++ b/packages/import-manager/test/unitTests/test-importsA.js @@ -1,15 +1,15 @@ // Copyright (C) 2019 Agoric, under Apache License 2.0 import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeGoodImportManager } from './goodImports'; test('import num is not empty', t => { const importer = makeGoodImportManager(); const name = 'numIsEmpty'; const emptyFn = importer[name]; - t.notOk(emptyFn(30)); - t.end(); + t.falsy(emptyFn(30)); + return; // t.end(); }); test('import num is empty', t => { @@ -17,21 +17,21 @@ test('import num is empty', t => { const name = 'numIsEmpty'; const emptyFn = importer[name]; t.assert(emptyFn(0)); - t.end(); + return; // t.end(); }); test('import listIsEmpty (false)', t => { const importer = makeGoodImportManager(); const op = 'listIsEmpty'; - t.notok(importer[op]([20])); - t.end(); + t.falsy(importer[op]([20])); + return; // t.end(); }); test('import listIsEmpty (true)', t => { const importer = makeGoodImportManager(); const op = 'listIsEmpty'; t.assert(importer[op]([])); - t.end(); + return; // t.end(); }); // TODO: This test throws because `lookupImport` does not exist. This @@ -40,7 +40,7 @@ test.skip('import not found', t => { const importer = makeGoodImportManager(); t.throws( () => importer.lookupImport('emptyPixel'), - /There is no entry for "c"./, + { message: /There is no entry for "c"./ }, ); - t.end(); + return; // t.end(); }); diff --git a/packages/install-metering-and-ses/test-install-metering-and-ses.js b/packages/install-metering-and-ses/test-install-metering-and-ses.js index 43d3e294612..8d6901d8fe1 100644 --- a/packages/install-metering-and-ses/test-install-metering-and-ses.js +++ b/packages/install-metering-and-ses/test-install-metering-and-ses.js @@ -1,6 +1,6 @@ /* global Compartment harden */ import './install-metering-and-ses'; -import test from 'tape'; +import test from 'ava'; import { makeMeter } from '@agoric/transform-metering'; // I don't know how to test that replaceGlobalMeter already exists, because @@ -16,15 +16,15 @@ import { tameMetering } from '@agoric/tame-metering'; const replaceGlobalMeter = tameMetering(); test('SES globals are present', t => { - t.equal(typeof Compartment, 'function'); - t.equal(typeof harden, 'function'); - t.end(); + t.is(typeof Compartment, 'function'); + t.is(typeof harden, 'function'); + return; // t.end(); }); test('can replaceGlobalMeter', t => { const { meter } = makeMeter(); const oldMeter = replaceGlobalMeter(meter); - t.isNot(meter, oldMeter); + t.not(meter, oldMeter); // provoke some globals const a = []; a.length = 10; @@ -33,5 +33,5 @@ test('can replaceGlobalMeter', t => { t.is(meter, newMeter); // TODO: once meters provide an API to read out their value, assert that // the value changed at all - t.end(); + return; // t.end(); }); diff --git a/packages/install-ses/test-install-ses.js b/packages/install-ses/test-install-ses.js index d0153337232..18c65ba8fed 100644 --- a/packages/install-ses/test-install-ses.js +++ b/packages/install-ses/test-install-ses.js @@ -1,9 +1,9 @@ /* global Compartment harden */ import './install-ses'; -import test from 'tape'; +import test from 'ava'; test('globals are present', t => { - t.equal(typeof Compartment, 'function'); - t.equal(typeof harden, 'function'); - t.end(); + t.is(typeof Compartment, 'function'); + t.is(typeof harden, 'function'); + return; // t.end(); }); diff --git a/packages/marshal/test/test-marshal.js b/packages/marshal/test/test-marshal.js index 3a41741572a..b826fab55dc 100644 --- a/packages/marshal/test/test-marshal.js +++ b/packages/marshal/test/test-marshal.js @@ -1,7 +1,7 @@ /* global harden BigInt */ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { Remotable, getInterfaceOf, @@ -81,22 +81,22 @@ test('serialize static data', t => { }); const cd = ser(harden([1, 2])); - t.equal(Object.isFrozen(cd), true); - t.equal(Object.isFrozen(cd.slots), true); + t.is(Object.isFrozen(cd), true); + t.is(Object.isFrozen(cd.slots), true); - t.end(); + return; // t.end(); }); test('unserialize static data', t => { const m = makeMarshal(); const uns = body => m.unserialize({ body, slots: [] }); - t.equal(uns('1'), 1); - t.equal(uns('"abc"'), 'abc'); - t.equal(uns('false'), false); + t.is(uns('1'), 1); + t.is(uns('"abc"'), 'abc'); + t.is(uns('false'), false); // JS primitives that aren't natively representable by JSON t.deepEqual(uns('{"@qclass":"undefined"}'), undefined); - t.ok(Object.is(uns('{"@qclass":"NaN"}'), NaN)); + t.assert(Object.is(uns('{"@qclass":"NaN"}'), NaN)); t.deepEqual(uns('{"@qclass":"Infinity"}'), Infinity); t.deepEqual(uns('{"@qclass":"-Infinity"}'), -Infinity); @@ -118,17 +118,17 @@ test('unserialize static data', t => { const em1 = uns( '{"@qclass":"error","name":"ReferenceError","message":"msg"}', ); - t.ok(em1 instanceof ReferenceError); - t.equal(em1.message, 'msg'); - t.ok(Object.isFrozen(em1)); + t.assert(em1 instanceof ReferenceError); + t.is(em1.message, 'msg'); + t.assert(Object.isFrozen(em1)); const em2 = uns('{"@qclass":"error","name":"TypeError","message":"msg2"}'); - t.ok(em2 instanceof TypeError); - t.equal(em2.message, 'msg2'); + t.assert(em2 instanceof TypeError); + t.is(em2.message, 'msg2'); const em3 = uns('{"@qclass":"error","name":"Unknown","message":"msg3"}'); - t.ok(em3 instanceof Error); - t.equal(em3.message, 'msg3'); + t.assert(em3 instanceof Error); + t.is(em3.message, 'msg3'); t.deepEqual(uns('[1,2]'), [1, 2]); t.deepEqual(uns('{"a":1,"b":2}'), { a: 1, b: 2 }); @@ -136,14 +136,14 @@ test('unserialize static data', t => { // should be frozen const arr = uns('[1,2]'); - t.ok(Object.isFrozen(arr)); + t.assert(Object.isFrozen(arr)); const a = uns('{"b":{"c":{"d": []}}}'); - t.ok(Object.isFrozen(a)); - t.ok(Object.isFrozen(a.b)); - t.ok(Object.isFrozen(a.b.c)); - t.ok(Object.isFrozen(a.b.c.d)); + t.assert(Object.isFrozen(a)); + t.assert(Object.isFrozen(a.b)); + t.assert(Object.isFrozen(a.b.c)); + t.assert(Object.isFrozen(a.b.c.d)); - t.end(); + return; // t.end(); }); test('serialize ibid cycle', t => { @@ -157,7 +157,7 @@ test('serialize ibid cycle', t => { body: '["a",{"@qclass":"ibid","index":0},"c"]', slots: [], }); - t.end(); + return; // t.end(); }); test('forbid ibid cycle', t => { @@ -165,63 +165,63 @@ test('forbid ibid cycle', t => { const uns = body => m.unserialize({ body, slots: [] }); t.throws( () => uns('["a",{"@qclass":"ibid","index":0},"c"]'), - /Ibid cycle at 0/, + { message: /Ibid cycle at 0/ }, ); - t.end(); + return; // t.end(); }); test('unserialize ibid cycle', t => { const m = makeMarshal(); const uns = body => m.unserialize({ body, slots: [] }, 'warnOfCycles'); const cycle = uns('["a",{"@qclass":"ibid","index":0},"c"]'); - t.ok(Object.is(cycle[1], cycle)); - t.end(); + t.assert(Object.is(cycle[1], cycle)); + return; // t.end(); }); test('null cannot be pass-by-presence', t => { t.throws(() => mustPassByPresence(null), /null cannot be pass-by-remote/); - t.end(); + return; // t.end(); }); test('mal-formed @qclass', t => { const m = makeMarshal(); const uns = body => m.unserialize({ body, slots: [] }); t.throws(() => uns('{"@qclass": 0}'), /invalid qclass/); - t.end(); + return; // t.end(); }); test('Remotable/getInterfaceOf', t => { t.throws( () => Remotable({ bar: 29 }), - /unimplemented/, + { message: /unimplemented/ }, 'object ifaces are not implemented', ); t.throws( () => Remotable('MyHandle', { foo: 123 }), - /cannot serialize/, + { message: /cannot serialize/ }, 'non-function props are not implemented', ); t.throws( () => Remotable('MyHandle', {}, a => a + 1), - /cannot serialize/, + { message: /cannot serialize/ }, 'function presences are not implemented', ); - t.equals(getInterfaceOf('foo'), undefined, 'string, no interface'); - t.equals(getInterfaceOf(null), undefined, 'null, no interface'); - t.equals( + t.is(getInterfaceOf('foo'), undefined, 'string, no interface'); + t.is(getInterfaceOf(null), undefined, 'null, no interface'); + t.is( getInterfaceOf(a => a + 1), undefined, 'function, no interface', ); - t.equals(getInterfaceOf(123), undefined, 'number, no interface'); + t.is(getInterfaceOf(123), undefined, 'number, no interface'); // Check that a handle can be created. const p = Remotable('MyHandle'); harden(p); // console.log(p); - t.equals(getInterfaceOf(p), 'MyHandle', `interface is MyHandle`); - t.equals(`${p}`, '[MyHandle]', 'stringify is [MyHandle]'); + t.is(getInterfaceOf(p), 'MyHandle', `interface is MyHandle`); + t.is(`${p}`, '[MyHandle]', 'stringify is [MyHandle]'); const p2 = Remotable('Thing', { name() { @@ -231,8 +231,8 @@ test('Remotable/getInterfaceOf', t => { return now - 64; }, }); - t.equals(getInterfaceOf(p2), 'Thing', `interface is Thing`); - t.equals(p2.name(), 'cretin', `name() method is presence`); - t.equals(p2.birthYear(2020), 1956, `birthYear() works`); - t.end(); + t.is(getInterfaceOf(p2), 'Thing', `interface is Thing`); + t.is(p2.name(), 'cretin', `name() method is presence`); + t.is(p2.birthYear(2020), 1956, `birthYear() works`); + return; // t.end(); }); diff --git a/packages/notifier/test/test-notifier-adaptor.js b/packages/notifier/test/test-notifier-adaptor.js index c6ccd517651..ff9953e5c94 100644 --- a/packages/notifier/test/test-notifier-adaptor.js +++ b/packages/notifier/test/test-notifier-adaptor.js @@ -1,6 +1,6 @@ // @ts-check import '@agoric/install-ses'; -import test from 'tape-promise/tape'; +import test from 'ava'; import { makeAsyncIterableFromNotifier, makeNotifierFromAsyncIterable, @@ -44,14 +44,14 @@ const explodingStream = makeIterable(true); const testEnding = (t, p, fails) => { return Promise.resolve(p).then( result => { - t.equal(fails, false); - t.equal(result, refResult); - return t.end(); + t.is(fails, false); + t.is(result, refResult); + returnreturn; // t.end(); }, reason => { - t.equal(fails, true); - t.equal(reason, refReason); - return t.end(); + t.is(fails, true); + t.is(reason, refReason); + returnreturn; // t.end(); }, ); }; @@ -72,7 +72,7 @@ const testManualConsumer = (t, iterable, lossy) => { return iterator.next().then( ({ value, done }) => { if (done) { - t.equal(i, payloads.length); + t.is(i, payloads.length); return value; } i = skip(i, value, lossy); @@ -82,7 +82,7 @@ const testManualConsumer = (t, iterable, lossy) => { return testLoop(i + 1); }, reason => { - t.equal(i, payloads.length); + t.is(i, payloads.length); throw reason; }, ); @@ -101,7 +101,7 @@ const testAutoConsumer = async (t, iterable, lossy) => { i += 1; } } finally { - t.equal(i, payloads.length); + t.is(i, payloads.length); } // The for-await-of loop cannot observe the final value of the iterator // so this consumer cannot test what that was. Just return what testEnding @@ -120,14 +120,14 @@ const makeTestUpdater = (t, lossy, fails) => { i += 1; }, finish(finalState) { - t.equal(fails, false); - t.equal(finalState, refResult); - return t.end(); + t.is(fails, false); + t.is(finalState, refResult); + returnreturn; // t.end(); }, fail(reason) { - t.equal(fails, true); - t.equal(reason, refReason); - return t.end(); + t.is(fails, true); + t.is(reason, refReason); + returnreturn; // t.end(); }, }); }; diff --git a/packages/notifier/test/test-notifier.js b/packages/notifier/test/test-notifier.js index 6e6461fb986..5329ddefdb5 100644 --- a/packages/notifier/test/test-notifier.js +++ b/packages/notifier/test/test-notifier.js @@ -1,7 +1,7 @@ // @ts-check import '@agoric/install-ses'; -import test from 'tape-promise/tape'; +import test from 'ava'; import { makeNotifierKit } from '../src/index'; import '../src/types'; @@ -14,9 +14,9 @@ test('notifier - initial state', async t => { const updateDeNovo = await notifier.getUpdateSince(); const updateFromNonExistent = await notifier.getUpdateSince(); - t.equals(updateDeNovo.value, 1, 'state is one'); - t.deepEquals(updateDeNovo, updateFromNonExistent, 'no param same as unknown'); - t.end(); + t.is(updateDeNovo.value, 1, 'state is one'); + t.deepEqual(updateDeNovo, updateFromNonExistent, 'no param same as unknown'); + return; // t.end(); }); test('notifier - single update', async t => { @@ -26,15 +26,15 @@ test('notifier - single update', async t => { updater.updateState(1); const updateDeNovo = await notifier.getUpdateSince(); - t.equals(updateDeNovo.value, 1, 'initial state is one'); + t.is(updateDeNovo.value, 1, 'initial state is one'); const updateInWaiting = notifier.getUpdateSince(updateDeNovo.updateCount); const all = Promise.all([updateInWaiting]).then(([update]) => { - t.equals(update.value, 3, 'updated state is eventually three'); + t.is(update.value, 3, 'updated state is eventually three'); }); const update2 = await notifier.getUpdateSince(); - t.equals(update2.value, 1); + t.is(update2.value, 1); updater.updateState(3); await all; }); @@ -45,15 +45,15 @@ test('notifier - initial update', async t => { const { notifier, updater } = makeNotifierKit(1); const updateDeNovo = await notifier.getUpdateSince(); - t.equals(updateDeNovo.value, 1, 'initial state is one'); + t.is(updateDeNovo.value, 1, 'initial state is one'); const updateInWaiting = notifier.getUpdateSince(updateDeNovo.updateCount); const all = Promise.all([updateInWaiting]).then(([update]) => { - t.equals(update.value, 3, 'updated state is eventually three'); + t.is(update.value, 3, 'updated state is eventually three'); }); const update2 = await notifier.getUpdateSince(); - t.equals(update2.value, 1); + t.is(update2.value, 1); updater.updateState(3); await all; }); @@ -65,19 +65,19 @@ test('notifier - update after state change', async t => { const updateDeNovo = await notifier.getUpdateSince(); const updateInWaiting = notifier.getUpdateSince(updateDeNovo.updateCount); - t.equals(updateDeNovo.value, 1, 'first state check (1)'); + t.is(updateDeNovo.value, 1, 'first state check (1)'); const all = Promise.all([updateInWaiting]).then(([update1]) => { - t.equals(update1.value, 3, '4th check (delayed) 3'); + t.is(update1.value, 3, '4th check (delayed) 3'); const thirdStatePromise = notifier.getUpdateSince(update1.updateCount); Promise.all([thirdStatePromise]).then(([update2]) => { - t.equals(update2.value, 5, '5th check (delayed) 5'); + t.is(update2.value, 5, '5th check (delayed) 5'); }); }); - t.equals((await notifier.getUpdateSince()).value, 1, '2nd check (1)'); + t.is((await notifier.getUpdateSince()).value, 1, '2nd check (1)'); updater.updateState(3); - t.equals((await notifier.getUpdateSince()).value, 3, '3rd check (3)'); + t.is((await notifier.getUpdateSince()).value, 3, '3rd check (3)'); updater.updateState(5); await all; }); @@ -89,19 +89,19 @@ test('notifier - final state', async t => { const updateDeNovo = await notifier.getUpdateSince(); const updateInWaiting = notifier.getUpdateSince(updateDeNovo.updateCount); - t.equals(updateDeNovo.value, 1, 'initial state is one'); + t.is(updateDeNovo.value, 1, 'initial state is one'); const all = Promise.all([updateInWaiting]).then(([update]) => { - t.equals(update.value, 'final', 'state is "final"'); - t.notOk(update.updateCount, 'no handle after close'); + t.is(update.value, 'final', 'state is "final"'); + t.falsy(update.updateCount, 'no handle after close'); const postFinalUpdate = notifier.getUpdateSince(update.updateCount); Promise.all([postFinalUpdate]).then(([after]) => { - t.equals(after.value, 'final', 'stable'); - t.notOk(after.updateCount, 'no handle after close'); + t.is(after.value, 'final', 'stable'); + t.falsy(after.updateCount, 'no handle after close'); }); }); const invalidHandle = await notifier.getUpdateSince(); - t.equals(invalidHandle.value, 1, 'still one'); + t.is(invalidHandle.value, 1, 'still one'); updater.finish('final'); await all; }); diff --git a/packages/registrar/test/unitTests/test-registrar.js b/packages/registrar/test/unitTests/test-registrar.js index 6ec5764ae22..bf39ac23cce 100644 --- a/packages/registrar/test/unitTests/test-registrar.js +++ b/packages/registrar/test/unitTests/test-registrar.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeRegistrar } from '../../src/registrar'; test('Registrar operations', async t => { @@ -11,26 +11,26 @@ test('Registrar operations', async t => { t.assert(id1.match(/^myname_\d{4,}$/), 'id1 is correct format'); const id2 = registrarService.register('myname', obj2); t.assert(id2.match(/^myname_\d{4,}$/), 'id2 is correct format'); - t.isNot(id2, id1, 'ids for different objects are different'); + t.not(id2, id1, 'ids for different objects are different'); const id1a = registrarService.register('myname', obj1); t.assert(id1a.match(/^myname_\d{4,}$/), 'id1a is correct format'); - t.isNot(id1a, id1, 'ids for same object are different'); + t.not(id1a, id1, 'ids for same object are different'); const id1b = registrarService.register('othername', obj1); t.assert(id1b.match(/^othername_\d{4,}$/), 'id1b is correct format'); const ret1 = registrarService.get(id1); - t.equals(ret1, obj1, 'returned obj1 is equal'); + t.is(ret1, obj1, 'returned obj1 is equal'); const ret2 = registrarService.get(id2); - t.equals(ret2, obj2, 'returned obj2 is equal'); + t.is(ret2, obj2, 'returned obj2 is equal'); const ret1a = registrarService.get(id1a); - t.equals(ret1a, obj1, 'returned obj1a is equal'); + t.is(ret1a, obj1, 'returned obj1a is equal'); const ret1b = registrarService.get(id1b); - t.equals(ret1b, obj1, 'returned obj1b is equal'); + t.is(ret1b, obj1, 'returned obj1b is equal'); - t.equals(registrarService.keys().length, 4, 'number of keys is expected'); + t.is(registrarService.keys().length, 4, 'number of keys is expected'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -44,18 +44,18 @@ test('Registrar collisions', async t => { const id = registrarService.register('a', myobj); maxlength = Math.max(maxlength, id.length); } - t.equals(maxlength, 7, 'expected maximum key length'); + t.is(maxlength, 7, 'expected maximum key length'); const keys = registrarService.keys(); - t.equals(keys.length, iterations, 'expected number of keys'); - t.equals( + t.is(keys.length, iterations, 'expected number of keys'); + t.is( keys.filter(key => registrarService.get(key) !== myobj).length, 0, 'expected no deviations', ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/sharing-service/test/swingsetTests/sharingService/test-sharing.js b/packages/sharing-service/test/swingsetTests/sharingService/test-sharing.js index 08207b06557..b129924fe30 100644 --- a/packages/sharing-service/test/swingsetTests/sharingService/test-sharing.js +++ b/packages/sharing-service/test/swingsetTests/sharingService/test-sharing.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; import { buildVatController, loadBasedir } from '@agoric/swingset-vat'; @@ -20,8 +20,8 @@ const sharedMapContentsGolden = ['starting testSharedMapStorage']; test('run sharing Demo --sharedMap contents', async t => { const dump = await main('sharingService', ['sharedMap']); - t.deepEquals(dump.log, sharedMapContentsGolden); - t.end(); + t.deepEqual(dump.log, sharedMapContentsGolden); + return; // t.end(); }); const sharingTestGolden = [ @@ -31,8 +31,8 @@ const sharingTestGolden = [ test('run sharing Demo --sharing service', async t => { const dump = await main('sharingService', ['sharing']); - t.deepEquals(dump.log, sharingTestGolden); - t.end(); + t.deepEqual(dump.log, sharingTestGolden); + return; // t.end(); }); const twoPartySharingGolden = [ @@ -42,6 +42,6 @@ const twoPartySharingGolden = [ test('run sharing Demo --Two Party handoff', async t => { const dump = await main('sharingService', ['twoVatSharing']); - t.deepEquals(dump.log, twoPartySharingGolden); - t.end(); + t.deepEqual(dump.log, twoPartySharingGolden); + return; // t.end(); }); diff --git a/packages/sharing-service/test/unitTests/test-sharing.js b/packages/sharing-service/test/unitTests/test-sharing.js index f146a678a8e..4c3d2329e20 100644 --- a/packages/sharing-service/test/unitTests/test-sharing.js +++ b/packages/sharing-service/test/unitTests/test-sharing.js @@ -1,12 +1,12 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeSharingService } from '../../src/sharing'; test('Sharing creation', t => { const sharingService = makeSharingService(); const first = sharingService.createSharedMap('first'); t.assert(sharingService.validate(first)); - t.end(); + return; // t.end(); }); test('Sharing repeated creation', t => { @@ -15,10 +15,10 @@ test('Sharing repeated creation', t => { t.assert(sharingService.validate(first)); t.throws( _ => sharingService.createSharedMap('first'), - /already exists/, + { message: /already exists/ }, 'should throw on repeated call.', ); - t.end(); + return; // t.end(); }); test('Sharing grab value', t => { @@ -28,21 +28,21 @@ test('Sharing grab value', t => { t.assert(sharingService.validate(first)); const second = sharingService.grabSharedMap(firstName); t.assert(sharingService.validate(second)); - t.equals(second, first); + t.is(second, first); t.throws( _ => sharingService.grabSharedMap(firstName), - /has already been collected/, + { message: /has already been collected/ }, 'should throw on repeated call.', ); - t.end(); + return; // t.end(); }); test('Sharing validate non service', t => { const sharingService = makeSharingService(); t.throws( _ => sharingService.validate([]), - /Unrecognized sharedMap:/, + { message: /Unrecognized sharedMap:/ }, 'throws on non sharing service.', ); - t.end(); + return; // t.end(); }); diff --git a/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js b/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js index beb22bab7ee..c3977844554 100644 --- a/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js +++ b/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js @@ -1,5 +1,5 @@ import '@agoric/install-metering-and-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import path from 'path'; import { buildVatController, loadBasedir } from '@agoric/swingset-vat'; @@ -27,8 +27,8 @@ const contractMintGolden = [ test.skip('run contractHost Demo --mint', async t => { const dump = await main('contractHost', ['mint']); - t.deepEquals(dump.log, contractMintGolden); - t.end(); + t.deepEqual(dump.log, contractMintGolden); + return; // t.end(); }); const contractTrivialGolden = [ @@ -40,14 +40,14 @@ const contractTrivialGolden = [ ]; test('run contractHost Demo --trivial', async t => { const dump = await main('contractHost', ['trivial']); - t.deepEquals(dump.log, contractTrivialGolden); - t.end(); + t.deepEqual(dump.log, contractTrivialGolden); + return; // t.end(); }); test('run contractHost Demo --trivial-oldformat', async t => { const dump = await main('contractHost', ['trivial-oldformat']); - t.deepEquals(dump.log, contractTrivialGolden); - t.end(); + t.deepEqual(dump.log, contractTrivialGolden); + return; // t.end(); }); const contractExhaustedGolden = [ @@ -59,8 +59,8 @@ const contractExhaustedGolden = [ test('run contractHost Demo -- exhaust', async t => { const dump = await main('contractHost', ['exhaust']); - t.deepEquals(dump.log, contractExhaustedGolden); - t.end(); + t.deepEqual(dump.log, contractExhaustedGolden); + return; // t.end(); }); const contractAliceFirstGolden = [ @@ -71,8 +71,8 @@ const contractAliceFirstGolden = [ test.skip('run contractHost Demo --alice-first', async t => { const dump = await main('contractHost', ['alice-first']); - t.deepEquals(dump.log, contractAliceFirstGolden); - t.end(); + t.deepEqual(dump.log, contractAliceFirstGolden); + return; // t.end(); }); const contractBobFirstGolden = [ @@ -93,8 +93,8 @@ const contractBobFirstGolden = [ test.skip('run contractHost Demo --bob-first', async t => { const dump = await main('contractHost', ['bob-first']); - t.deepEquals(dump.log, contractBobFirstGolden); - t.end(); + t.deepEqual(dump.log, contractBobFirstGolden); + return; // t.end(); }); const contractCoveredCallGolden = [ @@ -116,8 +116,8 @@ const contractCoveredCallGolden = [ test.skip('run contractHost Demo --covered-call', async t => { const dump = await main('contractHost', ['covered-call']); - t.deepEquals(dump.log, contractCoveredCallGolden); - t.end(); + t.deepEqual(dump.log, contractCoveredCallGolden); + return; // t.end(); }); const contractCoveredCallSaleGolden = [ @@ -146,6 +146,6 @@ const contractCoveredCallSaleGolden = [ test.skip('run contractHost Demo --covered-call-sale', async t => { const dump = await main('contractHost', ['covered-call-sale']); - t.deepEquals(dump.log, contractCoveredCallSaleGolden); - t.end(); + t.deepEqual(dump.log, contractCoveredCallSaleGolden); + return; // t.end(); }); diff --git a/packages/spawner/test/swingsetTests/escrow/test-escrow.js b/packages/spawner/test/swingsetTests/escrow/test-escrow.js index 72b9fbfa075..97eadd9edff 100644 --- a/packages/spawner/test/swingsetTests/escrow/test-escrow.js +++ b/packages/spawner/test/swingsetTests/escrow/test-escrow.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { buildVatController, loadBasedir } from '@agoric/swingset-vat'; import path from 'path'; @@ -20,8 +20,8 @@ const escrowGolden = ['starting testEscrowServiceSuccess']; test('escrow checkUnits w/SES', async t => { const dump = await main('escrow', ['escrow matches']); - t.deepEquals(dump.log, escrowGolden); - t.end(); + t.deepEqual(dump.log, escrowGolden); + return; // t.end(); }); const escrowMismatchGolden = [ @@ -31,8 +31,8 @@ const escrowMismatchGolden = [ test.skip('escrow check misMatches w/SES', async t => { const dump = await main('escrow', ['escrow misMatches']); - t.deepEquals(dump.log, escrowMismatchGolden); - t.end(); + t.deepEqual(dump.log, escrowMismatchGolden); + return; // t.end(); }); const escrowCheckPartialWrongPriceGolden = [ @@ -42,8 +42,8 @@ const escrowCheckPartialWrongPriceGolden = [ test.skip('escrow check partial misMatches w/SES', async t => { const dump = await main('escrow', ['escrow partial price']); - t.deepEquals(dump.log, escrowCheckPartialWrongPriceGolden); - t.end(); + t.deepEqual(dump.log, escrowCheckPartialWrongPriceGolden); + return; // t.end(); }); const escrowCheckPartialWrongStockGolden = [ @@ -53,8 +53,8 @@ const escrowCheckPartialWrongStockGolden = [ test.skip('escrow check partial misMatches w/SES', async t => { const dump = await main('escrow', ['escrow partial stock']); - t.deepEquals(dump.log, escrowCheckPartialWrongStockGolden); - t.end(); + t.deepEqual(dump.log, escrowCheckPartialWrongStockGolden); + return; // t.end(); }); const escrowCheckPartialWrongSeatGolden = [ @@ -64,6 +64,6 @@ const escrowCheckPartialWrongSeatGolden = [ test.skip('escrow check partial wrong seat w/SES', async t => { const dump = await main('escrow', ['escrow partial seat']); - t.deepEquals(dump.log, escrowCheckPartialWrongSeatGolden); - t.end(); + t.deepEqual(dump.log, escrowCheckPartialWrongSeatGolden); + return; // t.end(); }); diff --git a/packages/spawner/test/test-function-bundle.js b/packages/spawner/test/test-function-bundle.js index 799bc3be320..3fdeaa15312 100644 --- a/packages/spawner/test/test-function-bundle.js +++ b/packages/spawner/test/test-function-bundle.js @@ -1,5 +1,5 @@ import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { importBundle } from '@agoric/import-bundle'; import { bundleFunction } from './make-function-bundle'; @@ -17,6 +17,6 @@ test('bundleFunction', async t => { return 'yes'; }, }; - t.equal(ns.default('terms', inviteMaker), 'yes'); - t.end(); + t.is(ns.default('terms', inviteMaker), 'yes'); + return; // t.end(); }); diff --git a/packages/swing-store-lmdb/test/test-state.js b/packages/swing-store-lmdb/test/test-state.js index 2d64449b198..1c1e5782688 100644 --- a/packages/swing-store-lmdb/test/test-state.js +++ b/packages/swing-store-lmdb/test/test-state.js @@ -6,7 +6,7 @@ import '@agoric/install-ses'; import fs from 'fs'; import path from 'path'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { getAllState } from '@agoric/swing-store-simple'; import { @@ -16,12 +16,12 @@ import { } from '../lmdbSwingStore'; function testStorage(t, storage) { - t.notOk(storage.has('missing')); - t.equal(storage.get('missing'), undefined); + t.falsy(storage.has('missing')); + t.is(storage.get('missing'), undefined); storage.set('foo', 'f'); - t.ok(storage.has('foo')); - t.equal(storage.get('foo'), 'f'); + t.assert(storage.has('foo')); + t.is(storage.get('foo'), 'f'); storage.set('foo2', 'f2'); storage.set('foo1', 'f1'); @@ -34,8 +34,8 @@ function testStorage(t, storage) { ]); storage.delete('foo2'); - t.notOk(storage.has('foo2')); - t.equal(storage.get('foo2'), undefined); + t.falsy(storage.has('foo2')); + t.is(storage.get('foo2'), undefined); t.deepEqual(Array.from(storage.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); const reference = { @@ -48,18 +48,18 @@ function testStorage(t, storage) { test('storageInLMDB under SES', t => { fs.rmdirSync('testdb', { recursive: true }); - t.equal(isSwingStore('testdb'), false); + t.is(isSwingStore('testdb'), false); const { storage, commit, close } = initSwingStore('testdb'); testStorage(t, storage); commit(); const before = getAllState(storage); close(); - t.equal(isSwingStore('testdb'), true); + t.is(isSwingStore('testdb'), true); const { storage: after } = openSwingStore('testdb'); t.deepEqual(getAllState(after), before, 'check state after reread'); - t.equal(isSwingStore('testdb'), true); - t.end(); + t.is(isSwingStore('testdb'), true); + return; // t.end(); }); test('rejectSimple under SES', t => { @@ -69,8 +69,8 @@ test('rejectSimple under SES', t => { path.resolve(simpleDir, 'swingset-kernel-state.jsonlines'), 'some data\n', ); - t.equal(isSwingStore(simpleDir), false); - t.end(); + t.is(isSwingStore(simpleDir), false); + return; // t.end(); }); test.onFinish(() => fs.rmdirSync('testdb', { recursive: true })); diff --git a/packages/swing-store-simple/test/test-state.js b/packages/swing-store-simple/test/test-state.js index 89eae37c75a..8e2d8d7df6a 100644 --- a/packages/swing-store-simple/test/test-state.js +++ b/packages/swing-store-simple/test/test-state.js @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { initSwingStore, openSwingStore, @@ -10,12 +10,12 @@ import { } from '../simpleSwingStore'; function testStorage(t, storage) { - t.notOk(storage.has('missing')); - t.equal(storage.get('missing'), undefined); + t.falsy(storage.has('missing')); + t.is(storage.get('missing'), undefined); storage.set('foo', 'f'); - t.ok(storage.has('foo')); - t.equal(storage.get('foo'), 'f'); + t.assert(storage.has('foo')); + t.is(storage.get('foo'), 'f'); storage.set('foo2', 'f2'); storage.set('foo1', 'f1'); @@ -28,8 +28,8 @@ function testStorage(t, storage) { ]); storage.delete('foo2'); - t.notOk(storage.has('foo2')); - t.equal(storage.get('foo2'), undefined); + t.falsy(storage.has('foo2')); + t.is(storage.get('foo2'), undefined); t.deepEqual(Array.from(storage.getKeys('foo1', 'foo4')), ['foo1', 'foo3']); const reference = { @@ -43,23 +43,23 @@ function testStorage(t, storage) { test('storageInMemory', t => { const { storage } = initSwingStore(); testStorage(t, storage); - t.end(); + return; // t.end(); }); test('storageInFile', t => { fs.rmdirSync('testdb', { recursive: true }); - t.equal(isSwingStore('testdb'), false); + t.is(isSwingStore('testdb'), false); const { storage, commit, close } = initSwingStore('testdb'); testStorage(t, storage); commit(); const before = getAllState(storage); close(); - t.equal(isSwingStore('testdb'), true); + t.is(isSwingStore('testdb'), true); const { storage: after } = openSwingStore('testdb'); t.deepEqual(getAllState(after), before, 'check state after reread'); - t.equal(isSwingStore('testdb'), true); - t.end(); + t.is(isSwingStore('testdb'), true); + return; // t.end(); }); test('rejectLMDB', t => { @@ -67,8 +67,8 @@ test('rejectLMDB', t => { fs.mkdirSync(notSimpleDir, { recursive: true }); fs.writeFileSync(path.resolve(notSimpleDir, 'data.mdb'), 'some data\n'); fs.writeFileSync(path.resolve(notSimpleDir, 'lock.mdb'), 'lock stuff\n'); - t.equal(isSwingStore(notSimpleDir), false); - t.end(); + t.is(isSwingStore(notSimpleDir), false); + return; // t.end(); }); test.onFinish(() => fs.rmdirSync('testdb', { recursive: true })); diff --git a/packages/swingset-runner/test/test-demo.js b/packages/swingset-runner/test/test-demo.js index 770c64cfac8..4079e35a766 100644 --- a/packages/swingset-runner/test/test-demo.js +++ b/packages/swingset-runner/test/test-demo.js @@ -1,4 +1,4 @@ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { spawn } from 'child_process'; async function innerTest(t, extraFlags) { @@ -17,7 +17,7 @@ async function innerTest(t, extraFlags) { output += data; }); proc.addListener('exit', code => { - t.equal(code, 0, 'exits successfully'); + t.is(code, 0, 'exits successfully'); const uMsg = 'user vat is happy'; t.notEqual(output.indexOf(`\n${uMsg}\n`), -1, uMsg); const bMsg = 'bot vat is happy'; @@ -26,9 +26,9 @@ async function innerTest(t, extraFlags) { }); }); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } } diff --git a/packages/tame-metering/test/test-istamed.js b/packages/tame-metering/test/test-istamed.js index dca2adb3473..f894a3b22aa 100644 --- a/packages/tame-metering/test/test-istamed.js +++ b/packages/tame-metering/test/test-istamed.js @@ -1,11 +1,11 @@ -import test from 'tape-promise/tape'; +import test from 'ava'; import { isTamed, tameMetering } from '../src/index'; test('isTamed', t => { - t.equal(isTamed(), false, 'isTamed() is false in a new untamed realm'); + t.is(isTamed(), false, 'isTamed() is false in a new untamed realm'); tameMetering(); - t.equal(isTamed(), true, 'isTamed() becomes true after tameMetering()'); + t.is(isTamed(), true, 'isTamed() becomes true after tameMetering()'); tameMetering(); // idempotent - t.equal(isTamed(), true, 'isTamed() remains true after duplicate call'); - t.end(); + t.is(isTamed(), true, 'isTamed() remains true after duplicate call'); + return; // t.end(); }); diff --git a/packages/tame-metering/test/test-sanity.js b/packages/tame-metering/test/test-sanity.js index 372a5b4496a..62d8e6ebf15 100644 --- a/packages/tame-metering/test/test-sanity.js +++ b/packages/tame-metering/test/test-sanity.js @@ -1,9 +1,9 @@ import '../src/install-global-metering'; -import test from 'tape-promise/tape'; +import test from 'ava'; test('symbol properties', t => { t.assert(RegExp[Symbol.species], 'RegExp[Symbol.species] is kept'); - t.end(); + return; // t.end(); }); function foo(_bar) { @@ -12,6 +12,6 @@ function foo(_bar) { } test('direct eval', t => { - t.equals(foo(123), 123, 'direct eval succeeds'); - t.end(); + t.is(foo(123), 123, 'direct eval succeeds'); + return; // t.end(); }); diff --git a/packages/transform-eventual-send/test/test-transformer.js b/packages/transform-eventual-send/test/test-transformer.js index ca2d9770a20..07a0f1c38b0 100644 --- a/packages/transform-eventual-send/test/test-transformer.js +++ b/packages/transform-eventual-send/test/test-transformer.js @@ -7,43 +7,43 @@ test('transformer', t => { const source = `let p = bob~.foo(arg1, arg2);`; const transformer = makeTransform(babelParser, babelGenerate); const output = transformer(source); - t.equal( + t.is( output, `let p = HandledPromise.applyMethod(bob, "foo", [arg1, arg2]);`, ); - t.equal( + t.is( transformer(output), `let p = HandledPromise.applyMethod(bob, "foo", [arg1, arg2]);`, ); - t.equal(transformer('123;456'), '123;456;'); - t.equal(transformer('123;'), '123;'); - t.equal(transformer('123'), '123;'); - t.equal( + t.is(transformer('123;456'), '123;456;'); + t.is(transformer('123;'), '123;'); + t.is(transformer('123'), '123;'); + t.is( transformer(`"abc"~.length`), 'HandledPromise.get("abc", "length");', '.get() works', ); - t.equal( + t.is( transformer(`({foo(nick) { return "hello " + nick; }})~.foo('person')`), `HandledPromise.applyMethod({ foo(nick) {return "hello " + nick;} }, "foo", ['person']);`, '.applyMethod() works', ); - t.equal( + t.is( transformer(`((punct) => "world" + punct)~.('!')`), `HandledPromise.applyFunction(punct => "world" + punct, ['!']);`, '.applyFunction() works', ); - t.equal( + t.is( transformer(`["a", "b", "c"]~.[2]`), `HandledPromise.get(["a", "b", "c"], 2);`, 'computed .get works', ); - t.equal( + t.is( transformer( `({foo(greeting) { return greeting + ' world';}})~.foo~.('hello')`, ), `HandledPromise.applyFunction(HandledPromise.get({ foo(greeting) {return greeting + ' world';} }, "foo"), ['hello']);`, 'double eventual send works', ); - t.end(); + return; // t.end(); }); diff --git a/packages/transform-metering/test/test-meter.js b/packages/transform-metering/test/test-meter.js index 407e9e03a87..2f7627128be 100644 --- a/packages/transform-metering/test/test-meter.js +++ b/packages/transform-metering/test/test-meter.js @@ -1,5 +1,5 @@ /* eslint-disable no-await-in-loop */ -import test from 'tape-promise/tape'; +import test from 'ava'; import { makeMeter } from '../src/index'; import * as c from '../src/constants'; @@ -56,9 +56,9 @@ test('meter running', async t => { t.throws(() => meter3[c.METER_ENTER](), RangeError, 'stack exhausted'); testAllExhausted(t, meter3, 'stack meter'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); @@ -102,23 +102,23 @@ test('meter running', async t => { ); testAllExhausted(t, meter, 'combined meter'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); test('getBalance', async t => { try { const { meter, refillFacet } = makeMeter({ budgetCompute: 10 }); - t.equal(refillFacet.getComputeBalance(), 10); + t.is(refillFacet.getComputeBalance(), 10); meter[c.METER_COMPUTE](3); - t.equal(refillFacet.getComputeBalance(), 7); - t.equal(refillFacet.getAllocateBalance(), c.DEFAULT_COMBINED_METER); - t.equal(refillFacet.getCombinedBalance(), c.DEFAULT_COMBINED_METER); + t.is(refillFacet.getComputeBalance(), 7); + t.is(refillFacet.getAllocateBalance(), c.DEFAULT_COMBINED_METER); + t.is(refillFacet.getCombinedBalance(), c.DEFAULT_COMBINED_METER); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/transform-metering/test/test-tame.js b/packages/transform-metering/test/test-tame.js index a20ea8fe9e2..7175509930c 100644 --- a/packages/transform-metering/test/test-tame.js +++ b/packages/transform-metering/test/test-tame.js @@ -2,7 +2,7 @@ import replaceGlobalMeter from '@agoric/tame-metering/src/install-global-metering'; // eslint-disable-next-line import/order -import test from 'tape-promise/tape'; +import test from 'ava'; import { makeMeter, makeWithMeter } from '../src/index'; @@ -16,14 +16,14 @@ test('meter running', async t => { const withMeterFn = (thunk, newMeter = meter) => () => withMeter(thunk, newMeter); - t.equal(new [].constructor(40).length, 40, `new [].constructor works`); + t.is(new [].constructor(40).length, 40, `new [].constructor works`); - t.equal( + t.is( withoutMeter(() => true), true, `withoutMeter works`, ); - t.equal( + t.is( withMeter(() => true), true, `withMeter works`, @@ -51,8 +51,8 @@ test('meter running', async t => { 'map to Object create exhausted', ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/transform-metering/test/test-transform.js b/packages/transform-metering/test/test-transform.js index 3c48fab05a3..6a93fdce7b1 100644 --- a/packages/transform-metering/test/test-transform.js +++ b/packages/transform-metering/test/test-transform.js @@ -1,5 +1,5 @@ /* eslint-disable no-await-in-loop */ -import test from 'tape-promise/tape'; +import test from 'ava'; import * as babelCore from '@babel/core'; import fs from 'fs'; @@ -25,7 +25,7 @@ test('meter transform', async t => { sourceType: 'script', }); - t.equals(cMeter, source.length, `compute meter updated ${testName}`); + t.is(cMeter, source.length, `compute meter updated ${testName}`); return ss.src; }; @@ -51,11 +51,11 @@ test('meter transform', async t => { if (rewritten === undefined) { console.log(transformed); } - t.equals(transformed, rewritten.trimRight(), `rewrite ${testDir}`); + t.is(transformed, rewritten.trimRight(), `rewrite ${testDir}`); } } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { - t.end(); + return; // t.end(); } }); diff --git a/packages/transform-metering/test/test-zzz-eval.js b/packages/transform-metering/test/test-zzz-eval.js index 23983921a21..52065d10da2 100644 --- a/packages/transform-metering/test/test-zzz-eval.js +++ b/packages/transform-metering/test/test-zzz-eval.js @@ -3,7 +3,7 @@ import { replaceGlobalMeter } from './install-metering'; import '@agoric/install-ses'; // calls lockdown() -import test from 'tape-promise/tape'; +import test from 'ava'; import * as babelCore from '@babel/core'; import { makeMeter, makeMeteredEvaluator } from '../src/index'; @@ -77,15 +77,15 @@ test.skip('metering evaluator', async t => { }; */ const src1 = `123; 456;`; - t.equals(await myEval(meter, src1), 456, 'trivial source succeeds'); + t.is(await myEval(meter, src1), 456, 'trivial source succeeds'); const src5a = `\ ('x'.repeat(1e8), 0) `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src5a), - /Allocate meter exceeded/, + { message: /Allocate meter exceeded/ }, 'big string exhausts', ); @@ -93,9 +93,9 @@ test.skip('metering evaluator', async t => { (new Array(1e9), 0) `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src5), - /Allocate meter exceeded/, + { message: /Allocate meter exceeded/ }, 'big array exhausts', ); @@ -106,9 +106,9 @@ function f(a) { f(1); `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src2), - /Stack meter exceeded/, + { message: /Stack meter exceeded/ }, 'stack overflow exhausts', ); @@ -116,9 +116,9 @@ f(1); while (true) {} `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src3), - /Compute meter exceeded/, + { message: /Compute meter exceeded/ }, 'infinite loop exhausts', ); @@ -126,9 +126,9 @@ while (true) {} (() => { while(true) {} })(); `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src3b), - /Compute meter exceeded/, + { message: /Compute meter exceeded/ }, 'nested loop exhausts', ); @@ -140,9 +140,9 @@ Promise.resolve().then( 0 `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src3a), - /Compute meter exceeded/, + { message: /Compute meter exceeded/ }, 'promised infinite loop exhausts', ); @@ -154,16 +154,16 @@ f(); 0 `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src3c), - / meter exceeded/, + { message: / meter exceeded/ }, 'promise loop exhausts', ); const src4 = `\ /(x+x+)+y/.test('x'.repeat(10000)); `; - t.equals( + t.is( await myEval(meter, src4), false, `catastrophic backtracking doesn't happen`, @@ -173,21 +173,21 @@ f(); new Array(1e8).map(Object.create); 0 `; expectedExhaustedTimes += 1; - await t.rejects( + await t.throwsAsync( myEval(meter, src6), - /Allocate meter exceeded/, + { message: /Allocate meter exceeded/ }, 'long map exhausts', ); - t.equals( + t.is( exhaustedTimes, expectedExhaustedTimes, `meter was exhausted ${expectedExhaustedTimes} times`, ); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } finally { process.off('unhandledRejection', rejectionHandler); - t.end(); + return; // t.end(); } }); diff --git a/packages/zoe/test/swingsetTests/brokenContracts/test-crashingContract.js b/packages/zoe/test/swingsetTests/brokenContracts/test-crashingContract.js index a08601f0045..bfec2475d14 100644 --- a/packages/zoe/test/swingsetTests/brokenContracts/test-crashingContract.js +++ b/packages/zoe/test/swingsetTests/brokenContracts/test-crashingContract.js @@ -1,6 +1,6 @@ import '@agoric/install-metering-and-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import { loadBasedir, buildVatController } from '@agoric/swingset-vat'; // eslint-disable-next-line import/no-extraneous-dependencies @@ -42,9 +42,9 @@ test('ZCF metering crash on invite exercise', async t => { t.plan(1); try { const dump = await main(['meterInOfferHook', [3, 0, 0]]); - t.deepEquals(dump.log, meterExceededInOfferLog); + t.deepEqual(dump.log, meterExceededInOfferLog); } catch (e) { - t.isNot(e, e, 'unexpected metering exception in crashing contract test'); + t.not(e, e, 'unexpected metering exception in crashing contract test'); } }); @@ -69,9 +69,9 @@ test('ZCF metering crash on invite exercise', async t => { t.plan(1); try { const dump = await main(['meterInSecondInvite', [8, 0, 0]]); - t.deepEquals(dump.log, meterExceededInSecondOfferLog); + t.deepEqual(dump.log, meterExceededInSecondOfferLog); } catch (e) { - t.isNot(e, e, 'unexpected metering exception in crashing contract test'); + t.not(e, e, 'unexpected metering exception in crashing contract test'); } }); @@ -95,9 +95,9 @@ test('ZCF throwing on invite exercise', async t => { t.plan(1); try { const dump = await main(['throwInOfferHook', [3, 0, 0]]); - t.deepEquals(dump.log, throwInOfferLog); + t.deepEqual(dump.log, throwInOfferLog); } catch (e) { - t.isNot(e, e, 'unexpected throw in crashing contract test'); + t.not(e, e, 'unexpected throw in crashing contract test'); } }); @@ -122,9 +122,9 @@ test('ZCF throwing in API call', async t => { t.plan(1); try { const dump = await main(['throwInApiCall', [5, 12, 0]]); - t.deepEquals(dump.log, throwInAPILog); + t.deepEqual(dump.log, throwInAPILog); } catch (e) { - t.isNot(e, e, 'unexpected API throw in crashing contract test'); + t.not(e, e, 'unexpected API throw in crashing contract test'); } }); @@ -146,9 +146,9 @@ test('ZCF metering crash in API call', async t => { t.plan(1); try { const dump = await main(['meterInApiCall', [3, 0, 0]]); - t.deepEquals(dump.log, meteringExceededInAPILog); + t.deepEqual(dump.log, meteringExceededInAPILog); } catch (e) { - t.isNot( + t.not( e, e, 'unexpected API metering exception in crashing contract test', @@ -167,9 +167,9 @@ test('ZCF metering crash in makeContract call', async t => { t.plan(1); try { const dump = await main(['meterInMakeContract', [3, 0, 0]]); - t.deepEquals(dump.log, meteringExceptionInMakeContractILog); + t.deepEqual(dump.log, meteringExceptionInMakeContractILog); } catch (e) { - t.isNot( + t.not( e, e, 'unexpected API metering exception in crashing contract test', @@ -188,9 +188,9 @@ test('ZCF metering crash in makeContract call', async t => { t.plan(1); try { const dump = await main(['throwInMakeContract', [3, 0, 0]]); - t.deepEquals(dump.log, thrownExceptionInMakeContractILog); + t.deepEqual(dump.log, thrownExceptionInMakeContractILog); } catch (e) { - t.isNot( + t.not( e, e, 'unexpected API metering exception in crashing contract test', diff --git a/packages/zoe/test/swingsetTests/zoe-metering/test-zoe-metering.js b/packages/zoe/test/swingsetTests/zoe-metering/test-zoe-metering.js index f624872db39..519d92e2454 100644 --- a/packages/zoe/test/swingsetTests/zoe-metering/test-zoe-metering.js +++ b/packages/zoe/test/swingsetTests/zoe-metering/test-zoe-metering.js @@ -1,5 +1,5 @@ import '@agoric/install-metering-and-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { loadBasedir, buildVatController } from '@agoric/swingset-vat'; import fs from 'fs'; import bundleSource from '../../bundle-source'; @@ -41,9 +41,9 @@ test('zoe - metering - infinite loop in installation', async t => { t.plan(1); try { const dump = await main(['infiniteInstallLoop']); - t.deepEquals(dump.log, infiniteInstallLoopLog, 'log is correct'); + t.deepEqual(dump.log, infiniteInstallLoopLog, 'log is correct'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -56,9 +56,9 @@ test('zoe - metering - infinite loop in instantiation', async t => { t.plan(1); try { const dump = await main(['infiniteInstanceLoop']); - t.deepEquals(dump.log, infiniteInstanceLoopLog, 'log is correct'); + t.deepEqual(dump.log, infiniteInstanceLoopLog, 'log is correct'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -72,9 +72,9 @@ test('zoe - metering - infinite loop in contract method', async t => { t.plan(1); try { const dump = await main(['infiniteTestLoop']); - t.deepEquals(dump.log, infiniteTestLoopLog, 'log is correct'); + t.deepEqual(dump.log, infiniteTestLoopLog, 'log is correct'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -88,8 +88,8 @@ test('zoe - metering - expensive builtins in contract method', async t => { t.plan(1); try { const dump = await main(['testBuiltins']); - t.deepEquals(dump.log, testBuiltinsLog, 'log is correct'); + t.deepEqual(dump.log, testBuiltinsLog, 'log is correct'); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); diff --git a/packages/zoe/test/swingsetTests/zoe/test-zoe.js b/packages/zoe/test/swingsetTests/zoe/test-zoe.js index 9b5d67e89a0..a86e79604df 100644 --- a/packages/zoe/test/swingsetTests/zoe/test-zoe.js +++ b/packages/zoe/test/swingsetTests/zoe/test-zoe.js @@ -1,6 +1,6 @@ import '@agoric/install-metering-and-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import { loadBasedir, buildVatController } from '@agoric/swingset-vat'; // eslint-disable-next-line import/no-extraneous-dependencies @@ -58,9 +58,9 @@ test('zoe - automaticRefund - valid inputs', async t => { [0, 17, 0], ]; const dump = await main(['automaticRefundOk', startingValues]); - t.deepEquals(dump.log, expectedAutomaticRefundOkLog); + t.deepEqual(dump.log, expectedAutomaticRefundOkLog); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -83,9 +83,9 @@ test('zoe - coveredCall - valid inputs', async t => { [0, 7, 0], ]; const dump = await main(['coveredCallOk', startingValues]); - t.deepEquals(dump.log, expectedCoveredCallOkLog); + t.deepEqual(dump.log, expectedCoveredCallOkLog); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -117,9 +117,9 @@ test('zoe - swapForOption - valid inputs', async t => { [0, 7, 1], // Dave starts with 7 simoleans and 1 buck ]; const dump = await main(['swapForOptionOk', startingValues]); - t.deepEquals(dump.log, expectedSwapForOptionOkLog); + t.deepEqual(dump.log, expectedSwapForOptionOkLog); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -148,9 +148,9 @@ test('zoe - publicAuction - valid inputs', async t => { [0, 5, 0], ]; const dump = await main(['publicAuctionOk', startingValues]); - t.deepEquals(dump.log, expectedPublicAuctionOkLog); + t.deepEqual(dump.log, expectedPublicAuctionOkLog); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -170,9 +170,9 @@ test('zoe - atomicSwap - valid inputs', async t => { [0, 7, 0], ]; const dump = await main(['atomicSwapOk', startingValues]); - t.deepEquals(dump.log, expectedAtomicSwapOkLog); + t.deepEqual(dump.log, expectedAtomicSwapOkLog); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -194,9 +194,9 @@ test('zoe - simpleExchange - valid inputs', async t => { [0, 7, 0], ]; const dump = await main(['simpleExchangeOk', startingValues]); - t.deepEquals(dump.log, expectedSimpleExchangeOkLog); + t.deepEqual(dump.log, expectedSimpleExchangeOkLog); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -232,7 +232,7 @@ test('zoe - simpleExchange - state Update', async t => { [0, 24, 0], ]; const dump = await main(['simpleExchangeNotifier', startingValues]); - t.deepEquals(dump.log, expectedSimpleExchangeNotificationLog); + t.deepEqual(dump.log, expectedSimpleExchangeNotificationLog); }); const expectedAutoswapOkLog = [ @@ -257,7 +257,7 @@ test('zoe - autoswap - valid inputs', async t => { [3, 7, 0], ]; const dump = await main(['autoswapOk', startingValues]); - t.deepEquals(dump.log, expectedAutoswapOkLog); + t.deepEqual(dump.log, expectedAutoswapOkLog); }); const expectedSellTicketsOkLog = [ @@ -274,5 +274,5 @@ test('zoe - sellTickets - valid inputs', async t => { [22, 0, 0], ]; const dump = await main(['sellTicketsOk', startingValues]); - t.deepEquals(dump.log, expectedSellTicketsOkLog); + t.deepEqual(dump.log, expectedSellTicketsOkLog); }); diff --git a/packages/zoe/test/unitTests/contractSupport/test-bondingCurves.js b/packages/zoe/test/unitTests/contractSupport/test-bondingCurves.js index ad6a68e5f00..96cea289aa7 100644 --- a/packages/zoe/test/unitTests/contractSupport/test-bondingCurves.js +++ b/packages/zoe/test/unitTests/contractSupport/test-bondingCurves.js @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { getInputPrice, @@ -9,7 +9,7 @@ import { const testGetPrice = (t, input, expectedOutput) => { const output = getInputPrice(input); - t.deepEquals(output, expectedOutput); + t.deepEqual(output, expectedOutput); }; // If these tests of `getInputPrice` fail, it would indicate that we have @@ -127,8 +127,8 @@ test('calculate value to mint - positive supply', t => { inputValue: 30, inputReserve: 5, }); - t.equals(res, (20 * 30) / 5, 'When supply is present, floor(x*y/z)'); - t.end(); + t.is(res, (20 * 30) / 5, 'When supply is present, floor(x*y/z)'); + return; // t.end(); }); test('calculate value to mint - mispelled key', t => { @@ -141,7 +141,7 @@ test('calculate value to mint - mispelled key', t => { }), `calcLiqValueToMint should throw if a key is misspelled`, ); - t.end(); + return; // t.end(); }); test('calculate value to mint - positive supply', t => { @@ -150,8 +150,8 @@ test('calculate value to mint - positive supply', t => { inputValue: 8, inputReserve: 7, }); - t.equals(res, 5, 'When supply is present, floor(x*y/z)'); - t.end(); + t.is(res, 5, 'When supply is present, floor(x*y/z)'); + return; // t.end(); }); test('calculate value to mint - no supply', t => { @@ -160,6 +160,6 @@ test('calculate value to mint - no supply', t => { inputValue: 30, inputReserve: 5, }); - t.equals(res, 30, 'When the supply is empty, return inputValue'); - t.end(); + t.is(res, 30, 'When the supply is empty, return inputValue'); + return; // t.end(); }); diff --git a/packages/zoe/test/unitTests/contractSupport/test-stateMachine.js b/packages/zoe/test/unitTests/contractSupport/test-stateMachine.js index 4a409820979..062db348c59 100644 --- a/packages/zoe/test/unitTests/contractSupport/test-stateMachine.js +++ b/packages/zoe/test/unitTests/contractSupport/test-stateMachine.js @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { makeStateMachine } from '../../../src/contractSupport'; @@ -17,11 +17,11 @@ test('stateMachine', t => { ['closed', []], ]; const stateMachine = makeStateMachine(startState, allowedTransitions); - t.equal(stateMachine.getStatus(), 'empty'); - t.ok(stateMachine.canTransitionTo('open')); - t.notOk(stateMachine.canTransitionTo('closed')); + t.is(stateMachine.getStatus(), 'empty'); + t.assert(stateMachine.canTransitionTo('open')); + t.falsy(stateMachine.canTransitionTo('closed')); stateMachine.transitionTo('open'); - t.equal(stateMachine.getStatus(), 'open'); + t.is(stateMachine.getStatus(), 'open'); } catch (e) { t.assert(false, e); } diff --git a/packages/zoe/test/unitTests/contractSupport/test-zoeHelpers.js b/packages/zoe/test/unitTests/contractSupport/test-zoeHelpers.js index 2a895b410c6..ea26d4abe20 100644 --- a/packages/zoe/test/unitTests/contractSupport/test-zoeHelpers.js +++ b/packages/zoe/test/unitTests/contractSupport/test-zoeHelpers.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeStore from '@agoric/store'; import { setup } from '../setupBasicMints'; @@ -15,11 +15,11 @@ import { test('ZoeHelpers messages', t => { t.plan(2); try { - t.equals( + t.is( defaultAcceptanceMsg, `The offer has been accepted. Once the contract has been completed, please check your payout`, ); - t.equals( + t.is( defaultRejectMsg, `The offer was invalid. Please check your refund.`, ); @@ -90,17 +90,17 @@ test('ZoeHelpers assertKeywords', t => { ); t.throws( () => assertKeywords(['TokenA', 'TokenB']), - /were not as expected/, + { message: /were not as expected/ }, `The wrong keywords will throw`, ); t.throws( () => assertKeywords(['Asset', 'Price', 'Price2']), - /were not as expected/, + { message: /were not as expected/ }, `An extra keyword will throw`, ); t.throws( () => assertKeywords(['Asset']), - /were not as expected/, + { message: /were not as expected/ }, `a missing keyword will throw`, ); } catch (e) { @@ -190,7 +190,7 @@ test('ZoeHelpers rejectIfNotProposal', t => { /The offer was invalid. Please check your refund./, `had the wrong exit rule`, ); - t.deepEquals( + t.deepEqual( mockZCF.getCompletedHandles(), [], `offers 1, 2, 3, (zero-indexed) won't be completed before rejection`, @@ -223,7 +223,7 @@ test('ZoeHelpers rejectIfNotProposal', t => { /The offer was invalid. Please check your refund./, `had the wrong want`, ); - t.deepEquals( + t.deepEqual( mockZCF.getCompletedHandles(), [], `offers won't be completed before rejection`, @@ -249,7 +249,7 @@ test('ZoeHelpers checkIfProposal', t => { const mockZCF = mockZCFBuilder.build(); const { checkIfProposal } = makeZoeHelpers(mockZCF); - t.ok( + t.assert( checkIfProposal( handle, harden({ @@ -260,7 +260,7 @@ test('ZoeHelpers checkIfProposal', t => { ), `want, give, and exit match expected`, ); - t.notOk( + t.falsy( checkIfProposal( handle, harden({ @@ -270,7 +270,7 @@ test('ZoeHelpers checkIfProposal', t => { ), `want was not as expected`, ); - t.ok( + t.assert( checkIfProposal(handle, harden({})), `having no expectations passes trivially`, ); @@ -295,7 +295,7 @@ test('ZoeHelpers checkIfProposal multiple keys', t => { const mockZCF = mockZCFBuilder.build(); const { checkIfProposal } = makeZoeHelpers(mockZCF); - t.ok( + t.assert( checkIfProposal( handle, harden({ @@ -306,7 +306,7 @@ test('ZoeHelpers checkIfProposal multiple keys', t => { ), `want, give, and exit match expected`, ); - t.ok( + t.assert( checkIfProposal( handle, harden({ @@ -337,7 +337,7 @@ test('ZoeHelpers getActiveOffers', t => { }); const { getActiveOffers } = makeZoeHelpers(mockZCF); const offerHandles = harden([{}, {}]); - t.deepEquals( + t.deepEqual( getActiveOffers(offerHandles), harden([{ handle: offerHandles[0], id: 0 }]), `active offers gotten`, @@ -359,16 +359,16 @@ test('ZoeHelpers rejectOffer', t => { const offerHandles = harden([{}, {}]); t.throws( () => rejectOffer(offerHandles[0]), - /Error: The offer was invalid. Please check your refund./, + { message: /Error: The offer was invalid. Please check your refund./ }, `rejectOffer intentionally throws`, ); - t.deepEquals(completedOfferHandles, harden([]), 'no completion'); + t.deepEqual(completedOfferHandles, harden([]), 'no completion'); t.throws( () => rejectOffer(offerHandles[1], 'offer was wrong'), - /Error: offer was wrong/, + { message: /Error: offer was wrong/ }, `rejectOffer throws with custom msg`, ); - t.deepEquals( + t.deepEqual( completedOfferHandles, [], 'rejection does not include completions', @@ -416,19 +416,19 @@ test('ZoeHelpers swap ok', t => { }); const mockZCF = mockZCFBuilder.build(); const { swap } = makeZoeHelpers(mockZCF); - t.ok( + t.assert( swap( leftOfferHandle, rightOfferHandle, 'prior offer no longer available', ), ); - t.deepEquals( + t.deepEqual( mockZCF.getReallocatedHandles(), harden([leftOfferHandle, rightOfferHandle]), `both handles reallocated`, ); - t.deepEquals( + t.deepEqual( mockZCF.getReallocatedAmountObjs(), [ { Asset: moola(3), Price: simoleans(4) }, @@ -436,7 +436,7 @@ test('ZoeHelpers swap ok', t => { ], `amounts reallocated passed to reallocate were as expected`, ); - t.deepEquals( + t.deepEqual( mockZCF.getCompletedHandles(), harden([leftOfferHandle, rightOfferHandle]), `both handles were completed`, @@ -489,10 +489,10 @@ test('ZoeHelpers swap keep inactive', t => { `throws if keepHandle offer is not active`, ); const reallocatedHandles = mockZCF.getReallocatedHandles(); - t.deepEquals(reallocatedHandles, harden([]), `nothing reallocated`); + t.deepEqual(reallocatedHandles, harden([]), `nothing reallocated`); const reallocatedAmountObjs = mockZCF.getReallocatedAmountObjs(); - t.deepEquals(reallocatedAmountObjs, harden([]), `no amounts reallocated`); - t.deepEquals( + t.deepEqual(reallocatedAmountObjs, harden([]), `no amounts reallocated`); + t.deepEqual( mockZCF.getCompletedHandles(), harden([]), `no offers were completed`, @@ -550,11 +550,11 @@ test(`ZoeHelpers swap - can't trade with`, t => { `throws if can't trade with left and right`, ); const reallocatedHandles = mockZcf.getReallocatedHandles(); - t.deepEquals(reallocatedHandles, harden([]), `nothing reallocated`); + t.deepEqual(reallocatedHandles, harden([]), `nothing reallocated`); const reallocatedAmountObjs = mockZcf.getReallocatedAmountObjs(); - t.deepEquals(reallocatedAmountObjs, harden([]), `no amounts reallocated`); + t.deepEqual(reallocatedAmountObjs, harden([]), `no amounts reallocated`); const completedHandles = mockZcf.getCompletedHandles(); - t.deepEquals(completedHandles, harden([]), `no offers were completed`); + t.deepEqual(completedHandles, harden([]), `no offers were completed`); } catch (e) { t.assert(false, e); } @@ -588,8 +588,8 @@ test('ZoeHelpers makeEmptyOffer', async t => { }); const { makeEmptyOffer } = makeZoeHelpers(mockZCF); const result = await makeEmptyOffer(); - t.deepEquals(result, offerHandle, `offerHandle was returned`); - t.deepEquals(redeemedInvites, harden(['anInvite']), `invite was redeemed`); + t.deepEqual(result, offerHandle, `offerHandle was returned`); + t.deepEqual(redeemedInvites, harden(['anInvite']), `invite was redeemed`); } catch (e) { t.assert(false, e); } @@ -622,23 +622,23 @@ test('ZoeHelpers isOfferSafe', t => { }); const mockZCF = mockZCFBuilder.build(); const { isOfferSafe } = makeZoeHelpers(mockZCF); - t.ok( + t.assert( isOfferSafe(leftOfferHandle, { Asset: moola(0), Price: simoleans(4), }), `giving someone exactly what they want is offer safe`, ); - t.notOk( + t.falsy( isOfferSafe(leftOfferHandle, { Asset: moola(0), Price: simoleans(3), }), `giving someone less than what they want and not what they gave is not offer safe`, ); - t.deepEquals(reallocatedHandles, harden([]), `nothing reallocated`); - t.deepEquals(reallocatedAmountObjs, harden([]), `no amounts reallocated`); - t.deepEquals(completedHandles, harden([]), `no offers completed`); + t.deepEqual(reallocatedHandles, harden([]), `nothing reallocated`); + t.deepEqual(reallocatedAmountObjs, harden([]), `no amounts reallocated`); + t.deepEqual(completedHandles, harden([]), `no offers completed`); } catch (e) { t.assert(false, e); } @@ -671,30 +671,30 @@ test('ZoeHelpers satisfies', t => { }); const mockZCF = mockZCFBuilder.build(); const { satisfies } = makeZoeHelpers(mockZCF); - t.ok( + t.assert( satisfies(leftOfferHandle, { Asset: moola(0), Price: simoleans(4), }), `giving someone exactly what they want satisifies wants`, ); - t.notOk( + t.falsy( satisfies(leftOfferHandle, { Asset: moola(10), Price: simoleans(3), }), `giving someone less than what they want even with a refund doesn't satisfy wants`, ); - t.notOk( + t.falsy( satisfies(leftOfferHandle, { Asset: moola(0), Price: simoleans(3), }), `giving someone less than what they want even with a refund doesn't satisfy wants`, ); - t.deepEquals(reallocatedHandles, harden([]), `nothing reallocated`); - t.deepEquals(reallocatedAmountObjs, harden([]), `no amounts reallocated`); - t.deepEquals(completedHandles, harden([]), `no offers completed`); + t.deepEqual(reallocatedHandles, harden([]), `nothing reallocated`); + t.deepEqual(reallocatedAmountObjs, harden([]), `no amounts reallocated`); + t.deepEqual(completedHandles, harden([]), `no offers completed`); } catch (e) { t.assert(false, e); } @@ -741,12 +741,12 @@ test('ZoeHelpers trade ok', t => { }, ), ); - t.deepEquals( + t.deepEqual( mockZCF.getReallocatedHandles(), harden([leftOfferHandle, rightOfferHandle]), `both handles reallocated`, ); - t.deepEquals( + t.deepEqual( mockZCF.getReallocatedAmountObjs(), [ { Asset: moola(3), Bid: simoleans(4) }, @@ -754,7 +754,7 @@ test('ZoeHelpers trade ok', t => { ], `amounts reallocated passed to reallocate were as expected`, ); - t.deepEquals( + t.deepEqual( mockZCF.getCompletedHandles(), harden([]), `no handles were completed`, @@ -808,17 +808,17 @@ test('ZoeHelpers trade sameHandle', t => { /an offer cannot trade with itself/, `safe offer trading with itself fails with nice error message`, ); - t.deepEquals( + t.deepEqual( mockZCF.getReallocatedHandles(), harden([]), `no handles reallocated`, ); - t.deepEquals( + t.deepEqual( mockZCF.getReallocatedAmountObjs(), [], `no amounts reallocated`, ); - t.deepEquals( + t.deepEqual( mockZCF.getCompletedHandles(), harden([]), `no handles were completed`, diff --git a/packages/zoe/test/unitTests/contracts/test-atomicSwap.js b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js index 087e1217f52..5875225b115 100644 --- a/packages/zoe/test/unitTests/contracts/test-atomicSwap.js +++ b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; @@ -48,7 +48,7 @@ test('zoe - atomicSwap', async t => { .getPayout('Asset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, moola(0), `Alice didn't get any of what she put in`, @@ -59,7 +59,7 @@ test('zoe - atomicSwap', async t => { .getPayout('Price') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.Price, `Alice got exactly what she wanted`, @@ -90,17 +90,17 @@ test('zoe - atomicSwap', async t => { value: [invitationValue], } = await invitationIssuer.getAmountOf(invitation); - t.equals( + t.is( invitationValue.installation, installation, 'installation is atomicSwap', ); - t.deepEquals( + t.deepEqual( invitationValue.asset, moola(3), `asset to be traded is 3 moola`, ); - t.deepEquals( + t.deepEqual( invitationValue.price, simoleans(7), `price is 7 simoleans, so bob must give that`, @@ -115,7 +115,7 @@ test('zoe - atomicSwap', async t => { const seat = await zoe.offer(invitation, proposal, payments); - t.equals( + t.is( await E(seat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -124,7 +124,7 @@ test('zoe - atomicSwap', async t => { .getPayout('Asset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.Asset, `Bob got what he wanted`, @@ -135,7 +135,7 @@ test('zoe - atomicSwap', async t => { .getPayout('Price') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, simoleans(0), `Bob didn't get anything back`, @@ -210,7 +210,7 @@ test('zoe - non-fungible atomicSwap', async t => { .getPayout('Asset') .then(ccPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, cryptoCats(harden([])), `Alice didn't get any of what she put in`, @@ -221,7 +221,7 @@ test('zoe - non-fungible atomicSwap', async t => { .getPayout('Price') .then(rpgPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.Price, `Alice got exactly what she wanted`, @@ -253,17 +253,17 @@ test('zoe - non-fungible atomicSwap', async t => { value: [invitationValue], } = await invitationIssuer.getAmountOf(invitation); - t.equals( + t.is( invitationValue.installation, installation, 'installation is atomicSwap', ); - t.deepEquals( + t.deepEqual( invitationValue.asset, calico37Amount, `asset to be traded is a particular crypto cat`, ); - t.deepEquals( + t.deepEqual( invitationValue.price, vorpalAmount, `price is vorpalAmount, so bob must give that`, @@ -278,7 +278,7 @@ test('zoe - non-fungible atomicSwap', async t => { const seat = await zoe.offer(invitation, proposal, payments); - t.equals( + t.is( await E(seat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -287,7 +287,7 @@ test('zoe - non-fungible atomicSwap', async t => { .getPayout('Asset') .then(ccPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.Asset, `Bob got what he wanted`, @@ -298,7 +298,7 @@ test('zoe - non-fungible atomicSwap', async t => { .getPayout('Price') .then(rpgPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, rpgItems(harden([])), `Bob didn't get anything back`, @@ -389,10 +389,10 @@ test('zoe - atomicSwap like-for-like', async t => { const bobIssuers = zoe.getIssuers(bobInviteValue.instance); - t.equals(bobInviteValue.installation, installation, 'bobInstallationId'); - t.deepEquals(bobIssuers, { Asset: moolaIssuer, Price: moolaIssuer }); - t.deepEquals(bobInviteValue.asset, moola(3)); - t.deepEquals(bobInviteValue.price, moola(7)); + t.is(bobInviteValue.installation, installation, 'bobInstallationId'); + t.deepEqual(bobIssuers, { Asset: moolaIssuer, Price: moolaIssuer }); + t.deepEqual(bobInviteValue.asset, moola(3)); + t.deepEqual(bobInviteValue.price, moola(7)); const bobProposal = harden({ give: { Price: moola(7) }, @@ -404,7 +404,7 @@ test('zoe - atomicSwap like-for-like', async t => { // 5: Bob makes an offer const bobSeat = await zoe.offer(bobExclusiveInvite, bobProposal, bobPayments); - t.equals( + t.is( await E(bobSeat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -416,29 +416,29 @@ test('zoe - atomicSwap like-for-like', async t => { const alicePricePayout = await aliceSeat.getPayout('Price'); // Alice gets what Alice wanted - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(alicePricePayout), aliceProposal.want.Price, ); // Alice didn't get any of what Alice put in - t.deepEquals(await moolaIssuer.getAmountOf(aliceAssetPayout), moola(0)); + t.deepEqual(await moolaIssuer.getAmountOf(aliceAssetPayout), moola(0)); // Alice deposits her payout to ensure she can const aliceAssetAmount = await aliceMoolaPurse.deposit(aliceAssetPayout); - t.equals(aliceAssetAmount.value, 0); + t.is(aliceAssetAmount.value, 0); const alicePriceAmount = await aliceMoolaPurse.deposit(alicePricePayout); - t.equals(alicePriceAmount.value, 7); + t.is(alicePriceAmount.value, 7); // Bob deposits his original payments to ensure he can const bobAssetAmount = await bobMoolaPurse.deposit(bobAssetPayout); - t.equals(bobAssetAmount.value, 3); + t.is(bobAssetAmount.value, 3); const bobPriceAmount = await bobMoolaPurse.deposit(bobPricePayout); - t.equals(bobPriceAmount.value, 0); + t.is(bobPriceAmount.value, 0); // Assert that the correct payouts were received. // Alice had 3 moola from Asset and 0 from Price. // Bob had 0 moola from Asset and 7 from Price. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 7); - t.equals(bobMoolaPurse.getCurrentAmount().value, 3); + t.is(aliceMoolaPurse.getCurrentAmount().value, 7); + t.is(bobMoolaPurse.getCurrentAmount().value, 3); }); diff --git a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js index eaccaa6b39c..d8917a3f56b 100644 --- a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js +++ b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; @@ -47,7 +47,7 @@ test('zoe - simplest automaticRefund', async t => { const aliceMoolaPayout = await seat.getPayout('Contribution'); // Alice got back what she put in - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(aliceMoolaPayout), aliceProposal.give.Contribution, `Alice's payout matches what she put in`, @@ -95,7 +95,7 @@ test('zoe - automaticRefund same issuer', async t => { const aliceMoolaPayout = await E(seat).getPayout('Contribution2'); // Alice got back what she put in - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(aliceMoolaPayout), aliceProposal.give.Contribution2, ); @@ -157,7 +157,7 @@ test('zoe with automaticRefund', async t => { const bobInvitation = await E(publicFacet).makeInvitation(); const count = await E(publicFacet).getOffersCount(); - t.equals(count, 1); + t.is(count, 1); // Imagine that Alice has shared the bobInvitation with Bob. He // will do a claim on the invitation with the Zoe invitation issuer and @@ -168,12 +168,12 @@ test('zoe with automaticRefund', async t => { const { value: [bobInviteValue], } = await E(invitationIssuer).getAmountOf(exclusBobInvitation); - t.equals(bobInviteValue.installation, installation); + t.is(bobInviteValue.installation, installation); // bob wants to know what issuers this contract is about and in // what order. Is it what he expects? const bobIssuers = await E(zoe).getIssuers(bobInviteValue.instance); - t.deepEquals(bobIssuers, { + t.deepEqual(bobIssuers, { Contribution1: moolaR.issuer, Contribution2: simoleanR.issuer, }); @@ -194,8 +194,8 @@ test('zoe with automaticRefund', async t => { bobPayments, ); - t.equals(await E(aliceSeat).getOfferResult(), 'The offer was accepted'); - t.equals(await E(bobSeat).getOfferResult(), 'The offer was accepted'); + t.is(await E(aliceSeat).getOfferResult(), 'The offer was accepted'); + t.is(await E(bobSeat).getOfferResult(), 'The offer was accepted'); // These promise resolve when the offer completes, but it may // still take longer for a remote issuer to actually make the @@ -210,13 +210,13 @@ test('zoe with automaticRefund', async t => { const bobSimoleanPayout = await bobSeat.getPayout('Contribution2'); // Alice got back what she put in - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(aliceMoolaPayout), aliceProposal.give.Contribution1, ); // Alice didn't get any of what she wanted - t.deepEquals( + t.deepEqual( await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), simoleans(0), ); @@ -232,10 +232,10 @@ test('zoe with automaticRefund', async t => { // Assert that the correct refund was achieved. // Alice had 3 moola and 0 simoleans. // Bob had 0 moola and 7 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 3); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 0); - t.equals(bobMoolaPurse.getCurrentAmount().value, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 17); + t.is(aliceMoolaPurse.getCurrentAmount().value, 3); + t.is(aliceSimoleanPurse.getCurrentAmount().value, 0); + t.is(bobMoolaPurse.getCurrentAmount().value, 0); + t.is(bobSimoleanPurse.getCurrentAmount().value, 17); } catch (e) { t.assert(false, e); console.log(e); @@ -308,23 +308,23 @@ test('multiple instances of automaticRefund for the same Zoe', async t => { const moolaPayout3 = await seat3.getPayout('ContributionA'); // Ensure that she got what she put in for each - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(moolaPayout1), aliceProposal.give.ContributionA, ); - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(moolaPayout2), aliceProposal.give.ContributionA, ); - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(moolaPayout3), aliceProposal.give.ContributionA, ); // Ensure that the number of offers received by each instance is one - t.equals(await E(publicFacet1).getOffersCount(), 1); - t.equals(await E(publicFacet2).getOffersCount(), 1); - t.equals(await E(publicFacet3).getOffersCount(), 1); + t.is(await E(publicFacet1).getOffersCount(), 1); + t.is(await E(publicFacet2).getOffersCount(), 1); + t.is(await E(publicFacet3).getOffersCount(), 1); } catch (e) { t.assert(false, e); console.log(e); @@ -371,22 +371,22 @@ test('zoe - alice tries to complete after completion has already occurred', asyn console.log( 'EXPECTED ERROR: Cannot exit seat. Seat has already exited >>>', ); - t.rejects( + t.throwsAsync( () => E(aliceSeat).exit(), - /Error: Cannot exit seat. Seat has already exited/, + { message: /Error: Cannot exit seat. Seat has already exited/ }, ); const moolaPayout = await aliceSeat.getPayout('ContributionA'); const simoleanPayout = await aliceSeat.getPayout('ContributionB'); // Alice got back what she put in - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(moolaPayout), aliceProposal.give.ContributionA, ); // Alice didn't get any of what she wanted - t.deepEquals( + t.deepEqual( await simoleanR.issuer.getAmountOf(simoleanPayout), simoleans(0), ); @@ -397,8 +397,8 @@ test('zoe - alice tries to complete after completion has already occurred', asyn // Assert that the correct refund was achieved. // Alice had 3 moola and 0 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 3); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 0); + t.is(aliceMoolaPurse.getCurrentAmount().value, 3); + t.is(aliceSimoleanPurse.getCurrentAmount().value, 0); } catch (e) { t.assert(false, e); console.log(e); @@ -435,7 +435,7 @@ test('zoe - automaticRefund non-fungible', async t => { const aliceCcPayout = await seat.getPayout('Contribution'); // Alice got back what she put in - t.deepEquals( + t.deepEqual( await ccIssuer.getAmountOf(aliceCcPayout), aliceProposal.give.Contribution, ); diff --git a/packages/zoe/test/unitTests/contracts/test-autoswap.js b/packages/zoe/test/unitTests/contracts/test-autoswap.js index 4046b62a852..199218fe2c0 100644 --- a/packages/zoe/test/unitTests/contracts/test-autoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-autoswap.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { E } from '@agoric/eventual-send'; import { setup } from '../setupBasicMints'; @@ -70,7 +70,7 @@ test('autoSwap with valid offers', async t => { const liquidityPayout = await aliceSeat.getPayout('Liquidity'); assertPayoutAmount(t, liquidityIssuer, liquidityPayout, liquidity(10)); - t.deepEquals( + t.deepEqual( await E(publicFacet).getPoolAllocation(), { TokenA: moola(10), @@ -89,7 +89,7 @@ test('autoSwap with valid offers', async t => { installation: bobInstallation, instance: bobInstance, } = await getInviteFields(inviteIssuer, bobExclInvite); - t.equals(bobInstallation, installation, `installation`); + t.is(bobInstallation, installation, `installation`); const bobAutoswap = E(zoe).getPublicFacet(bobInstance); // Bob looks up the price of 3 moola in simoleans @@ -97,7 +97,7 @@ test('autoSwap with valid offers', async t => { moola(3), simoleans(0).brand, ); - t.deepEquals(simoleanAmounts, simoleans(1), `currentPrice`); + t.deepEqual(simoleanAmounts, simoleans(1), `currentPrice`); // Bob escrows const bobMoolaForSimProposal = harden({ @@ -122,7 +122,7 @@ test('autoSwap with valid offers', async t => { assertPayoutAmount(t, moolaIssuer, bobMoolaPayout1, moola(0)); assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout1, simoleans(1)); - t.deepEquals( + t.deepEqual( await E(bobAutoswap).getPoolAllocation(), { TokenA: moola(13), @@ -137,7 +137,7 @@ test('autoSwap with valid offers', async t => { simoleans(3), moola(0).brand, ); - t.deepEquals(moolaAmounts, moola(5), `price 2`); + t.deepEqual(moolaAmounts, moola(5), `price 2`); // Bob makes another offer and swaps const bobSecondInvite = await E(bobAutoswap).makeSwapInvite(); @@ -196,7 +196,7 @@ test('autoSwap with valid offers', async t => { assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(7)); assertPayoutAmount(t, liquidityIssuer, aliceLiquidityPayout, liquidity(0)); - t.deepEquals(await E(publicFacet).getPoolAllocation(), { + t.deepEqual(await E(publicFacet).getPoolAllocation(), { TokenA: moola(0), TokenB: simoleans(0), Liquidity: liquidity(10), @@ -266,7 +266,7 @@ test('autoSwap - test fee', async t => { const liquidityPayout = await aliceSeat.getPayout('Liquidity'); assertPayoutAmount(t, liquidityIssuer, liquidityPayout, liquidity(10000)); - t.deepEquals( + t.deepEqual( await E(publicFacet).getPoolAllocation(), { TokenA: moola(10000), @@ -285,7 +285,7 @@ test('autoSwap - test fee', async t => { installation: bobInstallation, instance: bobInstance, } = await getInviteFields(inviteIssuer, bobExclInvite); - t.equals(bobInstallation, bobInstallation); + t.is(bobInstallation, bobInstallation); const bobAutoswap = E(zoe).getPublicFacet(bobInstance); // Bob looks up the price of 1000 moola in simoleans @@ -293,7 +293,7 @@ test('autoSwap - test fee', async t => { moola(1000), simoleans(0).brand, ); - t.deepEquals(simoleanAmounts, simoleans(906), `simoleans out`); + t.deepEqual(simoleanAmounts, simoleans(906), `simoleans out`); // Bob escrows const bobMoolaForSimProposal = harden({ @@ -318,7 +318,7 @@ test('autoSwap - test fee', async t => { assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(0)); assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(906)); - t.deepEquals( + t.deepEqual( await E(bobAutoswap).getPoolAllocation(), { TokenA: moola(11000), diff --git a/packages/zoe/test/unitTests/contracts/test-barter.js b/packages/zoe/test/unitTests/contracts/test-barter.js index 7012108b756..ba2a4da6bcc 100644 --- a/packages/zoe/test/unitTests/contracts/test-barter.js +++ b/packages/zoe/test/unitTests/contracts/test-barter.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { E } from '@agoric/eventual-send'; import { setup } from '../setupBasicMints'; @@ -75,8 +75,8 @@ test('barter with valid offers', async t => { // 4: Bob decides to join. const bobExclusiveInvitation = await invitationIssuer.claim(bobInvitation); - t.equals(bobInstallation, installation); - t.equals(bobInstance, instance); + t.is(bobInstallation, installation); + t.is(bobInstance, instance); // Bob creates a buy order, saying that he wants exactly 3 moola, // and is willing to pay up to 7 simoleans. @@ -107,7 +107,7 @@ test('barter with valid offers', async t => { } = await aliceSeat.getPayouts(); // Alice gets paid at least what she wanted - t.ok( + t.assert( amountMaths .get('simoleans') .isGTE( @@ -117,7 +117,7 @@ test('barter with valid offers', async t => { ); // Alice sold all of her moola - t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); + t.deepEqual(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); // Alice had 0 moola and 4 simoleans. assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(0)); diff --git a/packages/zoe/test/unitTests/contracts/test-brokenContract.js b/packages/zoe/test/unitTests/contracts/test-brokenContract.js index ebd73ff8374..ab50545ad26 100644 --- a/packages/zoe/test/unitTests/contracts/test-brokenContract.js +++ b/packages/zoe/test/unitTests/contracts/test-brokenContract.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -27,7 +27,7 @@ test('zoe - brokenAutomaticRefund', async t => { console.log( 'EXPECTED ERROR: The contract did not correctly return a creatorInvitation', ); - t.rejects( + t.throwsAsync( () => zoe.makeInstance(installation, issuerKeywordRecord), new Error('The contract did not correctly return a creatorInvitation'), 'makeInstance should have thrown', diff --git a/packages/zoe/test/unitTests/contracts/test-coveredCall.js b/packages/zoe/test/unitTests/contracts/test-coveredCall.js index 8337e585314..ea7ed32bd88 100644 --- a/packages/zoe/test/unitTests/contracts/test-coveredCall.js +++ b/packages/zoe/test/unitTests/contracts/test-coveredCall.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; @@ -59,7 +59,7 @@ test('zoe - coveredCall', async t => { .getPayout('UnderlyingAsset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, moola(0), `Alice didn't get any of what she put in`, @@ -70,7 +70,7 @@ test('zoe - coveredCall', async t => { .getPayout('StrikePrice') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.StrikePrice, `Alice got exactly what she wanted`, @@ -103,25 +103,25 @@ test('zoe - coveredCall', async t => { value: [invitationValue], } = await invitationIssuer.getAmountOf(invitation); - t.equals( + t.is( invitationValue.installation, installation, 'installation is atomicSwap', ); - t.equal(invitationValue.description, 'exerciseOption'); + t.is(invitationValue.description, 'exerciseOption'); - t.deepEquals( + t.deepEqual( invitationValue.underlyingAsset, moola(3), `underlying asset is 3 moola`, ); - t.deepEquals( + t.deepEqual( invitationValue.strikePrice, simoleans(7), `strike price is 7 simoleans, so bob must give that`, ); - t.equal(invitationValue.expirationDate, 1); + t.is(invitationValue.expirationDate, 1); t.deepEqual(invitationValue.timerAuthority, timer); const proposal = harden({ @@ -133,7 +133,7 @@ test('zoe - coveredCall', async t => { const seat = await E(zoe).offer(invitation, proposal, payments); - t.equals( + t.is( await E(seat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -142,7 +142,7 @@ test('zoe - coveredCall', async t => { .getPayout('UnderlyingAsset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.UnderlyingAsset, `Bob got what he wanted`, @@ -153,7 +153,7 @@ test('zoe - coveredCall', async t => { .getPayout('StrikePrice') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, simoleans(0), `Bob didn't get anything back`, @@ -184,7 +184,7 @@ test('zoe - coveredCall', async t => { // counter-party, without needing to trust Alice at all. await bob.offer(invitation); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -253,11 +253,11 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a const { installationHandle } = zoe.getInstanceRecord( optionValue.instanceHandle, ); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.description, 'exerciseOption'); - t.ok(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); - t.ok(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); - t.equal(optionValue.expirationDate, 1); + t.is(installationHandle, coveredCallInstallationHandle); + t.is(optionValue.description, 'exerciseOption'); + t.assert(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); + t.assert(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); + t.is(optionValue.expirationDate, 1); t.deepEqual(optionValue.timerAuthority, timer); const bobPayments = { StrikePrice: bobSimoleanPayment }; @@ -274,7 +274,7 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a bobPayments, ); - t.rejects( + t.throwsAsync( () => bobOutcomeP, new Error('The covered call option is expired'), 'The call option should be expired', @@ -289,10 +289,10 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a const aliceSimoleanPayout = await alicePayout.StrikePrice; // Alice gets back what she put in - t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(3)); + t.deepEqual(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(3)); // Alice doesn't get what she wanted - t.deepEquals( + t.deepEqual( await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), simoleans(0), ); @@ -308,12 +308,12 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a // Assert that the correct outcome was achieved. // Alice had 3 moola and 0 simoleans. // Bob had 0 moola and 7 simoleans. - t.deepEquals(aliceMoolaPurse.getCurrentAmount(), moola(3)); - t.deepEquals(aliceSimoleanPurse.getCurrentAmount(), simoleans(0)); - t.deepEquals(bobMoolaPurse.getCurrentAmount(), moola(0)); - t.deepEquals(bobSimoleanPurse.getCurrentAmount(), simoleans(7)); + t.deepEqual(aliceMoolaPurse.getCurrentAmount(), moola(3)); + t.deepEqual(aliceSimoleanPurse.getCurrentAmount(), simoleans(0)); + t.deepEqual(bobMoolaPurse.getCurrentAmount(), moola(0)); + t.deepEqual(bobSimoleanPurse.getCurrentAmount(), simoleans(7)); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -407,11 +407,11 @@ test('zoe - coveredCall with swap for invite', async t => { const { installationHandle } = zoe.getInstanceRecord( optionDesc.instanceHandle, ); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionDesc.description, 'exerciseOption'); - t.ok(moolaR.amountMath.isEqual(optionDesc.underlyingAsset, moola(3))); - t.ok(simoleanR.amountMath.isEqual(optionDesc.strikePrice, simoleans(7))); - t.equal(optionDesc.expirationDate, 100); + t.is(installationHandle, coveredCallInstallationHandle); + t.is(optionDesc.description, 'exerciseOption'); + t.assert(moolaR.amountMath.isEqual(optionDesc.underlyingAsset, moola(3))); + t.assert(simoleanR.amountMath.isEqual(optionDesc.strikePrice, simoleans(7))); + t.is(optionDesc.expirationDate, 100); t.deepEqual(optionDesc.timerAuthority, timer); // Let's imagine that Bob wants to create a swap to trade this @@ -459,10 +459,10 @@ test('zoe - coveredCall with swap for invite', async t => { // checks that this instance matches what he wants // Did this swap use the correct swap installation? Yes - t.equal(daveSwapInstallId, swapInstallationId); + t.is(daveSwapInstallId, swapInstallationId); // Is this swap for the correct issuers and has no other terms? Yes - t.ok( + t.assert( sameStructure( daveSwapIssuers, harden({ @@ -490,7 +490,7 @@ test('zoe - coveredCall with swap for invite', async t => { outcome: daveSwapOutcomeP, } = await zoe.offer(daveSwapInviteP, daveSwapProposal, daveSwapPayments); - t.equals( + t.is( await daveSwapOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -518,7 +518,7 @@ test('zoe - coveredCall with swap for invite', async t => { daveCoveredCallPayments, ); - t.equals( + t.is( await daveCoveredCallOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -535,23 +535,23 @@ test('zoe - coveredCall with swap for invite', async t => { const bobInvitePayout = await bobResult.Asset; const bobBucksPayout = await bobResult.Price; - t.deepEquals(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3)); - t.deepEquals( + t.deepEqual(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3)); + t.deepEqual( await simoleanR.issuer.getAmountOf(daveSimoleanPayout), simoleans(0), ); - t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); - t.deepEquals( + t.deepEqual(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); + t.deepEqual( await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), simoleans(7), ); - t.deepEquals( + t.deepEqual( await inviteIssuer.getAmountOf(bobInvitePayout), inviteAmountMath.getEmpty(), ); - t.deepEquals(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1)); + t.deepEqual(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1)); // Alice deposits her payouts await aliceMoolaPurse.deposit(aliceMoolaPayout); @@ -565,18 +565,18 @@ test('zoe - coveredCall with swap for invite', async t => { await daveSimoleanPurse.deposit(daveSimoleanPayout); await daveBucksPurse.deposit(daveBucksPayout); - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 7); + t.is(aliceMoolaPurse.getCurrentAmount().value, 0); + t.is(aliceSimoleanPurse.getCurrentAmount().value, 7); - t.equals(bobMoolaPurse.getCurrentAmount().value, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 0); - t.equals(bobBucksPurse.getCurrentAmount().value, 1); + t.is(bobMoolaPurse.getCurrentAmount().value, 0); + t.is(bobSimoleanPurse.getCurrentAmount().value, 0); + t.is(bobBucksPurse.getCurrentAmount().value, 1); - t.equals(daveMoolaPurse.getCurrentAmount().value, 3); - t.equals(daveSimoleanPurse.getCurrentAmount().value, 0); - t.equals(daveBucksPurse.getCurrentAmount().value, 0); + t.is(daveMoolaPurse.getCurrentAmount().value, 3); + t.is(daveSimoleanPurse.getCurrentAmount().value, 0); + t.is(daveBucksPurse.getCurrentAmount().value, 0); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -670,11 +670,11 @@ test('zoe - coveredCall with coveredCall for invite', async t => { const { installationHandle } = zoe.getInstanceRecord( optionValue.instanceHandle, ); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.description, 'exerciseOption'); - t.ok(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); - t.ok(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); - t.equal(optionValue.expirationDate, 100); + t.is(installationHandle, coveredCallInstallationHandle); + t.is(optionValue.description, 'exerciseOption'); + t.assert(moolaR.amountMath.isEqual(optionValue.underlyingAsset, moola(3))); + t.assert(simoleanR.amountMath.isEqual(optionValue.strikePrice, simoleans(7))); + t.is(optionValue.expirationDate, 100); t.deepEqual(optionValue.timerAuthority, timer); // Let's imagine that Bob wants to create another coveredCall, but @@ -724,19 +724,19 @@ test('zoe - coveredCall with coveredCall for invite', async t => { const { installationHandle: daveOptionInstallationHandle, } = zoe.getInstanceRecord(daveOptionValue.instanceHandle); - t.equal(daveOptionInstallationHandle, coveredCallInstallationHandle); - t.equal(daveOptionValue.description, 'exerciseOption'); - t.ok(bucksR.amountMath.isEqual(daveOptionValue.strikePrice, bucks(1))); - t.equal(daveOptionValue.expirationDate, 100); + t.is(daveOptionInstallationHandle, coveredCallInstallationHandle); + t.is(daveOptionValue.description, 'exerciseOption'); + t.assert(bucksR.amountMath.isEqual(daveOptionValue.strikePrice, bucks(1))); + t.is(daveOptionValue.expirationDate, 100); t.deepEqual(daveOptionValue.timerAuthority, timer); // What about the underlying asset (the other option)? - t.equal( + t.is( daveOptionValue.underlyingAsset.value[0].description, 'exerciseOption', ); - t.equal(daveOptionValue.underlyingAsset.value[0].expirationDate, 100); - t.ok( + t.is(daveOptionValue.underlyingAsset.value[0].expirationDate, 100); + t.assert( simoleanR.amountMath.isEqual( daveOptionValue.underlyingAsset.value[0].strikePrice, simoleans(7), @@ -761,7 +761,7 @@ test('zoe - coveredCall with coveredCall for invite', async t => { daveProposalCoveredCall, daveSecondCoveredCallPayments, ); - t.equals( + t.is( await daveSecondCoveredCallOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', `dave second offer accepted`, @@ -791,7 +791,7 @@ test('zoe - coveredCall with coveredCall for invite', async t => { daveFirstCoveredCallPayments, ); - t.equals( + t.is( await daveFirstCoveredCallOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', `dave first offer accepted`, @@ -812,23 +812,23 @@ test('zoe - coveredCall with coveredCall for invite', async t => { const bobInvitePayout = await bobResult.UnderlyingAsset; const bobBucksPayout = await bobResult.StrikePrice; - t.deepEquals(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3)); - t.deepEquals( + t.deepEqual(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3)); + t.deepEqual( await simoleanR.issuer.getAmountOf(daveSimoleanPayout), simoleans(0), ); - t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); - t.deepEquals( + t.deepEqual(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); + t.deepEqual( await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), simoleans(7), ); - t.deepEquals( + t.deepEqual( await inviteIssuer.getAmountOf(bobInvitePayout), inviteAmountMath.getEmpty(), ); - t.deepEquals(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1)); + t.deepEqual(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1)); // Alice deposits her payouts await aliceMoolaPurse.deposit(aliceMoolaPayout); @@ -842,18 +842,18 @@ test('zoe - coveredCall with coveredCall for invite', async t => { await daveSimoleanPurse.deposit(daveSimoleanPayout); await daveBucksPurse.deposit(daveBucksPayout); - t.equals(aliceMoolaPurse.getCurrentAmount().value, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 7); + t.is(aliceMoolaPurse.getCurrentAmount().value, 0); + t.is(aliceSimoleanPurse.getCurrentAmount().value, 7); - t.equals(bobMoolaPurse.getCurrentAmount().value, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 0); - t.equals(bobBucksPurse.getCurrentAmount().value, 1); + t.is(bobMoolaPurse.getCurrentAmount().value, 0); + t.is(bobSimoleanPurse.getCurrentAmount().value, 0); + t.is(bobBucksPurse.getCurrentAmount().value, 1); - t.equals(daveMoolaPurse.getCurrentAmount().value, 3); - t.equals(daveSimoleanPurse.getCurrentAmount().value, 0); - t.equals(daveBucksPurse.getCurrentAmount().value, 0); + t.is(daveMoolaPurse.getCurrentAmount().value, 3); + t.is(daveSimoleanPurse.getCurrentAmount().value, 0); + t.is(daveBucksPurse.getCurrentAmount().value, 0); } catch (e) { - t.isNot(e, e, 'unexpected exception'); + t.not(e, e, 'unexpected exception'); } }); @@ -936,19 +936,19 @@ test('zoe - coveredCall non-fungible', async t => { const { installationHandle } = zoe.getInstanceRecord( optionValue.instanceHandle, ); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionValue.description, 'exerciseOption'); - t.ok( + t.is(installationHandle, coveredCallInstallationHandle); + t.is(optionValue.description, 'exerciseOption'); + t.assert( amountMaths .get('cc') .isEqual(optionValue.underlyingAsset, growlTigerAmount), ); - t.ok( + t.assert( amountMaths .get('rpg') .isEqual(optionValue.strikePrice, aGloriousShieldAmount), ); - t.equal(optionValue.expirationDate, 1); + t.is(optionValue.expirationDate, 1); t.deepEqual(optionValue.timerAuthority, timer); const bobPayments = { StrikePrice: bobRpgPayment }; @@ -967,7 +967,7 @@ test('zoe - coveredCall non-fungible', async t => { bobPayments, ); - t.equals( + t.is( await bobOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -981,13 +981,13 @@ test('zoe - coveredCall non-fungible', async t => { const aliceRpgPayout = await alicePayout.StrikePrice; // Alice gets what Alice wanted - t.deepEquals( + t.deepEqual( await rpgIssuer.getAmountOf(aliceRpgPayout), aliceProposal.want.StrikePrice, ); // Alice didn't get any of what Alice put in - t.deepEquals( + t.deepEqual( await ccIssuer.getAmountOf(aliceCcPayout), cryptoCats(harden([])), ); @@ -1003,8 +1003,8 @@ test('zoe - coveredCall non-fungible', async t => { // Assert that the correct payouts were received. // Alice had growlTiger and no RPG tokens. // Bob had an empty CryptoCat purse and the Glorious Shield. - t.deepEquals(aliceCcPurse.getCurrentAmount().value, []); - t.deepEquals(aliceRpgPurse.getCurrentAmount().value, aGloriousShield); - t.deepEquals(bobCcPurse.getCurrentAmount().value, ['GrowlTiger']); - t.deepEquals(bobRpgPurse.getCurrentAmount().value, []); + t.deepEqual(aliceCcPurse.getCurrentAmount().value, []); + t.deepEqual(aliceRpgPurse.getCurrentAmount().value, aGloriousShield); + t.deepEqual(bobCcPurse.getCurrentAmount().value, ['GrowlTiger']); + t.deepEqual(bobRpgPurse.getCurrentAmount().value, []); }); diff --git a/packages/zoe/test/unitTests/contracts/test-escrowToVote.js b/packages/zoe/test/unitTests/contracts/test-escrowToVote.js index a7f0564c4a0..c9bb58d82c0 100644 --- a/packages/zoe/test/unitTests/contracts/test-escrowToVote.js +++ b/packages/zoe/test/unitTests/contracts/test-escrowToVote.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; @@ -71,12 +71,12 @@ test('zoe - escrowToVote', async t => { const voter = await voterP; const result = await E(voter).vote('YES'); - t.equals(result, `Successfully voted 'YES'`, `voter1 votes YES`); + t.is(result, `Successfully voted 'YES'`, `voter1 votes YES`); payoutP.then(async payout => { const moolaPayment = await payout.Assets; - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(moolaPayment), moola(3), `voter1 gets everything she escrowed back`, @@ -85,7 +85,7 @@ test('zoe - escrowToVote', async t => { console.log('EXPECTED ERROR ->>>'); t.throws( () => voter.vote('NO'), - /the escrowing offer is no longer active/, + { message: /the escrowing offer is no longer active/ }, `voter1 voting fails once offer is withdrawn or amounts are reallocated`, ); }); @@ -110,23 +110,23 @@ test('zoe - escrowToVote', async t => { const voter = await voterP; console.log('EXPECTED ERROR ->>>'); - t.rejects( + t.throwsAsync( () => E(voter).vote('NOT A VALID ANSWER'), - /the answer "NOT A VALID ANSWER" was not 'YES' or 'NO'/, + { message: /the answer "NOT A VALID ANSWER" was not 'YES' or 'NO'/ }, `A vote with an invalid answer throws`, ); const result1 = await E(voter).vote('YES'); - t.equals(result1, `Successfully voted 'YES'`, `voter2 votes YES`); + t.is(result1, `Successfully voted 'YES'`, `voter2 votes YES`); // Votes can be recast at any time const result2 = await E(voter).vote('NO'); - t.equals(result2, `Successfully voted 'NO'`, `voter 2 recast vote for NO`); + t.is(result2, `Successfully voted 'NO'`, `voter 2 recast vote for NO`); payoutP.then(async payout => { const moolaPayment = await payout.Assets; - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(moolaPayment), moola(5), `voter2 gets everything she escrowed back`, @@ -135,7 +135,7 @@ test('zoe - escrowToVote', async t => { console.log('EXPECTED ERROR ->>>'); t.throws( () => voter.vote('NO'), - /the escrowing offer is no longer active/, + { message: /the escrowing offer is no longer active/ }, `voter2 voting fails once offer is withdrawn or amounts are reallocated`, ); }); @@ -159,7 +159,7 @@ test('zoe - escrowToVote', async t => { const voter = await voterP; const result = await E(voter).vote('NO'); - t.equals(result, `Successfully voted 'NO'`, `voter3 votes NOT`); + t.is(result, `Successfully voted 'NO'`, `voter3 votes NOT`); // Voter3 completes their offer and exits before the election is // closed. Voter3's vote will not be counted. @@ -169,7 +169,7 @@ test('zoe - escrowToVote', async t => { const moolaPayment = await payout.Assets; - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(moolaPayment), moola(1), `voter3 gets everything she escrowed back`, @@ -178,7 +178,7 @@ test('zoe - escrowToVote', async t => { console.log('EXPECTED ERROR ->>>'); t.throws( () => voter.vote('NO'), - /the escrowing offer is no longer active/, + { message: /the escrowing offer is no longer active/ }, `voter3 voting fails once offer is withdrawn or amounts are reallocated`, ); }; @@ -202,12 +202,12 @@ test('zoe - escrowToVote', async t => { const voter = await voterP; const result = await E(voter).vote('YES'); - t.equals(result, `Successfully voted 'YES'`, `voter1 votes YES`); + t.is(result, `Successfully voted 'YES'`, `voter1 votes YES`); payoutP.then(async payout => { const moolaPayment = await payout.Assets; - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(moolaPayment), moola(4), `voter4 gets everything she escrowed back`, @@ -219,7 +219,7 @@ test('zoe - escrowToVote', async t => { // Secretary closes election and tallies the votes. const electionResults = await E(secretary).closeElection(); - t.deepEquals(electionResults, { YES: moola(7), NO: moola(5) }); + t.deepEqual(electionResults, { YES: moola(7), NO: moola(5) }); // Once the election is closed, the voters get their escrowed funds // back and can no longer vote. See the voter functions for the diff --git a/packages/zoe/test/unitTests/contracts/test-grifter.js b/packages/zoe/test/unitTests/contracts/test-grifter.js index cb5d80f93a1..0d4e8f65d4c 100644 --- a/packages/zoe/test/unitTests/contracts/test-grifter.js +++ b/packages/zoe/test/unitTests/contracts/test-grifter.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -53,9 +53,9 @@ test('zoe - grifter tries to steal; prevented by offer safety', async t => { vicPayments, ); - t.rejects( + t.throwsAsync( vicOutcomeP, - /The reallocation was not offer safe/, + { message: /The reallocation was not offer safe/ }, `vicOffer is rejected`, ); }); diff --git a/packages/zoe/test/unitTests/contracts/test-mintPayments.js b/packages/zoe/test/unitTests/contracts/test-mintPayments.js index 6e9425f111a..79f49fbb44f 100644 --- a/packages/zoe/test/unitTests/contracts/test-mintPayments.js +++ b/packages/zoe/test/unitTests/contracts/test-mintPayments.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -44,7 +44,7 @@ test('zoe - mint payments', async t => { value: [invitationValue], } = await E(invitationIssuer).getAmountOf(invitation); - t.equals( + t.is( invitationValue.installation, installation, 'installation is mintPayment', @@ -66,7 +66,7 @@ test('zoe - mint payments', async t => { const tokenPayoutAmount = await E(tokenIssuer).getAmountOf(paymentP); // Bob got 1000 tokens - t.deepEquals(tokenPayoutAmount, tokens1000); + t.deepEqual(tokenPayoutAmount, tokens1000); }, }; }; @@ -126,7 +126,7 @@ test('zoe - mint payments with unrelated give and want', async t => { value: [invitationValue], } = await E(invitationIssuer).getAmountOf(invitation); - t.equals( + t.is( invitationValue.installation, installation, 'installation is mintPayment', @@ -162,10 +162,10 @@ test('zoe - mint payments with unrelated give and want', async t => { ); // Bob got 1000 tokens - t.deepEquals(tokenPayoutAmount, tokens1000); + t.deepEqual(tokenPayoutAmount, tokens1000); // Got refunded all the moola given - t.deepEquals( + t.deepEqual( await E(moolaKit.issuer).getAmountOf(moolaRefundP), moolaKit.amountMath.make(10), ); diff --git a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js index ff13b4e98f2..dda1846256c 100644 --- a/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-multipoolAutoswap.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -62,7 +62,7 @@ test('multipoolAutoSwap with valid offers', async t => { const aliceInviteAmount = await inviteIssuer.getAmountOf( aliceAddLiquidityInvitation, ); - t.deepEquals( + t.deepEqual( aliceInviteAmount, inviteAmountMath.make( harden([ @@ -96,7 +96,7 @@ test('multipoolAutoSwap with valid offers', async t => { const simoleanLiquidity = simoleanLiquidityAmountMath.make; const issuerKeywordRecord = zoe.getIssuers(instance); - t.deepEquals( + t.deepEqual( issuerKeywordRecord, harden({ Central: centralR.issuer, @@ -107,12 +107,12 @@ test('multipoolAutoSwap with valid offers', async t => { }), `There are keywords for central token and two additional tokens and liquidity`, ); - t.deepEquals( + t.deepEqual( await E(publicFacet).getPoolAllocation(moolaR.brand), {}, `The poolAllocation object values for moola should be empty`, ); - t.deepEquals( + t.deepEqual( await E(publicFacet).getPoolAllocation(simoleanR.brand), {}, `The poolAllocation object values for simoleans should be empty`, @@ -136,7 +136,7 @@ test('multipoolAutoSwap with valid offers', async t => { alicePayments, ); - t.equals( + t.is( await E(addLiquiditySeat).getOfferResult(), 'Added liquidity.', `Alice added moola and central liquidity`, @@ -144,11 +144,11 @@ test('multipoolAutoSwap with valid offers', async t => { const liquidityPayout = await addLiquiditySeat.getPayout('Liquidity'); - t.deepEquals( + t.deepEqual( await moolaLiquidityIssuer.getAmountOf(liquidityPayout), moolaLiquidity(50), ); - t.deepEquals( + t.deepEqual( await E(publicFacet).getPoolAllocation(moolaR.brand), harden({ Secondary: moola(100), @@ -165,7 +165,7 @@ test('multipoolAutoSwap with valid offers', async t => { value: [bobInviteValue], } = await inviteIssuer.getAmountOf(bobSwapInvite1); const bobPublicFacet = zoe.getPublicFacet(bobInviteValue.instance); - t.equals( + t.is( bobInviteValue.installation, installation, `installation is as expected`, @@ -176,7 +176,7 @@ test('multipoolAutoSwap with valid offers', async t => { moola(17), centralR.brand, ); - t.deepEquals( + t.deepEqual( priceInCentrals, centralTokens(7), `price in central tokens of 17 moola is as expected`, @@ -195,7 +195,7 @@ test('multipoolAutoSwap with valid offers', async t => { bobMoolaForCentralPayments, ); - t.equal(await E(bobSeat).getOfferResult(), 'Swap successfully completed.'); + t.is(await E(bobSeat).getOfferResult(), 'Swap successfully completed.'); const bobMoolaPayout1 = await bobSeat.getPayout('In'); const bobCentralPayout1 = await bobSeat.getPayout('Out'); @@ -210,7 +210,7 @@ test('multipoolAutoSwap with valid offers', async t => { centralTokens(7), `bob gets the same price as when he called the getCurrentPrice method`, ); - t.deepEquals( + t.deepEqual( await E(bobPublicFacet).getPoolAllocation(moolaR.brand), { Secondary: moola(117), @@ -228,7 +228,7 @@ test('multipoolAutoSwap with valid offers', async t => { centralTokens(7), moolaR.brand, ); - t.deepEquals( + t.deepEqual( moolaAmounts, moola(16), `the fee was one moola over the two trades`, @@ -250,7 +250,7 @@ test('multipoolAutoSwap with valid offers', async t => { centralForMoolaPayments, ); - t.equal( + t.is( await bobSeat2.getOfferResult(), 'Swap successfully completed.', `second swap successful`, @@ -304,7 +304,7 @@ test('multipoolAutoSwap with valid offers', async t => { aliceSimCentralPayments, ); - t.equals( + t.is( await aliceSeat2.getOfferResult(), 'Added liquidity.', `Alice added simoleans and central liquidity`, @@ -312,12 +312,12 @@ test('multipoolAutoSwap with valid offers', async t => { const simoleanLiquidityPayout = await aliceSeat2.getPayout('Liquidity'); - t.deepEquals( + t.deepEqual( await simoleanLiquidityIssuer.getAmountOf(simoleanLiquidityPayout), simoleanLiquidity(43), `simoleanLiquidity minted was equal to the amount of central tokens added to pool`, ); - t.deepEquals( + t.deepEqual( await E(publicFacet).getPoolAllocation(simoleanR.brand), harden({ Secondary: simoleans(398), @@ -338,7 +338,7 @@ test('multipoolAutoSwap with valid offers', async t => { simoleans(74), moolaR.brand, ); - t.deepEquals( + t.deepEqual( priceInMoola, moola(10), `price is as expected for secondary token to secondary token`, @@ -349,7 +349,7 @@ test('multipoolAutoSwap with valid offers', async t => { simoleans(74), centralR.brand, ); - t.deepEquals( + t.deepEqual( priceInCentral, centralTokens(6), `price is as expected for secondary token to central`, @@ -359,7 +359,7 @@ test('multipoolAutoSwap with valid offers', async t => { centralTokens(6), moolaR.brand, ); - t.deepEquals( + t.deepEqual( centralPriceInMoola, moola(10), `price is as expected for secondary token to secondary token`, @@ -433,7 +433,7 @@ test('multipoolAutoSwap with valid offers', async t => { harden({ Liquidity: liquidityPayout }), ); - t.equals( + t.is( await aliceSeat3.getOfferResult(), 'Liquidity successfully removed.', ); @@ -442,17 +442,17 @@ test('multipoolAutoSwap with valid offers', async t => { const aliceCentralPayout = await aliceSeat3.getPayout('Central'); const aliceLiquidityPayout = await aliceSeat3.getPayout('Liquidity'); - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(91), `alice gets all the moola in the pool`, ); - t.deepEquals( + t.deepEqual( await centralR.issuer.getAmountOf(aliceCentralPayout), centralTokens(56), `alice gets all the central tokens in the pool`, ); - t.deepEquals( + t.deepEqual( await moolaLiquidityIssuer.getAmountOf(aliceLiquidityPayout), moolaLiquidity(0), `alice gets no liquidity tokens`, diff --git a/packages/zoe/test/unitTests/contracts/test-publicAuction.js b/packages/zoe/test/unitTests/contracts/test-publicAuction.js index d20e12a2e02..e725dbc7e8d 100644 --- a/packages/zoe/test/unitTests/contracts/test-publicAuction.js +++ b/packages/zoe/test/unitTests/contracts/test-publicAuction.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; import { E } from '@agoric/eventual-send'; @@ -56,7 +56,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { .getPayout('Asset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, moola(0), `Alice didn't get any of what she put in`, @@ -67,7 +67,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { .getPayout('Ask') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, simoleans(7), `Alice got the second price bid, Carol's bid, even though Bob won`, @@ -96,23 +96,23 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { value: [invitationValue], } = await invitationIssuer.getAmountOf(invitation); - t.equals( + t.is( invitationValue.installation, installation, 'installation is publicAuction', ); - t.deepEquals( + t.deepEqual( invitationValue.auctionedAssets, moola(1), `asset to be auctioned is 1 moola`, ); - t.deepEquals( + t.deepEqual( invitationValue.minimumBid, simoleans(3), `minimum bid is 3 simoleans`, ); - t.deepEquals( + t.deepEqual( invitationValue.numBidsAllowed, 3, `auction will be closed after 3 bids`, @@ -126,7 +126,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { const seat = await zoe.offer(invitation, proposal, payments); - t.equals( + t.is( await E(seat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -135,7 +135,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { .getPayout('Asset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, proposal.want.Asset, `Bob wins the auction`, @@ -146,7 +146,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { .getPayout('Bid') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals( + t.deepEqual( amountDeposited, simoleans(4), `Bob gets the difference between the second-price bid (Carol's 7 simoleans) and his bid back`, @@ -172,7 +172,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { const seat = await zoe.offer(invitation, proposal, payments); - t.equals( + t.is( await E(seat).getOfferResult(), 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -181,14 +181,14 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { .getPayout('Asset') .then(moolaPurse.deposit) .then(amountDeposited => - t.deepEquals(amountDeposited, moola(0), `didn't win the auction`), + t.deepEqual(amountDeposited, moola(0), `didn't win the auction`), ); E(seat) .getPayout('Bid') .then(simoleanPurse.deposit) .then(amountDeposited => - t.deepEquals(amountDeposited, bidAmount, `full refund`), + t.deepEqual(amountDeposited, bidAmount, `full refund`), ); }, }); @@ -277,7 +277,7 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { const [bobInvite] = await E(publicAPI).makeInvites(1); - t.equals( + t.is( await aliceOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', ); @@ -302,7 +302,7 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { bobPayments, ); - t.rejects( + t.throwsAsync( () => brokenOutcomeP, new Error( 'The item up for auction has been withdrawn or the auction has completed', @@ -319,13 +319,13 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { const bobSimoleanPayout = await bobResult.Bid; // Alice (the creator of the auction) gets back what she put in - t.deepEquals( + t.deepEqual( await moolaR.issuer.getAmountOf(aliceMoolaPayout), aliceProposal.give.Asset, ); // Alice didn't get any of what she wanted - t.deepEquals( + t.deepEqual( await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), simoleans(0), ); @@ -335,8 +335,8 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { await aliceSimoleanPurse.deposit(aliceSimoleanPayout); // Bob gets a refund - t.deepEquals(await moolaR.issuer.getAmountOf(bobMoolaPayout), moola(0)); - t.deepEquals( + t.deepEqual(await moolaR.issuer.getAmountOf(bobMoolaPayout), moola(0)); + t.deepEqual( await simoleanR.issuer.getAmountOf(bobSimoleanPayout), bobProposal.give.Bid, ); @@ -350,10 +350,10 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { // Bob had 0 moola and 11 simoleans. // Carol had 0 moola and 7 simoleans. // Dave had 0 moola and 5 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().value, 1); - t.equals(aliceSimoleanPurse.getCurrentAmount().value, 0); - t.equals(bobMoolaPurse.getCurrentAmount().value, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().value, 11); + t.is(aliceMoolaPurse.getCurrentAmount().value, 1); + t.is(aliceSimoleanPurse.getCurrentAmount().value, 0); + t.is(bobMoolaPurse.getCurrentAmount().value, 0); + t.is(bobSimoleanPurse.getCurrentAmount().value, 11); } catch (e) { t.assert(false, e); console.log(e); @@ -428,7 +428,7 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { 3, ); - t.equals( + t.is( await aliceOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', 'aliceOutcome', @@ -447,11 +447,11 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { issuerKeywordRecord: bobIssuers, } = zoe.getInstanceRecord(bobInviteValue.instanceHandle); - t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); - t.deepEquals(bobIssuers, { Asset: ccIssuer, Ask: moolaIssuer }, 'bobIssuers'); - t.equals(bobTerms.numBidsAllowed, 3, 'bobTerms'); - t.deepEquals(bobInviteValue.minimumBid, moola(3), 'minimumBid'); - t.deepEquals( + t.is(bobInstallationId, installationHandle, 'bobInstallationId'); + t.deepEqual(bobIssuers, { Asset: ccIssuer, Ask: moolaIssuer }, 'bobIssuers'); + t.is(bobTerms.numBidsAllowed, 3, 'bobTerms'); + t.deepEqual(bobInviteValue.minimumBid, moola(3), 'minimumBid'); + t.deepEqual( bobInviteValue.auctionedAssets, cryptoCats(harden(['Felix'])), 'assets', @@ -471,7 +471,7 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { bobPayments, ); - t.equals( + t.is( await bobOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', 'bobOutcome', @@ -490,15 +490,15 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { issuerKeywordRecord: carolIssuers, } = zoe.getInstanceRecord(carolInviteValue.instanceHandle); - t.equals(carolInstallationId, installationHandle, 'carolInstallationId'); - t.deepEquals( + t.is(carolInstallationId, installationHandle, 'carolInstallationId'); + t.deepEqual( carolIssuers, { Asset: ccIssuer, Ask: moolaIssuer }, 'carolIssuers', ); - t.equals(carolTerms.numBidsAllowed, 3, 'carolTerms'); - t.deepEquals(carolInviteValue.minimumBid, moola(3), 'carolMinimumBid'); - t.deepEquals( + t.is(carolTerms.numBidsAllowed, 3, 'carolTerms'); + t.deepEqual(carolInviteValue.minimumBid, moola(3), 'carolMinimumBid'); + t.deepEqual( carolInviteValue.auctionedAssets, cryptoCats(harden(['Felix'])), 'carolAuctionedAssets', @@ -518,7 +518,7 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { carolPayments, ); - t.equals( + t.is( await carolOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', 'carolOutcome', @@ -536,15 +536,15 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { issuerKeywordRecord: daveIssuers, } = zoe.getInstanceRecord(daveInviteValue.instanceHandle); - t.equals(daveInstallationId, installationHandle, 'daveInstallationHandle'); - t.deepEquals( + t.is(daveInstallationId, installationHandle, 'daveInstallationHandle'); + t.deepEqual( daveIssuers, { Asset: ccIssuer, Ask: moolaIssuer }, 'daveIssuers', ); - t.equals(daveTerms.numBidsAllowed, 3, 'bobTerms'); - t.deepEquals(daveInviteValue.minimumBid, moola(3), 'daveMinimumBid'); - t.deepEquals( + t.is(daveTerms.numBidsAllowed, 3, 'bobTerms'); + t.deepEqual(daveInviteValue.minimumBid, moola(3), 'daveMinimumBid'); + t.deepEqual( daveInviteValue.auctionedAssets, cryptoCats(harden(['Felix'])), 'daveAssets', @@ -564,7 +564,7 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { davePayments, ); - t.equals( + t.is( await daveOutcomeP, 'The offer has been accepted. Once the contract has been completed, please check your payout', 'daveOutcome', @@ -588,14 +588,14 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { const daveMoolaPayout = await daveResult.Bid; // Alice (the creator of the auction) gets back the second highest bid - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(aliceMoolaPayout), carolProposal.give.Bid, `alice gets carol's bid`, ); // Alice didn't get any of what she put in - t.deepEquals( + t.deepEqual( await ccIssuer.getAmountOf(aliceCcPayout), cryptoCats(harden([])), `alice gets nothing of what she put in`, @@ -607,12 +607,12 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { // Bob (the winner of the auction) gets the one moola and the // difference between his bid and the price back - t.deepEquals( + t.deepEqual( await ccIssuer.getAmountOf(bobCcPayout), cryptoCats(harden(['Felix'])), `bob is the winner`, ); - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(bobMoolaPayout), moola(4), `bob gets difference back`, @@ -623,12 +623,12 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { await bobMoolaPurse.deposit(bobMoolaPayout); // Carol gets a full refund - t.deepEquals( + t.deepEqual( await ccIssuer.getAmountOf(carolCcPayout), cryptoCats(harden([])), `carol doesn't win`, ); - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(carolMoolaPayout), carolProposal.give.Bid, `carol gets a refund`, @@ -639,7 +639,7 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { await carolMoolaPurse.deposit(carolMoolaPayout); // Dave gets a full refund - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(daveMoolaPayout), daveProposal.give.Bid, `dave gets a refund`, @@ -660,12 +660,12 @@ test('zoe - secondPriceAuction non-fungible asset', async t => { // Bob: the CryptoCat and 4 moola // Carol: an empty CryptoCat purse and 7 moola // Dave: an empty CryptoCat purse and 5 moola - t.deepEquals(aliceCcPurse.getCurrentAmount().value, []); - t.equals(aliceMoolaPurse.getCurrentAmount().value, 7); - t.deepEquals(bobCcPurse.getCurrentAmount().value, ['Felix']); - t.equals(bobMoolaPurse.getCurrentAmount().value, 4); - t.deepEquals(carolCcPurse.getCurrentAmount().value, []); - t.equals(carolMoolaPurse.getCurrentAmount().value, 7); - t.deepEquals(daveCcPurse.getCurrentAmount().value, []); - t.equals(daveMoolaPurse.getCurrentAmount().value, 5); + t.deepEqual(aliceCcPurse.getCurrentAmount().value, []); + t.is(aliceMoolaPurse.getCurrentAmount().value, 7); + t.deepEqual(bobCcPurse.getCurrentAmount().value, ['Felix']); + t.is(bobMoolaPurse.getCurrentAmount().value, 4); + t.deepEqual(carolCcPurse.getCurrentAmount().value, []); + t.is(carolMoolaPurse.getCurrentAmount().value, 7); + t.deepEqual(daveCcPurse.getCurrentAmount().value, []); + t.is(daveMoolaPurse.getCurrentAmount().value, 5); }); diff --git a/packages/zoe/test/unitTests/contracts/test-sellTickets.js b/packages/zoe/test/unitTests/contracts/test-sellTickets.js index 83b8d047374..65f448b244f 100644 --- a/packages/zoe/test/unitTests/contracts/test-sellTickets.js +++ b/packages/zoe/test/unitTests/contracts/test-sellTickets.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -45,7 +45,7 @@ test(`mint and sell tickets for multiple shows`, async t => { sellItemsInstallation, pricePerItem: moolaAmountMath.make(20), }); - t.equals( + t.is( await sellItemsCreatorSeat.getOfferResult(), defaultAcceptanceMsg, `escrowTicketsOutcome is default acceptance message`, @@ -56,7 +56,7 @@ test(`mint and sell tickets for multiple shows`, async t => { const sellItemsInstance = await E(sellItemsCreatorFacet).getInstance(); const ticketSalesPublicFacet = await E(zoe).getPublicFacet(sellItemsInstance); const ticketsForSale = await E(ticketSalesPublicFacet).getAvailableItems(); - t.deepEquals( + t.deepEqual( ticketsForSale, { brand: ticketBrand, @@ -96,7 +96,7 @@ test(`mint and sell tickets for multiple shows`, async t => { const sellItemsInstance2 = sellItemsCreatorFacet2.getInstance(); const sellItemsPublicFacet2 = await E(zoe).getPublicFacet(sellItemsInstance2); const ticketsForSale2 = await E(sellItemsPublicFacet2).getAvailableItems(); - t.deepEquals( + t.deepEqual( ticketsForSale2, { brand: ticketBrand, @@ -115,7 +115,7 @@ test(`mint and sell tickets for multiple shows`, async t => { }, `we can reuse the mint to make more tickets and sell them in a different instance`, ); - t.end(); + return; // t.end(); }); // __Test Scenario__ @@ -181,7 +181,7 @@ test(`mint and sell opera tickets`, async t => { const ticketsForSale = await E(ticketSalesPublicFacet).getAvailableItems(); - t.equal(ticketsForSale.value.length, 3, `3 tickets for sale`); + t.is(ticketsForSale.value.length, 3, `3 tickets for sale`); return harden({ sellItemsCreatorSeat, @@ -205,26 +205,26 @@ test(`mint and sell opera tickets`, async t => { const alicePurse = await E(moolaIssuer).makeEmptyPurse(); await E(alicePurse).deposit(moola100Payment); - t.deepEquals(terms.pricePerItem, moola(22), `pricePerItem is 22 moola`); + t.deepEqual(terms.pricePerItem, moola(22), `pricePerItem is 22 moola`); const availableTickets = await E( ticketSalesPublicFacet, ).getAvailableItems(); - t.equal( + t.is( availableTickets.value.length, 3, 'Alice should see 3 available tickets', ); - t.ok( + t.assert( availableTickets.value.find(ticket => ticket.number === 1), `availableTickets contains ticket number 1`, ); - t.ok( + t.assert( availableTickets.value.find(ticket => ticket.number === 2), `availableTickets contains ticket number 2`, ); - t.ok( + t.assert( availableTickets.value.find(ticket => ticket.number === 3), `availableTickets contains ticket number 3`, ); @@ -255,12 +255,12 @@ test(`mint and sell opera tickets`, async t => { aliceTickets, ); - t.equal( + t.is( aliceBoughtTicketAmount.value[0].show, 'Steven Universe, the Opera', 'Alice should have received the ticket for the correct show', ); - t.equal( + t.is( aliceBoughtTicketAmount.value[0].number, 1, 'Alice should have received the ticket for the correct number', @@ -317,9 +317,9 @@ test(`mint and sell opera tickets`, async t => { console.log( 'EXPECTED ERROR: Some of the wanted items were not available for sale >>>', ); - t.rejects( + t.throwsAsync( seat.getOfferResult(), - /Some of the wanted items were not available for sale/, + { message: /Some of the wanted items were not available for sale/ }, 'ticket 1 is no longer available', ); @@ -330,11 +330,11 @@ test(`mint and sell opera tickets`, async t => { seat.getPayout('Money'), ); - t.ok( + t.assert( ticketAmountMath.isEmpty(jokerTicketPayoutAmount), 'Joker should not receive ticket #1', ); - t.deepEquals( + t.deepEqual( jokerMoneyPayoutAmount, moola(22), 'Joker should get a refund after trying to get ticket #1', @@ -391,9 +391,9 @@ test(`mint and sell opera tickets`, async t => { console.log( 'EXPECTED ERROR: More money is required to buy these items >>> ', ); - t.rejects( + t.throwsAsync( seat.getOfferResult(), - /More money.*is required to buy these items/, + { message: /More money.*is required to buy these items/ }, 'outcome from Joker should throw when trying to buy a ticket for 1 moola', ); @@ -404,11 +404,11 @@ test(`mint and sell opera tickets`, async t => { seat.getPayout('Money'), ); - t.ok( + t.assert( ticketAmountMath.isEmpty(jokerTicketPayoutAmount), 'Joker should not receive ticket #2', ); - t.deepEquals( + t.deepEqual( jokerMoneyPayoutAmount, insufficientAmount, 'Joker should get a refund after trying to get ticket #2 for 1 moola', @@ -437,20 +437,20 @@ test(`mint and sell opera tickets`, async t => { ).getAvailableItems(); // Bob sees the currently available tickets - t.equal( + t.is( availableTickets.value.length, 2, 'Bob should see 2 available tickets', ); - t.ok( + t.assert( !availableTickets.value.find(ticket => ticket.number === 1), `availableTickets should NOT contain ticket number 1`, ); - t.ok( + t.assert( availableTickets.value.find(ticket => ticket.number === 2), `availableTickets should still contain ticket number 2`, ); - t.ok( + t.assert( availableTickets.value.find(ticket => ticket.number === 3), `availableTickets should still contain ticket number 3`, ); @@ -483,16 +483,16 @@ test(`mint and sell opera tickets`, async t => { const bobTicketAmount = await E(ticketIssuer).getAmountOf( seat.getPayout('Items'), ); - t.equal( + t.is( bobTicketAmount.value.length, 2, 'Bob should have received 2 tickets', ); - t.ok( + t.assert( bobTicketAmount.value.find(ticket => ticket.number === 2), 'Bob should have received tickets #2', ); - t.ok( + t.assert( bobTicketAmount.value.find(ticket => ticket.number === 3), 'Bob should have received tickets #3', ); @@ -506,7 +506,7 @@ test(`mint and sell opera tickets`, async t => { const availableTickets = await E(sellItemsCreatorFacet).getAvailableItems(); const ticketIssuer = await E(sellItemsCreatorFacet).getItemsIssuer(); const ticketAmountMath = await E(ticketIssuer).getAmountMath(); - t.ok( + t.assert( ticketAmountMath.isEmpty(availableTickets), 'All the tickets have been sold', ); @@ -519,7 +519,7 @@ test(`mint and sell opera tickets`, async t => { await E(operaPurse).deposit(moneyPayment); const currentPurseBalance = await E(operaPurse).getCurrentAmount(); - t.equal( + t.is( currentPurseBalance.value, 3 * 22, `The Opera should get ${3 * 22} moolas from ticket sales`, @@ -548,5 +548,5 @@ test(`mint and sell opera tickets`, async t => { moolaMint.mintPayment(moola(100)), ); await ticketSellerClosesContract(sellItemsCreatorSeat, sellItemsCreatorFacet); - t.end(); + return; // t.end(); }); diff --git a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js index 144c29d243d..b0e923f580e 100644 --- a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js +++ b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js @@ -1,7 +1,7 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import { E } from '@agoric/eventual-send'; @@ -60,7 +60,7 @@ test('simpleExchange with valid offers', async t => { E(aliceNotifier) .getUpdateSince() .then(({ value: beforeAliceOrders, updateCount: beforeAliceCount }) => { - t.deepEquals( + t.deepEqual( beforeAliceOrders, { buys: [], @@ -68,13 +68,13 @@ test('simpleExchange with valid offers', async t => { }, `Order book is empty`, ); - t.equals(beforeAliceCount, 3); + t.is(beforeAliceCount, 3); }); const { value: initialOrders, } = await publicFacet.getNotifier().getUpdateSince(); - t.deepEquals( + t.deepEqual( initialOrders, { buys: [], sells: [] }, `order notifier is initialized`, @@ -99,7 +99,7 @@ test('simpleExchange with valid offers', async t => { E(aliceNotifier) .getUpdateSince() .then(({ value: afterAliceOrders, updateCount: afterAliceCount }) => { - t.deepEquals( + t.deepEqual( afterAliceOrders, { buys: [], @@ -112,11 +112,11 @@ test('simpleExchange with valid offers', async t => { }, `order notifier is updated with Alice's sell order`, ); - t.equals(afterAliceCount, 4); + t.is(afterAliceCount, 4); aliceNotifier.getUpdateSince(afterAliceCount).then(update => { - t.notOk(update.value.sells[0], 'accepted offer from Bob'); - t.equals(update.updateCount, 5); + t.falsy(update.value.sells[0], 'accepted offer from Bob'); + t.is(update.updateCount, 5); }); }); @@ -131,7 +131,7 @@ test('simpleExchange with valid offers', async t => { const bobIssuers = zoe.getIssuers(instance); - t.equals(bobInstallation, installation); + t.is(bobInstallation, installation); assert( bobIssuers.Asset === moolaIssuer, @@ -162,7 +162,7 @@ test('simpleExchange with valid offers', async t => { const { value: afterBobOrders } = await E( E(publicFacet).getNotifier(), ).getUpdateSince(); - t.deepEquals( + t.deepEqual( afterBobOrders, { buys: [], sells: [] }, `order notifier is updated when Bob fulfills the order`, @@ -182,7 +182,7 @@ test('simpleExchange with valid offers', async t => { } = await aliceSeat.getPayouts(); // Alice gets paid at least what she wanted - t.ok( + t.assert( amountMaths .get('simoleans') .isGTE( @@ -193,7 +193,7 @@ test('simpleExchange with valid offers', async t => { ); // Alice sold all of her moola - t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); + t.deepEqual(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); // 6: Alice deposits her payout to ensure she can // Alice had 0 moola and 4 simoleans. @@ -304,7 +304,7 @@ test('simpleExchange with multiple sell offers', async t => { { want: { Price: simoleans(8) }, give: { Asset: moola(5) } }, ], }; - t.deepEquals( + t.deepEqual( (await E(E(publicFacet).getNotifier()).getUpdateSince()).value, expectedBook, ); @@ -377,7 +377,7 @@ test('simpleExchange with non-fungible assets', async t => { } = await getInviteFields(inviteIssuer, bobInvite); const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - t.equals(bobInstallation, installation); + t.is(bobInstallation, installation); const bobIssuers = zoe.getIssuers(bobInstance); assert( @@ -420,7 +420,7 @@ test('simpleExchange with non-fungible assets', async t => { } = await aliceSeat.getPayouts(); // Alice gets paid at least what she wanted - t.ok( + t.assert( amountMaths .get('cc') .isGTE( @@ -430,7 +430,7 @@ test('simpleExchange with non-fungible assets', async t => { ); // Alice sold the Spell - t.deepEquals( + t.deepEqual( await rpgIssuer.getAmountOf(aliceRpgPayout), rpgItems(harden([])), ); diff --git a/packages/zoe/test/unitTests/contracts/test-useObj.js b/packages/zoe/test/unitTests/contracts/test-useObj.js index f53514ed43d..c93f03b0bdb 100644 --- a/packages/zoe/test/unitTests/contracts/test-useObj.js +++ b/packages/zoe/test/unitTests/contracts/test-useObj.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -48,7 +48,7 @@ test('zoe - useObj', async t => { const useObj = await useObjP; - t.equals( + t.is( useObj.colorPixels('purple'), `successfully colored 3 pixels purple`, `use of use object works`, @@ -60,7 +60,7 @@ test('zoe - useObj', async t => { const aliceMoolaPayoutPayment = await alicePayout.Pixels; - t.deepEquals( + t.deepEqual( await moolaIssuer.getAmountOf(aliceMoolaPayoutPayment), moola(3), `alice gets everything she escrowed back`, @@ -69,7 +69,7 @@ test('zoe - useObj', async t => { console.log('EXPECTED ERROR ->>>'); t.throws( () => useObj.colorPixels('purple'), - /the escrowing offer is no longer active/, + { message: /the escrowing offer is no longer active/ }, `use of use object fails once offer is withdrawn or amounts are reallocated`, ); }); diff --git a/packages/zoe/test/unitTests/contracts/test-zcf.js b/packages/zoe/test/unitTests/contracts/test-zcf.js index 48945ec5f70..672a2bd1e3e 100644 --- a/packages/zoe/test/unitTests/contracts/test-zcf.js +++ b/packages/zoe/test/unitTests/contracts/test-zcf.js @@ -1,6 +1,6 @@ import '@agoric/install-ses'; // eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; +import test from 'ava'; // eslint-disable-next-line import/no-extraneous-dependencies import bundleSource from '@agoric/bundle-source'; @@ -26,7 +26,7 @@ test('zoe - test zcf', async t => { Pixels: moolaIssuer, Money: simoleanIssuer, }); - t.doesNotReject(() => + t.notThrowsAsync(() => zoe.makeInstance(installationHandle, issuerKeywordRecord), ); }); diff --git a/packages/zoe/test/unitTests/test-cleanProposal.js b/packages/zoe/test/unitTests/test-cleanProposal.js index 9fdb987a312..51750a82281 100644 --- a/packages/zoe/test/unitTests/test-cleanProposal.js +++ b/packages/zoe/test/unitTests/test-cleanProposal.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeStore from '@agoric/weak-store'; import { cleanProposal } from '../../src/zoeService/cleanProposal'; import { setup } from './setupBasicMints'; @@ -32,7 +32,7 @@ test('cleanProposal test', t => { const actual = cleanProposal(getAmountMathForBrand, proposal); - t.deepEquals(actual, expected); + t.deepEqual(actual, expected); } catch (e) { t.assert(false, e); } @@ -63,7 +63,7 @@ test('cleanProposal - all empty', t => { }); // cleanProposal no longer fills in empty keywords - t.deepEquals(cleanProposal(getAmountMathForBrand, proposal), expected); + t.deepEqual(cleanProposal(getAmountMathForBrand, proposal), expected); } catch (e) { t.assert(false, e); } @@ -97,9 +97,9 @@ test('cleanProposal - repeated brands', t => { }); // cleanProposal no longer fills in empty keywords const actual = cleanProposal(getAmountMathForBrand, proposal); - t.deepEquals(actual.want, expected.want); - t.deepEquals(actual.give, expected.give); - t.deepEquals(actual.exit, expected.exit); + t.deepEqual(actual.want, expected.want); + t.deepEqual(actual.give, expected.give); + t.deepEqual(actual.exit, expected.exit); } catch (e) { t.assert(false, e); } diff --git a/packages/zoe/test/unitTests/test-objArrayConversion.js b/packages/zoe/test/unitTests/test-objArrayConversion.js index 728511b5297..83e1a4b4bd9 100644 --- a/packages/zoe/test/unitTests/test-objArrayConversion.js +++ b/packages/zoe/test/unitTests/test-objArrayConversion.js @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { arrayToObj, objToArray } from '../../src/objArrayConversion'; @@ -9,19 +9,19 @@ test('arrayToObj', t => { try { const keywords = ['X', 'Y']; const array = [1, 2]; - t.deepEquals(arrayToObj(array, keywords), { X: 1, Y: 2 }); + t.deepEqual(arrayToObj(array, keywords), { X: 1, Y: 2 }); const keywords2 = ['X', 'Y', 'Z']; t.throws( () => arrayToObj(array, keywords2), - /Error: array and keys must be of equal length/, + { message: /Error: array and keys must be of equal length/ }, `unequal length should throw`, ); const array2 = [4, 5, 2]; t.throws( () => arrayToObj(array2, keywords), - /Error: array and keys must be of equal length/, + { message: /Error: array and keys must be of equal length/ }, `unequal length should throw`, ); } catch (e) { @@ -34,19 +34,19 @@ test('objToArray', t => { try { const keywords = ['X', 'Y']; const obj = { X: 1, Y: 2 }; - t.deepEquals(objToArray(obj, keywords), [1, 2]); + t.deepEqual(objToArray(obj, keywords), [1, 2]); const keywords2 = ['X', 'Y', 'Z']; t.throws( () => objToArray(obj, keywords2), - /Error: object keys \["X","Y"\] and keywords \["X","Y","Z"\] must be of equal length/, + { message: /Error: object keys \["X","Y"\] and keywords \["X","Y","Z"\] must be of equal length/ }, `unequal length should throw`, ); const obj2 = { X: 1, Y: 2, Z: 5 }; t.throws( () => objToArray(obj2, keywords), - /Error: object keys \["X","Y","Z"\] and keywords \["X","Y"\] must be of equal length/, + { message: /Error: object keys \["X","Y","Z"\] and keywords \["X","Y"\] must be of equal length/ }, `unequal length should throw`, ); } catch (e) { diff --git a/packages/zoe/test/unitTests/test-offerSafety.js b/packages/zoe/test/unitTests/test-offerSafety.js index d2ceba59abd..e516b090cde 100644 --- a/packages/zoe/test/unitTests/test-offerSafety.js +++ b/packages/zoe/test/unitTests/test-offerSafety.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import { isOfferSafe } from '../../src/contractFacet/offerSafety'; import { setup } from './setupBasicMints'; @@ -44,7 +44,7 @@ test('isOfferSafe - more than want, more than give', t => { }); const amounts = harden({ A: moola(10), B: simoleans(7), C: bucks(8) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -66,7 +66,7 @@ test('isOfferSafe - more than want, less than give', t => { }); const amounts = harden({ A: moola(1), B: simoleans(7), C: bucks(8) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -88,7 +88,7 @@ test('isOfferSafe - more than want, equal to give', t => { }); const amounts = harden({ A: moola(9), B: simoleans(6), C: bucks(7) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -110,7 +110,7 @@ test('isOfferSafe - less than want, more than give', t => { }); const amounts = harden({ A: moola(7), B: simoleans(9), C: bucks(19) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -132,7 +132,7 @@ test('isOfferSafe - less than want, less than give', t => { }); const amounts = harden({ A: moola(7), B: simoleans(5), C: bucks(6) }); - t.notOk(isOfferSafe(getAmountMath, proposal, amounts)); + t.falsy(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -154,7 +154,7 @@ test('isOfferSafe - less than want, equal to give', t => { }); const amounts = harden({ A: moola(1), B: simoleans(5), C: bucks(7) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -176,7 +176,7 @@ test('isOfferSafe - equal to want, more than give', t => { }); const amounts = harden({ A: moola(2), B: simoleans(6), C: bucks(8) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -198,7 +198,7 @@ test('isOfferSafe - equal to want, less than give', t => { }); const amounts = harden({ A: moola(0), B: simoleans(6), C: bucks(0) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -220,7 +220,7 @@ test('isOfferSafe - equal to want, equal to give', t => { }); const amounts = harden({ A: moola(1), B: simoleans(6), C: bucks(7) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } @@ -238,7 +238,7 @@ test('isOfferSafe - empty proposal', t => { const proposal = harden({ give: {}, want: {} }); const amounts = harden({ A: moola(1), B: simoleans(6), C: bucks(7) }); - t.ok(isOfferSafe(getAmountMath, proposal, amounts)); + t.assert(isOfferSafe(getAmountMath, proposal, amounts)); } catch (e) { t.assert(false, e); } diff --git a/packages/zoe/test/unitTests/test-rightsConservation.js b/packages/zoe/test/unitTests/test-rightsConservation.js index e733fb8feb7..29e5c8932b2 100644 --- a/packages/zoe/test/unitTests/test-rightsConservation.js +++ b/packages/zoe/test/unitTests/test-rightsConservation.js @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import '@agoric/install-ses'; -import { test } from 'tape-promise/tape'; +import test from 'ava'; import makeStore from '@agoric/weak-store'; import makeIssuerKit from '@agoric/ertp'; @@ -49,7 +49,7 @@ test(`areRightsConserved - true for amount with nat values`, t => { ).flat(); const newAmounts = makeAmountMatrix(amountMathArray, newValues).flat(); - t.ok( + t.assert( areRightsConserved(getAmountMathForBrand, previousAmounts, newAmounts), ); } catch (e) { @@ -76,7 +76,7 @@ test(`areRightsConserved - false for amount with Nat values`, t => { const oldAmounts = makeAmountMatrix(amountMathArray, oldValues).flat(); const newAmounts = makeAmountMatrix(amountMathArray, newValues).flat(); - t.notOk(areRightsConserved(getAmountMathForBrand, oldAmounts, newAmounts)); + t.falsy(areRightsConserved(getAmountMathForBrand, oldAmounts, newAmounts)); } catch (e) { t.assert(false, e); } @@ -89,7 +89,7 @@ test(`areRightsConserved - empty arrays`, t => { const oldAmounts = []; const newAmounts = []; - t.ok(areRightsConserved(getAmountMathForBrand, oldAmounts, newAmounts)); + t.assert(areRightsConserved(getAmountMathForBrand, oldAmounts, newAmounts)); } catch (e) { t.assert(false, e); } diff --git a/test/explicit.js b/test/explicit.js index 2aebee71711..c7f5aeb3e55 100644 --- a/test/explicit.js +++ b/test/explicit.js @@ -1,4 +1,4 @@ -import { test } from 'tape-promise/tape'; +import test from 'ava'; import bundleSource from '..'; function runTests({ rollup, pathResolve, resolvePlugin, dirname }) { diff --git a/yarn.lock b/yarn.lock index 5536963d79d..ef384ebb4a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -537,6 +537,13 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@concordance/react@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@concordance/react/-/react-2.0.0.tgz#aef913f27474c53731f4fd79cc2f54897de90fde" + integrity sha512-huLSkUuM2/P+U0uy2WwlKuixMsTODD8p4JVQBI4VKeopkiN0C7M3N9XYVawb4M+4spN5RrO/eLhk7KoQX6nsfA== + dependencies: + arrify "^1.0.1" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -1325,11 +1332,32 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@octokit/auth-token@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.0.tgz#b64178975218b99e4dfe948253f0673cbbb59d9f" @@ -1492,6 +1520,18 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -1536,6 +1576,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99" integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg== +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1624,12 +1669,12 @@ acorn-node@^1.6.1: acorn-walk "^7.0.0" xtend "^4.0.2" -acorn-walk@^7.0.0: +acorn-walk@^7.0.0, acorn-walk@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^7.0.0: +acorn@^7.0.0, acorn@^7.3.1: version "7.4.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== @@ -1660,6 +1705,14 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" +aggregate-error@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" + integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv@^6.10.0, ajv@^6.10.2: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" @@ -1685,6 +1738,13 @@ alphanum-sort@^1.0.0: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== + dependencies: + string-width "^3.0.0" + ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -1729,7 +1789,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== @@ -1869,6 +1929,11 @@ array-union@^1.0.2: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -1887,6 +1952,11 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" +arrgv@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arrgv/-/arrgv-1.0.2.tgz#025ed55a6a433cad9b604f8112fc4292715a6ec0" + integrity sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw== + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -1979,6 +2049,67 @@ autoprefixer@^9.4.5, autoprefixer@^9.5.1: postcss "^7.0.32" postcss-value-parser "^4.1.0" +ava@^3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/ava/-/ava-3.11.1.tgz#580bfc974b858fb13f7ce948b9651da4e5b17bc8" + integrity sha512-yGPD0msa5Qronw7GHDNlLaB7oU5zryYtXeuvny40YV6TMskSghqK7Ky3NisM/sr+aqI3DY7sfmORx8dIWQgMoQ== + dependencies: + "@concordance/react" "^2.0.0" + acorn "^7.3.1" + acorn-walk "^7.2.0" + ansi-styles "^4.2.1" + arrgv "^1.0.2" + arrify "^2.0.1" + callsites "^3.1.0" + chalk "^4.1.0" + chokidar "^3.4.1" + chunkd "^2.0.1" + ci-info "^2.0.0" + ci-parallel-vars "^1.0.1" + clean-yaml-object "^0.1.0" + cli-cursor "^3.1.0" + cli-truncate "^2.1.0" + code-excerpt "^3.0.0" + common-path-prefix "^3.0.0" + concordance "^5.0.0" + convert-source-map "^1.7.0" + currently-unhandled "^0.4.1" + debug "^4.1.1" + del "^5.1.0" + emittery "^0.7.1" + equal-length "^1.0.0" + figures "^3.2.0" + globby "^11.0.1" + ignore-by-default "^2.0.0" + import-local "^3.0.2" + indent-string "^4.0.0" + is-error "^2.2.2" + is-plain-object "^4.1.1" + is-promise "^4.0.0" + lodash "^4.17.19" + matcher "^3.0.0" + md5-hex "^3.0.1" + mem "^6.1.0" + ms "^2.1.2" + ora "^4.0.5" + p-map "^4.0.0" + picomatch "^2.2.2" + pkg-conf "^3.1.0" + plur "^4.0.0" + pretty-ms "^7.0.0" + read-pkg "^5.2.0" + resolve-cwd "^3.0.0" + slash "^3.0.0" + source-map-support "^0.5.19" + stack-utils "^2.0.2" + strip-ansi "^6.0.0" + supertap "^1.0.0" + temp-dir "^2.0.0" + trim-off-newlines "^1.0.1" + update-notifier "^4.1.0" + write-file-atomic "^3.0.3" + yargs "^15.4.1" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2084,6 +2215,11 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +blueimp-md5@^2.10.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.17.0.tgz#f4fcac088b115f7b4045f19f5da59e9d01b1bb96" + integrity sha512-x5PKJHY5rHQYaADj6NwPUR2QRCUVSggPzrUKkeENpj871o9l9IefJbO2jkT5UvYykeOK9dx0VmkIo6dZ+vThYw== + body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -2105,6 +2241,20 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2129,7 +2279,7 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -2237,6 +2387,19 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + caching-transform@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-3.0.2.tgz#601d46b91eca87687a281e71cef99791b0efca70" @@ -2271,7 +2434,7 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= -callsites@^3.0.0: +callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== @@ -2308,7 +2471,7 @@ camelcase@^4.0.0, camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -camelcase@^5.0.0: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -2383,7 +2546,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -"chalk@^3.0.0 || ^4.0.0", chalk@^4.0.0: +"chalk@^3.0.0 || ^4.0.0", chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -2411,16 +2574,41 @@ chokidar@^3.3.0: optionalDependencies: fsevents "~2.1.2" +chokidar@^3.4.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" + integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.1.1, chownr@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== +chunkd@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/chunkd/-/chunkd-2.0.1.tgz#49cd1d7b06992dc4f7fccd962fe2a101ee7da920" + integrity sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-parallel-vars@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz#e87ff0625ccf9d286985b29b4ada8485ca9ffbc2" + integrity sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg== + cipher-base@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -2439,6 +2627,21 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +clean-yaml-object@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz#63fb110dc2ce1a84dc21f6d9334876d010ae8b68" + integrity sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g= + +cli-boxes@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" + integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -2453,6 +2656,11 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-spinners@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" + integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== + cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" @@ -2484,6 +2692,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + clone-deep@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" @@ -2504,6 +2721,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -2518,6 +2742,13 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" +code-excerpt@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/code-excerpt/-/code-excerpt-3.0.0.tgz#fcfb6748c03dba8431c19f5474747fad3f250f10" + integrity sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw== + dependencies: + convert-to-spaces "^1.0.1" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -2606,6 +2837,11 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0" integrity sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ== +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -2656,6 +2892,20 @@ concat-with-sourcemaps@^1.1.0: dependencies: source-map "^0.6.1" +concordance@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/concordance/-/concordance-5.0.0.tgz#6d4552f76c78301dd65e748c26af2cf131f9dd49" + integrity sha512-stOCz9ffg0+rytwTaL2njUOIyMfANwfwmqc9Dr4vTUS/x/KkVFlWx9Zlzu6tHYtjKxxaCF/cEAZgPDac+n35sg== + dependencies: + date-time "^3.1.0" + esutils "^2.0.3" + fast-diff "^1.2.0" + js-string-escape "^1.0.1" + lodash "^4.17.15" + md5-hex "^3.0.1" + semver "^7.3.2" + well-known-symbols "^2.0.0" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -2664,6 +2914,18 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + confusing-browser-globals@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" @@ -2786,6 +3048,11 @@ convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +convert-to-spaces@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz#7e3e48bbe6d997b1417ddca2868204b4d3d85715" + integrity sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU= + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2880,6 +3147,11 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -3199,6 +3471,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-time@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/date-time/-/date-time-3.1.0.tgz#0d1e934d170579f481ed8df1e2b8ff70ee845e1e" + integrity sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg== + dependencies: + time-zone "^1.0.0" + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -3255,6 +3534,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + decompress-response@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" @@ -3318,6 +3604,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -3352,6 +3643,20 @@ defined@^1.0.0, defined@~1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= +del@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" + integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== + dependencies: + globby "^10.0.1" + graceful-fs "^4.2.2" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.1" + p-map "^3.0.0" + rimraf "^3.0.0" + slash "^3.0.0" + delaunator@4: version "4.0.1" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" @@ -3438,6 +3743,13 @@ dir-glob@^2.2.2: dependencies: path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -3507,6 +3819,11 @@ dotignore@~0.1.2: dependencies: minimatch "^3.0.4" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -3545,6 +3862,11 @@ electron-to-chromium@^1.3.488: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.521.tgz#67216aa87e5e17b2fe065f07ec8880b3fec8dae1" integrity sha512-7/Cf5jUuAfLRY8SjfRES/6+9BDvmHAB2YQotCAaXK0IEacpjoSlyosPoC4s7lfb7vIOBubXvsssu8+8qaRGjcg== +emittery@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451" + integrity sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ== + emoji-regex@^7.0.1, emoji-regex@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -3594,6 +3916,11 @@ envinfo@^7.3.1: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== +equal-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/equal-length/-/equal-length-1.0.1.tgz#21ca112d48ab24b4e1e7ffc0e5339d31fdfc274c" + integrity sha1-IcoRLUirJLTh5//A5TOdMf38J0w= + err-code@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" @@ -3687,6 +4014,11 @@ escalade@^3.0.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3702,6 +4034,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + eslint-config-airbnb-base@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz#8a7bcb9643d13c55df4dd7444f138bf4efa61e17" @@ -3968,7 +4305,7 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== -esutils@^2.0.0, esutils@^2.0.2: +esutils@^2.0.0, esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -4123,7 +4460,7 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== -fast-diff@^1.1.2: +fast-diff@^1.1.2, fast-diff@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== @@ -4140,6 +4477,18 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.0.3, fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -4155,6 +4504,13 @@ fastparse@^1.1.2: resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -4182,6 +4538,13 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" @@ -4255,6 +4618,14 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + findit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findit/-/findit-2.0.0.tgz#6509f0126af4c178551cfa99394e032e13a4d56e" @@ -4514,6 +4885,13 @@ get-stream@^4.0.0, get-stream@^4.1.0: dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -4595,6 +4973,13 @@ glob-parent@^5.0.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-parent@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -4612,6 +4997,13 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + dependencies: + ini "^1.3.5" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4624,6 +5016,32 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globby@^10.0.1: + version "10.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" + integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^9.2.0: version "9.2.0" resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" @@ -4638,6 +5056,23 @@ globby@^9.2.0: pify "^4.0.1" slash "^2.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" @@ -4730,6 +5165,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.0, has@^1.0.1, has@^1.0.3, has@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -4797,6 +5237,11 @@ http-cache-semantics@^3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -4868,6 +5313,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-by-default@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-2.0.0.tgz#537092018540640459569fe7c8c7a408af581146" + integrity sha512-+mQSgMRiFD3L3AOxLYOCxjIq4OnAmo5CIuC+lj5ehCJcPtV++QacEV7FdpzvYxH6DaOySWzQU6RR0lPLy37ckA== + ignore-walk@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" @@ -4885,6 +5335,11 @@ ignore@^5.0.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== +ignore@^5.1.1, ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -4941,6 +5396,11 @@ import-jsx@^3.1.0: caller-path "^2.0.0" resolve-from "^3.0.0" +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -4949,6 +5409,14 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -4961,11 +5429,16 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" -indent-string@^3.0.0: +indent-string@^3.0.0, indent-string@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -4994,7 +5467,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: +ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -5090,6 +5563,11 @@ ipaddr.js@1.9.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== +irregular-plurals@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.2.0.tgz#b19c490a0723798db51b235d7e39add44dab0822" + integrity sha512-YqTdPLfwP7YFN0SsD3QUVCkm9ZG2VzOXv3DOrw5G5mkMbVwptTwVcFv7/C0vOpBmgTxAeTG19XpUs1E522LW9Q== + is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" @@ -5212,6 +5690,11 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= +is-error@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-error/-/is-error-2.2.2.tgz#c10ade187b3c93510c5470a5567833ee25649843" + integrity sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -5272,11 +5755,29 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -5299,6 +5800,16 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" + integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== + is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -5318,11 +5829,21 @@ is-plain-object@^3.0.0: dependencies: isobject "^4.0.0" +is-plain-object@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-4.1.1.tgz#1a14d6452cbd50790edc7fdaa0aed5a40a35ebb5" + integrity sha512-5Aw8LLVsDlZsETVMhoMXzqsXwQqr/0vlnBYzIXJbYo2F4yYlhLHs+Ez7Bod7IIQKWkJbJfxrWD7pA1Dw1TKrwA== + is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-reference@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" @@ -5402,6 +5923,11 @@ is-windows@^1.0.0, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5513,11 +6039,24 @@ jest-worker@^26.0.0: merge-stream "^2.0.0" supports-color "^7.0.0" +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^3.10.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -5536,6 +6075,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -5624,6 +6168,13 @@ jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + kind-of@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" @@ -5660,6 +6211,13 @@ kleur@^3.0.0: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -5707,6 +6265,11 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + livereload-js@^3.1.0, "livereload-js@https://github.com/agoric-labs/livereload-js": version "3.3.1" resolved "https://github.com/agoric-labs/livereload-js#2c00632164680c8214a9ef95f763e27952f392a9" @@ -5752,7 +6315,7 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -load-json-file@^5.3.0: +load-json-file@^5.2.0, load-json-file@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.3.0.tgz#4d3c1e01fa1c03ea78a60ac7af932c9ce53403f3" integrity sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw== @@ -5793,6 +6356,13 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -5873,11 +6443,23 @@ lodash@^4.17.10, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + log-driver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg== +log-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + log-update@^3.0.0: version "3.4.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-3.4.0.tgz#3b9a71e00ac5b1185cc193a36d654581c48f97b9" @@ -5902,6 +6484,16 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -5944,6 +6536,13 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -5966,6 +6565,13 @@ make-fetch-happen@^5.0.0: socks-proxy-agent "^4.0.0" ssri "^6.0.0" +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -5988,6 +6594,20 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + +md5-hex@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c" + integrity sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw== + dependencies: + blueimp-md5 "^2.10.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -6012,6 +6632,14 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +mem@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-6.1.0.tgz#846eca0bd4708a8f04b9c3f3cd769e194ae63c5c" + integrity sha512-RlbnLQgRHk5lwqTtpEkBTQ2ll/CG/iB+J4Hy2Wh97PjgZgXgWJWrFF+XXujh3UUVLvR4OOTgZzcWMMwnehlEUg== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^3.0.0" + meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -6089,6 +6717,11 @@ merge2@^1.2.3: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -6113,6 +6746,14 @@ micromatch@^3.1.10: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + mime-db@1.42.0: version "1.42.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" @@ -6157,6 +6798,16 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + mimic-response@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" @@ -6575,6 +7226,11 @@ normalize-url@^3.0.0, normalize-url@^3.3.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + normalize.css@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" @@ -6878,6 +7534,20 @@ optionator@^0.8.3: resolved "https://registry.yarnpkg.com/opts/-/opts-2.0.0.tgz#c0b2cebdf53eaeafc21ed844e34773758dcf41e2" integrity sha512-rPleeyX48sBEc4aj7rAok5dCbvRdYpdbIdSRR4gnIK98a7Rvd4l3wlv4YHQr2mwPQTpKQiw8uipi/WoyItDINg== +ora@^4.0.5: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-4.1.1.tgz#566cc0348a15c36f5f0e979612842e02ba9dddbc" + integrity sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A== + dependencies: + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-spinners "^2.2.0" + is-interactive "^1.0.0" + log-symbols "^3.0.0" + mute-stream "0.0.8" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -6916,6 +7586,16 @@ own-or@^1.0.0: resolved "https://registry.yarnpkg.com/own-or/-/own-or-1.0.0.tgz#4e877fbeda9a2ec8000fbc0bcae39645ee8bf8dc" integrity sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw= +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -6935,6 +7615,13 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -6949,6 +7636,13 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-map-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" @@ -6961,6 +7655,20 @@ p-map@^2.1.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-pipe@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" @@ -7020,6 +7728,16 @@ package-hash@^3.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + parallel-transform@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" @@ -7056,11 +7774,26 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-json@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.1.tgz#7cfe35c1ccd641bce3981467e6c2ece61b3b3878" + integrity sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + lines-and-columns "^1.1.6" + parse-ms@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" integrity sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0= +parse-ms@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== + parse-path@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff" @@ -7106,6 +7839,11 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -7149,6 +7887,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -7159,7 +7902,7 @@ picomatch@^2.0.4, picomatch@^2.0.7: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== -picomatch@^2.2.2: +picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -7203,6 +7946,14 @@ pirates@^3.0.2: dependencies: node-modules-regexp "^1.0.0" +pkg-conf@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-3.1.0.tgz#d9f9c75ea1bae0e77938cde045b276dac7cc69ae" + integrity sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ== + dependencies: + find-up "^3.0.0" + load-json-file "^5.2.0" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -7217,6 +7968,13 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" @@ -7229,6 +7987,13 @@ plur@^1.0.0: resolved "https://registry.yarnpkg.com/plur/-/plur-1.0.0.tgz#db85c6814f5e5e5a3b49efc28d604fec62975156" integrity sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY= +plur@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84" + integrity sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg== + dependencies: + irregular-plurals "^3.2.0" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -7685,6 +8450,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -7711,6 +8481,13 @@ pretty-ms@^2.1.0: parse-ms "^1.0.0" plur "^1.0.0" +pretty-ms@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.0.tgz#45781273110caf35f55cab21a8a9bd403a233dc0" + integrity sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg== + dependencies: + parse-ms "^2.1.0" + process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -7830,6 +8607,13 @@ punycode@^2.0.0, punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pupa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" + integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== + dependencies: + escape-goat "^2.0.0" + purgecss@^1.4.0: version "1.4.2" resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.4.2.tgz#67ab50cb4f5c163fcefde56002467c974e577f41" @@ -7892,7 +8676,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7: +rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -8032,6 +8816,16 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + read@1, read@~1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -8113,6 +8907,13 @@ readdirp@~3.3.0: dependencies: picomatch "^2.0.7" +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== + dependencies: + picomatch "^2.2.1" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -8170,6 +8971,20 @@ regexpp@^2.0.1: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== +registry-auth-token@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" + integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== + dependencies: + rc "^1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" @@ -8247,6 +9062,13 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -8309,6 +9131,13 @@ resolve@~1.11.1: dependencies: path-parse "^1.0.6" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8342,6 +9171,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -8366,6 +9200,13 @@ rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -8479,6 +9320,11 @@ run-async@^2.2.0: dependencies: is-promise "^2.1.0" +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -8550,6 +9396,13 @@ semiver@^1.0.0: resolved "https://registry.yarnpkg.com/semiver/-/semiver-1.1.0.tgz#9c97fb02c21c7ce4fcf1b73e2c7a24324bdddd5f" integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg== +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -8560,6 +9413,11 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -8579,6 +9437,11 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" + integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= + serialize-javascript@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" @@ -8716,6 +9579,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -8838,7 +9706,7 @@ source-map-support@^0.5.11, source-map-support@^0.5.16, source-map-support@^0.5. buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@~0.5.12: +source-map-support@^0.5.19, source-map-support@~0.5.12: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -8979,6 +9847,13 @@ stack-utils@^1.0.2: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stack-utils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593" + integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg== + dependencies: + escape-string-regexp "^2.0.0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -9226,6 +10101,17 @@ supercop.js@^2.0.1: resolved "https://registry.yarnpkg.com/supercop.js/-/supercop.js-2.0.1.tgz#1fcfe9fc5ff6e42aef4e3683636c8cb891594b18" integrity sha1-H8/p/F/25CrvTjaDY2yMuJFZSxg= +supertap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supertap/-/supertap-1.0.0.tgz#bd9751c7fafd68c68cf8222a29892206a119fa9e" + integrity sha512-HZJ3geIMPgVwKk2VsmO5YHqnnJYl6bV5A9JW2uzqV43WmpgliNEYbuvukfor7URpaqpxuw3CfZ3ONdVbZjCgIA== + dependencies: + arrify "^1.0.1" + indent-string "^3.2.0" + js-yaml "^3.10.0" + serialize-error "^2.1.0" + strip-ansi "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -9554,6 +10440,11 @@ temp-dir@^1.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + temp-write@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492" @@ -9573,6 +10464,11 @@ temp@^0.9.0, temp@^0.9.1: dependencies: rimraf "~2.6.2" +term-size@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" + integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== + terser@^4.7.0: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" @@ -9636,6 +10532,11 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4, resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +time-zone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d" + integrity sha1-mcW/VZWJZq9tBtg73zgA3IL67F0= + timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" @@ -9677,6 +10578,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -9758,7 +10664,7 @@ trim-newlines@^2.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= -trim-off-newlines@^1.0.0: +trim-off-newlines@^1.0.0, trim-off-newlines@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= @@ -9823,6 +10729,11 @@ type-fest@^0.3.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -9918,6 +10829,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + universal-user-agent@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557" @@ -9960,6 +10878,25 @@ upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +update-notifier@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" + integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== + dependencies: + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -9972,6 +10909,13 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -10350,7 +11294,7 @@ vlq@^0.2.1: resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== -wcwidth@^1.0.0: +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= @@ -10374,6 +11318,11 @@ websocket-stream@^5.1.1: ws "^3.2.0" xtend "^4.0.0" +well-known-symbols@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-2.0.0.tgz#e9c7c07dbd132b7b84212c8174391ec1f9871ba5" + integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q== + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -10478,7 +11427,7 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.1: +write-file-atomic@^3.0.0, write-file-atomic@^3.0.1, write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== @@ -10550,6 +11499,11 @@ ws@^7.2.0: dependencies: async-limiter "^1.0.0" +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -10625,6 +11579,14 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs@^13.2.2: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" @@ -10675,6 +11637,23 @@ yargs@^14.2.0, yargs@^14.2.2: y18n "^4.0.0" yargs-parser "^15.0.0" +yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 68e92f6047d7067463fd806014fbe9d0cd5e8727 Mon Sep 17 00:00:00 2001 From: Dean Tribble Date: Tue, 11 Aug 2020 22:11:48 -0700 Subject: [PATCH 31/31] test: get more examples working --- package.json | 2 +- packages/SwingSet/test/test-queue-priority.js | 1 - packages/marshal/test/test-marshal.js | 88 ++++++++++--------- .../notifier/test/test-notifier-adaptor.js | 4 - 4 files changed, 46 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 358339f0ed2..bb29407413b 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ ], "concurrency": 12, "failFast": false, - "failWithoutAssertions": false, + "failWithoutAssertions": true, "environmentVariables": { "MY_ENVIRONMENT_VARIABLE": "some value" }, diff --git a/packages/SwingSet/test/test-queue-priority.js b/packages/SwingSet/test/test-queue-priority.js index c764105eb04..33f7c9c9b38 100644 --- a/packages/SwingSet/test/test-queue-priority.js +++ b/packages/SwingSet/test/test-queue-priority.js @@ -19,5 +19,4 @@ test('Promise queue should be higher priority than IO/timer queue', async t => { await p; t.deepEqual(log, [1, 2, 3, 4, 5, 6]); - returnreturn; // t.end(); }); diff --git a/packages/marshal/test/test-marshal.js b/packages/marshal/test/test-marshal.js index b826fab55dc..67b667ec96f 100644 --- a/packages/marshal/test/test-marshal.js +++ b/packages/marshal/test/test-marshal.js @@ -14,7 +14,8 @@ import { test('serialize static data', t => { const m = makeMarshal(); const ser = val => m.serialize(val); - t.throws(() => ser([1, 2]), /Cannot pass non-frozen objects like/); + t.throws(() => ser([1, 2]), + { message: /Cannot pass non-frozen objects like/ }); t.deepEqual(ser(harden([1, 2])), { body: '[1,2]', slots: [] }); t.deepEqual(ser(harden({ foo: 1 })), { body: '{"foo":1}', slots: [] }); t.deepEqual(ser(true), { body: 'true', slots: [] }); @@ -38,11 +39,12 @@ test('serialize static data', t => { slots: [], }); // registered symbols - t.throws(() => ser(Symbol.for('sym1')), /Cannot pass symbols/); + const thrown = { message: /Cannot pass symbols/ }; + t.throws(() => ser(Symbol.for('sym1')), thrown); // unregistered symbols - t.throws(() => ser(Symbol('sym2')), /Cannot pass symbols/); + t.throws(() => ser(Symbol('sym2')), thrown); // well known symbols - t.throws(() => ser(Symbol.iterator), /Cannot pass symbols/); + t.throws(() => ser(Symbol.iterator), thrown); let bn; try { bn = BigInt(4); @@ -81,22 +83,20 @@ test('serialize static data', t => { }); const cd = ser(harden([1, 2])); - t.is(Object.isFrozen(cd), true); - t.is(Object.isFrozen(cd.slots), true); - - return; // t.end(); + t.is(Object.isFrozen(cd), true); + t.is(Object.isFrozen(cd.slots), true); }); test('unserialize static data', t => { const m = makeMarshal(); const uns = body => m.unserialize({ body, slots: [] }); - t.is(uns('1'), 1); - t.is(uns('"abc"'), 'abc'); - t.is(uns('false'), false); + t.is(uns('1'), 1); + t.is(uns('"abc"'), 'abc'); + t.is(uns('false'), false); // JS primitives that aren't natively representable by JSON t.deepEqual(uns('{"@qclass":"undefined"}'), undefined); - t.assert(Object.is(uns('{"@qclass":"NaN"}'), NaN)); + t.assert(Object.is(uns('{"@qclass":"NaN"}'), NaN)); t.deepEqual(uns('{"@qclass":"Infinity"}'), Infinity); t.deepEqual(uns('{"@qclass":"-Infinity"}'), -Infinity); @@ -118,17 +118,17 @@ test('unserialize static data', t => { const em1 = uns( '{"@qclass":"error","name":"ReferenceError","message":"msg"}', ); - t.assert(em1 instanceof ReferenceError); - t.is(em1.message, 'msg'); - t.assert(Object.isFrozen(em1)); + t.assert(em1 instanceof ReferenceError); + t.is(em1.message, 'msg'); + t.assert(Object.isFrozen(em1)); const em2 = uns('{"@qclass":"error","name":"TypeError","message":"msg2"}'); - t.assert(em2 instanceof TypeError); - t.is(em2.message, 'msg2'); + t.assert(em2 instanceof TypeError); + t.is(em2.message, 'msg2'); const em3 = uns('{"@qclass":"error","name":"Unknown","message":"msg3"}'); - t.assert(em3 instanceof Error); - t.is(em3.message, 'msg3'); + t.assert(em3 instanceof Error); + t.is(em3.message, 'msg3'); t.deepEqual(uns('[1,2]'), [1, 2]); t.deepEqual(uns('{"a":1,"b":2}'), { a: 1, b: 2 }); @@ -136,14 +136,14 @@ test('unserialize static data', t => { // should be frozen const arr = uns('[1,2]'); - t.assert(Object.isFrozen(arr)); + t.assert(Object.isFrozen(arr)); const a = uns('{"b":{"c":{"d": []}}}'); - t.assert(Object.isFrozen(a)); - t.assert(Object.isFrozen(a.b)); - t.assert(Object.isFrozen(a.b.c)); - t.assert(Object.isFrozen(a.b.c.d)); + t.assert(Object.isFrozen(a)); + t.assert(Object.isFrozen(a.b)); + t.assert(Object.isFrozen(a.b.c)); + t.assert(Object.isFrozen(a.b.c.d)); - return; // t.end(); + return; // t.end(); }); test('serialize ibid cycle', t => { @@ -157,7 +157,7 @@ test('serialize ibid cycle', t => { body: '["a",{"@qclass":"ibid","index":0},"c"]', slots: [], }); - return; // t.end(); + return; // t.end(); }); test('forbid ibid cycle', t => { @@ -167,27 +167,29 @@ test('forbid ibid cycle', t => { () => uns('["a",{"@qclass":"ibid","index":0},"c"]'), { message: /Ibid cycle at 0/ }, ); - return; // t.end(); + return; // t.end(); }); test('unserialize ibid cycle', t => { const m = makeMarshal(); const uns = body => m.unserialize({ body, slots: [] }, 'warnOfCycles'); const cycle = uns('["a",{"@qclass":"ibid","index":0},"c"]'); - t.assert(Object.is(cycle[1], cycle)); - return; // t.end(); + t.assert(Object.is(cycle[1], cycle)); + return; // t.end(); }); test('null cannot be pass-by-presence', t => { - t.throws(() => mustPassByPresence(null), /null cannot be pass-by-remote/); - return; // t.end(); + t.throws(() => mustPassByPresence(null), + { message: /null cannot be pass-by-remote/ }); + return; // t.end(); }); test('mal-formed @qclass', t => { const m = makeMarshal(); const uns = body => m.unserialize({ body, slots: [] }); - t.throws(() => uns('{"@qclass": 0}'), /invalid qclass/); - return; // t.end(); + t.throws(() => uns('{"@qclass": 0}'), + { message: /invalid qclass/ }); + return; // t.end(); }); test('Remotable/getInterfaceOf', t => { @@ -207,21 +209,21 @@ test('Remotable/getInterfaceOf', t => { 'function presences are not implemented', ); - t.is(getInterfaceOf('foo'), undefined, 'string, no interface'); - t.is(getInterfaceOf(null), undefined, 'null, no interface'); - t.is( + t.is(getInterfaceOf('foo'), undefined, 'string, no interface'); + t.is(getInterfaceOf(null), undefined, 'null, no interface'); + t.is( getInterfaceOf(a => a + 1), undefined, 'function, no interface', ); - t.is(getInterfaceOf(123), undefined, 'number, no interface'); + t.is(getInterfaceOf(123), undefined, 'number, no interface'); // Check that a handle can be created. const p = Remotable('MyHandle'); harden(p); // console.log(p); - t.is(getInterfaceOf(p), 'MyHandle', `interface is MyHandle`); - t.is(`${p}`, '[MyHandle]', 'stringify is [MyHandle]'); + t.is(getInterfaceOf(p), 'MyHandle', `interface is MyHandle`); + t.is(`${p}`, '[MyHandle]', 'stringify is [MyHandle]'); const p2 = Remotable('Thing', { name() { @@ -231,8 +233,8 @@ test('Remotable/getInterfaceOf', t => { return now - 64; }, }); - t.is(getInterfaceOf(p2), 'Thing', `interface is Thing`); - t.is(p2.name(), 'cretin', `name() method is presence`); - t.is(p2.birthYear(2020), 1956, `birthYear() works`); - return; // t.end(); + t.is(getInterfaceOf(p2), 'Thing', `interface is Thing`); + t.is(p2.name(), 'cretin', `name() method is presence`); + t.is(p2.birthYear(2020), 1956, `birthYear() works`); + return; // t.end(); }); diff --git a/packages/notifier/test/test-notifier-adaptor.js b/packages/notifier/test/test-notifier-adaptor.js index ff9953e5c94..7876f524b33 100644 --- a/packages/notifier/test/test-notifier-adaptor.js +++ b/packages/notifier/test/test-notifier-adaptor.js @@ -46,12 +46,10 @@ const testEnding = (t, p, fails) => { result => { t.is(fails, false); t.is(result, refResult); - returnreturn; // t.end(); }, reason => { t.is(fails, true); t.is(reason, refReason); - returnreturn; // t.end(); }, ); }; @@ -122,12 +120,10 @@ const makeTestUpdater = (t, lossy, fails) => { finish(finalState) { t.is(fails, false); t.is(finalState, refResult); - returnreturn; // t.end(); }, fail(reason) { t.is(fails, true); t.is(reason, refReason); - returnreturn; // t.end(); }, }); };