diff --git a/packages/zoe/NEWS.md b/packages/zoe/NEWS.md index 0500a233956..96ad54a1966 100644 --- a/packages/zoe/NEWS.md +++ b/packages/zoe/NEWS.md @@ -1,4 +1,96 @@ User-visible changes in @agoric/zoe: + +## Release 0.3.0 (25-Mar-2020) + +"OfferRules", or the declarative statement portion of an offer, have +been renamed to "proposal". The structure of a proposal has changed to: + +```js +{ + give: { Asset: moola(4 )}, + want: { Price: simoleans(15) }, + exit: { afterDeadline: { + timer, + deadline: 100, + }} +} +``` + +(* `moola` is an alias for `moolaAmountMath.make`, and likewise for +`simoleans`.) + +There are no longer any keys such as 'payoutRules' or 'exitRule'. Most +importantly, we no longer rely on the specific order of payoutRules +arrays and payment/payout arrays to be the same. In fact, we can even +make a partial proposal that will be filled in. + +`Asset` and `Price` in the above example are called "keywords". Each +contract has its own specific keywords. For instance, an auction +might have "Asset" and "Bid". Keywords are unique identifiers per +contract, that tie together the proposal, payments to be escrowed, and +payouts to the user. + +Users should submit their payments using keywords: + +``` const payments = { Asset: moolaPayment }; ``` + +And, users will receive their payouts with keywords as the keys of a +payout object: + +``` moolaPurse.deposit(payout.Asset); ``` + +In summary, the arrays that were part of the previous API have been +replaced with records keyed by keywords. + +Below is a summary of the changes: + +Changes to the Zoe Service (User-facing) API: +* `makeInstance` now takes in three arguments: `installationHandle`, + `issuerKeywordRecord`, and `terms`. `issuerKeywordRecord` should have strings + as keys (called `keyword`s), and issuers as values (called `args`). + For instance, `{Asset: moolaIssuer, Price: simoleanIssuer }`. `Terms` + no longer needs an `issuers` property. Instead, `terms` should be used for + any contract-specific parameters, such as the number of bids an + auction should wait for before closing. +* OfferRules (now proposal) have the new structure mentioned above +* Payments to be escrowed must be an object keyed by `keywords` rather + than an array +* Payouts from Zoe will be in the form of a promise that resolves to a + object keyed by keywords where the values are promises for + payments +* We have added three new methods to the Zoe Service API: `getOffer`, + `getOffers`, and `isOfferActive`. Note that `getOffer` and + `getOffers` will throw if the offer is not found, whereas + `isOfferActive` is safe to check whether an offer is still active or + not. + +Changes to the Contract Facet API and contract helpers: +* The `userFlow` helpers have been renamed to `zoeHelpers` +* `hasValidPayoutRules` is no longer a helper function due to the + change in the proposal structure +* Instead, we have three new helpers: `assertKeywords` which can be + used to make sure that the keywords on instantiation match what + the contract expects, `rejectIfNotProposal` which rejects an + offer if the proposal have the wrong structure, and + `checkIfProposal` which checks if the proposal match the + expected structure and returns true or false. +* `getAmountMathForIssuers` and `getBrandsForIssuers` no longer exist +* `getAmountMaths` is added. It takes in a `issuerKeywordRecord` + record that maps keywords to issuers. +* `getInstanceRecord` is added. It allows the contracts to get access + to their keywords, issuers and other "instanceRecord" information from + Zoe. +* The instanceRecord now has properties: `issuerKeywordRecord` (a record with + `keyword` keys and issuer values) and `keywords` (an array of + strings). The keywords in `issuerKeywordRecord` and the `keywords` array + are the same. +* In the offerRecord, amounts are no longer an array. Instead they are + an `amountKeywordRecord`, an object with `keywords` as keys and `amount` + values. +* Reallocate now takes an array of offerHandles/inviteHandles and an + array of the above `amountKeywordRecord`s keyed on keywords. +* AddNewIssuer now requires a keyword to be provided alongside the issuer. + ## Release 0.2.1 (3-Feb-2020) Updates ERTP dependency to v0.3.0 and adds dependencies on new diff --git a/packages/zoe/src/cleanProposal.js b/packages/zoe/src/cleanProposal.js new file mode 100644 index 00000000000..86371481654 --- /dev/null +++ b/packages/zoe/src/cleanProposal.js @@ -0,0 +1,129 @@ +import harden from '@agoric/harden'; +import { assert, details } from '@agoric/assert'; +import { mustBeComparable } from '@agoric/same-structure'; + +import { arrayToObj } from './objArrayConversion'; + +// cleanProposal checks the keys and values of the proposal, including +// the keys and values of the internal objects. The proposal may have +// the following keys: `give`, `want`, and `exit`. These keys may be +// omitted in the `proposal` argument passed to cleanProposal, but +// anything other than these keys is not allowed. The values of `give` +// and `want` must be "amountKeywordRecords", meaning that the keys +// must be keywords and the values must be amounts. The value of +// `exit`, if present, must be a record of one of the following forms: +// `{ waived: null }` `{ onDemand: null }` `{ afterDeadline: { timer +// :Timer, deadline :Number } } + +// Assert that the keys of record, if present, are in expectedKeys. +// Return the keys after asserting this. +const checkKeys = (expectedKeys, record) => { + // Assert that keys, if present, match expectedKeys. + const keys = Object.getOwnPropertyNames(record); + keys.forEach(key => { + assert.typeof(key, 'string'); + assert( + expectedKeys.includes(key), + details`key ${key} was not an expected key`, + ); + }); + // assert that there are no symbol properties. + assert( + Object.getOwnPropertySymbols(record).length === 0, + details`no symbol properties allowed`, + ); + return keys; +}; + +const coerceAmountKeywordRecordValues = ( + amountMathKeywordRecord, + validatedKeywords, + allegedAmountKeywordRecord, +) => { + // Check that each value can be coerced using the amountMath indexed + // by keyword. `AmountMath.coerce` throws if coercion fails. + const coercedAmounts = validatedKeywords.map(keyword => + amountMathKeywordRecord[keyword].coerce( + allegedAmountKeywordRecord[keyword], + ), + ); + + // Recreate the amountKeywordRecord with coercedAmounts. + return arrayToObj(coercedAmounts, validatedKeywords); +}; + +export const coerceAmountKeywordRecord = ( + amountMathKeywordRecord, + keywords, + allegedAmountKeywordRecord, +) => { + const validatedKeywords = checkKeys(keywords, allegedAmountKeywordRecord); + return coerceAmountKeywordRecordValues( + amountMathKeywordRecord, + validatedKeywords, + allegedAmountKeywordRecord, + ); +}; + +export const cleanProposal = (keywords, amountMathKeywordRecord, proposal) => { + const expectedRootKeys = ['want', 'give', 'exit']; + mustBeComparable(proposal); + checkKeys(expectedRootKeys, proposal); + + // We fill in the default values if the keys are undefined. + let { want = harden({}), give = harden({}) } = proposal; + const { exit = harden({ onDemand: null }) } = proposal; + + want = coerceAmountKeywordRecord(amountMathKeywordRecord, keywords, want); + give = coerceAmountKeywordRecord(amountMathKeywordRecord, keywords, give); + + // Check exit + assert( + Object.getOwnPropertyNames(exit).length === 1, + details`exit ${proposal.exit} should only have one key`, + ); + // We expect the single exit key to be one of the following: + const expectedExitKeys = ['onDemand', 'afterDeadline', 'waived']; + const [exitKey] = checkKeys(expectedExitKeys, exit); + if (exitKey === 'onDemand' || exitKey === 'waived') { + assert( + exit[exitKey] === null, + `exit value must be null for key ${exitKey}`, + ); + } + if (exitKey === 'afterDeadline') { + const expectedAfterDeadlineKeys = ['timer', 'deadline']; + checkKeys(expectedAfterDeadlineKeys, exit.afterDeadline); + // timers must have a 'setWakeup' function which takes a deadline + // and an object as arguments. + // TODO: document timer interface + // /~https://github.com/Agoric/agoric-sdk/issues/751 + // TODO: how to check methods on presences? + } + + const hasPropDefined = (obj, prop) => obj[prop] !== undefined; + + // Create an unfrozen version of 'want' in case we need to add + // properties. + const wantObj = { ...want }; + + keywords.forEach(keyword => { + // check that keyword is not in both 'want' and 'give'. + const wantHas = hasPropDefined(wantObj, keyword); + const giveHas = hasPropDefined(give, keyword); + assert( + !(wantHas && giveHas), + details`a keyword cannot be in both 'want' and 'give'`, + ); + // If keyword is in neither, fill in with a 'want' of empty. + if (!(wantHas || giveHas)) { + wantObj[keyword] = amountMathKeywordRecord[keyword].getEmpty(); + } + }); + + return harden({ + want: wantObj, + give, + exit, + }); +}; diff --git a/packages/zoe/src/contracts/atomicSwap.js b/packages/zoe/src/contracts/atomicSwap.js index 133a7ec424a..402dc656cdc 100644 --- a/packages/zoe/src/contracts/atomicSwap.js +++ b/packages/zoe/src/contracts/atomicSwap.js @@ -1,18 +1,22 @@ /* eslint-disable no-use-before-define */ import harden from '@agoric/harden'; -import { makeHelpers } from './helpers/userFlow'; +import { makeZoeHelpers } from './helpers/zoeHelpers'; -export const makeContract = harden((zoe, terms) => { - const { issuers } = terms; - const { rejectOffer, swap, hasValidPayoutRules } = makeHelpers(zoe, issuers); +export const makeContract = harden(zoe => { + const { swap, assertKeywords, rejectIfNotProposal } = makeZoeHelpers(zoe); + assertKeywords(harden(['Asset', 'Price'])); const makeMatchingInvite = firstInviteHandle => { const seat = harden({ matchOffer: () => swap(firstInviteHandle, inviteHandle), }); + const { + proposal: { want, give }, + } = zoe.getOffer(firstInviteHandle); const { invite, inviteHandle } = zoe.makeInvite(seat, { - offerMadeRules: zoe.getOffer(firstInviteHandle).payoutRules, + asset: give.Asset, + price: want.Price, seatDesc: 'matchOffer', }); return invite; @@ -21,11 +25,8 @@ export const makeContract = harden((zoe, terms) => { const makeFirstOfferInvite = () => { const seat = harden({ makeFirstOffer: () => { - if ( - !hasValidPayoutRules(['offerAtMost', 'wantAtLeast'], inviteHandle) - ) { - throw rejectOffer(inviteHandle); - } + const expected = harden({ give: ['Asset'], want: ['Price'] }); + rejectIfNotProposal(inviteHandle, expected); return makeMatchingInvite(inviteHandle); }, }); @@ -37,6 +38,5 @@ export const makeContract = harden((zoe, terms) => { return harden({ invite: makeFirstOfferInvite(), - terms, }); }); diff --git a/packages/zoe/src/contracts/automaticRefund.js b/packages/zoe/src/contracts/automaticRefund.js index f9447ca3523..579ea1deb24 100644 --- a/packages/zoe/src/contracts/automaticRefund.js +++ b/packages/zoe/src/contracts/automaticRefund.js @@ -8,7 +8,7 @@ import harden from '@agoric/harden'; * sophisticated logic and interfaces. * @param {contractFacet} zoe - the contract facet of zoe */ -export const makeContract = harden((zoe, terms) => { +export const makeContract = harden(zoe => { let offersCount = 0; const makeSeatInvite = () => { const seat = harden({ @@ -30,6 +30,5 @@ export const makeContract = harden((zoe, terms) => { getOffersCount: () => offersCount, makeInvite: makeSeatInvite, }, - terms, }); }); diff --git a/packages/zoe/src/contracts/autoswap.js b/packages/zoe/src/contracts/autoswap.js index daccde4012c..5ab9550b064 100644 --- a/packages/zoe/src/contracts/autoswap.js +++ b/packages/zoe/src/contracts/autoswap.js @@ -1,35 +1,26 @@ /* eslint-disable no-use-before-define */ import harden from '@agoric/harden'; -import Nat from '@agoric/nat'; import produceIssuer from '@agoric/ertp'; import { assert, details } from '@agoric/assert'; -import { natSafeMath } from './helpers/safeMath'; -import { makeHelpers } from './helpers/userFlow'; +import { makeZoeHelpers } from './helpers/zoeHelpers'; import { makeConstProductBC } from './helpers/bondingCurves'; -export const makeContract = harden((zoe, terms) => { - // The user passes in an array of two issuers for the two kinds of - // assets to be swapped. - const startingIssuers = terms.issuers; +// Autoswap is a rewrite of Uniswap. Please see the documentation for +// more https://agoric.com/documentation/zoe/guide/contracts/autoswap.html - // There is also a third issuer, the issuer for the liquidity token, - // which is created in this contract. We will return all three as - // the canonical array of issuers for this contract +export const makeContract = harden(zoe => { + // Create the liquidity mint and issuer. const { mint: liquidityMint, issuer: liquidityIssuer } = produceIssuer( 'liquidity', ); - const issuers = [...startingIssuers, liquidityIssuer]; - const LIQ_INDEX = 2; - let poolHandle; let liqTokenSupply = 0; - const { subtract } = natSafeMath; - - return zoe.addNewIssuer(liquidityIssuer).then(() => { - const amountMathArray = zoe.getAmountMathForIssuers(issuers); - amountMathArray.forEach(amountMath => + return zoe.addNewIssuer(liquidityIssuer, 'Liquidity').then(() => { + const { issuerKeywordRecord } = zoe.getInstanceRecord(); + const amountMaths = zoe.getAmountMaths(issuerKeywordRecord); + Object.values(amountMaths).forEach(amountMath => assert( amountMath.getMathHelpersName() === 'nat', details`issuers must have natMathHelpers`, @@ -37,120 +28,154 @@ export const makeContract = harden((zoe, terms) => { ); const { rejectOffer, - hasValidPayoutRules, - vectorWith, - vectorWithout, makeEmptyOffer, - } = makeHelpers(zoe, issuers); + rejectIfNotProposal, + checkIfProposal, + } = makeZoeHelpers(zoe); const { getPrice, calcLiqExtentToMint, calcAmountsToRemove, - } = makeConstProductBC(zoe, issuers); - const getPoolAmounts = () => zoe.getOffer(poolHandle).amounts; + } = makeConstProductBC(zoe); - return makeEmptyOffer().then(handle => { - poolHandle = handle; + return makeEmptyOffer().then(poolHandle => { + const getPoolAmounts = () => zoe.getOffer(poolHandle).amounts; const makeInvite = () => { const seat = harden({ swap: () => { - let UNITS_IN_INDEX; - const amountInFirst = ['offerAtMost', 'wantAtLeast', 'wantAtLeast']; - const amountInSecond = [ - 'wantAtLeast', - 'offerAtMost', - 'wantAtLeast', - ]; - if (hasValidPayoutRules(amountInFirst, inviteHandle)) { - UNITS_IN_INDEX = 0; - } else if (hasValidPayoutRules(amountInSecond, inviteHandle)) { - UNITS_IN_INDEX = 1; + const { proposal } = zoe.getOffer(inviteHandle); + const giveTokenA = harden({ + give: ['TokenA'], + want: ['TokenB', 'Liquidity'], + }); + const giveTokenB = harden({ + give: ['TokenB'], + want: ['TokenA', 'Liquidity'], + }); + let giveKeyword; + let wantKeyword; + if (checkIfProposal(inviteHandle, giveTokenA)) { + giveKeyword = 'TokenA'; + wantKeyword = 'TokenB'; + } else if (checkIfProposal(inviteHandle, giveTokenB)) { + giveKeyword = 'TokenB'; + wantKeyword = 'TokenA'; } else { - throw rejectOffer(inviteHandle); + return rejectOffer(inviteHandle); + } + if (!amountMaths.Liquidity.isEmpty(proposal.want.Liquidity)) { + rejectOffer( + inviteHandle, + `A Liquidity amount should not be present in a swap`, + ); } - const UNITS_OUT_INDEX = Nat(1 - UNITS_IN_INDEX); - const { newPoolAmountsArray, amountOut } = getPrice( - getPoolAmounts(), - zoe.getOffer(inviteHandle).amounts[UNITS_IN_INDEX], - ); - const wantedAmounts = zoe.getOffer(inviteHandle).amounts[ - UNITS_OUT_INDEX - ]; + const poolAmounts = getPoolAmounts(); + const { + outputExtent, + newInputReserve, + newOutputReserve, + } = getPrice( + harden({ + inputExtent: proposal.give[giveKeyword].extent, + inputReserve: poolAmounts[giveKeyword].extent, + outputReserve: poolAmounts[wantKeyword].extent, + }), + ); + const amountOut = amountMaths[wantKeyword].make(outputExtent); + const wantedAmount = proposal.want[wantKeyword]; const satisfiesWantedAmounts = () => - amountMathArray[UNITS_OUT_INDEX].isGTE(amountOut, wantedAmounts); + amountMaths[wantKeyword].isGTE(amountOut, wantedAmount); if (!satisfiesWantedAmounts()) { throw rejectOffer(inviteHandle); } - const newUserAmounts = amountMathArray.map(amountMath => - amountMath.getEmpty(), + const newUserAmounts = { + Liquidity: amountMaths.Liquidity.getEmpty(), + }; + newUserAmounts[giveKeyword] = amountMaths[giveKeyword].getEmpty(); + newUserAmounts[wantKeyword] = amountOut; + + const newPoolAmounts = { Liquidity: poolAmounts.Liquidity }; + newPoolAmounts[giveKeyword] = amountMaths[giveKeyword].make( + newInputReserve, + ); + newPoolAmounts[wantKeyword] = amountMaths[wantKeyword].make( + newOutputReserve, ); - newUserAmounts[UNITS_OUT_INDEX] = amountOut; zoe.reallocate( harden([inviteHandle, poolHandle]), - harden([newUserAmounts, newPoolAmountsArray]), + harden([newUserAmounts, newPoolAmounts]), ); zoe.complete(harden([inviteHandle])); return `Swap successfully completed.`; }, addLiquidity: () => { - const kinds = ['offerAtMost', 'offerAtMost', 'wantAtLeast']; - if (!hasValidPayoutRules(kinds, inviteHandle)) { - throw rejectOffer(inviteHandle); - } + const expected = harden({ + give: ['TokenA', 'TokenB'], + want: ['Liquidity'], + }); + rejectIfNotProposal(inviteHandle, expected); const userAmounts = zoe.getOffer(inviteHandle).amounts; const poolAmounts = getPoolAmounts(); // Calculate how many liquidity tokens we should be minting. - // Calculations are based on the extents represented by index 0. + // Calculations are based on the extents represented by TokenA. // If the current supply is zero, start off by just taking the - // extent at index 0 and using it as the extent for the + // extent at TokenA and using it as the extent for the // liquidity token. const liquidityExtentOut = calcLiqExtentToMint( - liqTokenSupply, - poolAmounts, - userAmounts, + harden({ + liqTokenSupply, + inputExtent: userAmounts.TokenA.extent, + inputReserve: poolAmounts.TokenA.extent, + }), ); - const liquidityAmountsOut = amountMathArray[LIQ_INDEX].make( + const liquidityAmountOut = amountMaths.Liquidity.make( liquidityExtentOut, ); const liquidityPaymentP = liquidityMint.mintPayment( - liquidityAmountsOut, + liquidityAmountOut, ); - const offerRules = harden({ - payoutRules: [ - { kind: 'wantAtLeast', amount: amountMathArray[0].getEmpty() }, - { kind: 'wantAtLeast', amount: amountMathArray[1].getEmpty() }, - { kind: 'offerAtMost', amount: liquidityAmountsOut }, - ], - exitRule: { - kind: 'waived', - }, + const proposal = harden({ + give: { Liquidity: liquidityAmountOut }, }); const { inviteHandle: tempLiqHandle, invite } = zoe.makeInvite(); const zoeService = zoe.getZoeService(); return zoeService .redeem( invite, - offerRules, - harden([undefined, undefined, liquidityPaymentP]), + proposal, + harden({ Liquidity: liquidityPaymentP }), ) .then(() => { liqTokenSupply += liquidityExtentOut; - const newPoolAmounts = vectorWith(poolAmounts, userAmounts); - const newUserAmounts = amountMathArray.map(amountMath => - amountMath.getEmpty(), - ); - const newTempLiqAmounts = amountMathArray.map(amountMath => - amountMath.getEmpty(), - ); - newUserAmounts[LIQ_INDEX] = liquidityAmountsOut; + + const add = (key, obj1, obj2) => + amountMaths[key].add(obj1[key], obj2[key]); + + const newPoolAmounts = harden({ + TokenA: add('TokenA', userAmounts, poolAmounts), + TokenB: add('TokenB', userAmounts, poolAmounts), + Liquidity: poolAmounts.Liquidity, + }); + + const newUserAmounts = harden({ + TokenA: amountMaths.TokenA.getEmpty(), + TokenB: amountMaths.TokenB.getEmpty(), + Liquidity: liquidityAmountOut, + }); + + const newTempLiqAmounts = harden({ + TokenA: amountMaths.TokenA.getEmpty(), + TokenB: amountMaths.TokenB.getEmpty(), + Liquidity: amountMaths.Liquidity.getEmpty(), + }); zoe.reallocate( harden([inviteHandle, poolHandle, tempLiqHandle]), @@ -161,33 +186,41 @@ export const makeContract = harden((zoe, terms) => { }); }, removeLiquidity: () => { - const kinds = ['wantAtLeast', 'wantAtLeast', 'offerAtMost']; - if (!hasValidPayoutRules(kinds, inviteHandle)) { - throw rejectOffer(`The offer to remove liquidity was invalid`); - } + const expected = harden({ + want: ['TokenA', 'TokenB'], + give: ['Liquidity'], + }); + rejectIfNotProposal(inviteHandle, expected); + const userAmounts = zoe.getOffer(inviteHandle).amounts; - const liquidityAmountsIn = userAmounts[LIQ_INDEX]; + const liquidityExtentIn = userAmounts.Liquidity.extent; const poolAmounts = getPoolAmounts(); const newUserAmounts = calcAmountsToRemove( - liqTokenSupply, - poolAmounts, - liquidityAmountsIn, + harden({ + liqTokenSupply, + poolAmounts, + liquidityExtentIn, + }), ); - const newPoolAmounts = vectorWith( - vectorWithout(poolAmounts, newUserAmounts), - [ - amountMathArray[0].getEmpty(), - amountMathArray[1].getEmpty(), - liquidityAmountsIn, - ], - ); - liqTokenSupply = subtract( - liqTokenSupply, - liquidityAmountsIn.extent, - ); + const newPoolAmounts = harden({ + TokenA: amountMaths.TokenA.subtract( + poolAmounts.TokenA, + newUserAmounts.TokenA, + ), + TokenB: amountMaths.TokenB.subtract( + poolAmounts.TokenB, + newUserAmounts.TokenB, + ), + Liquidity: amountMaths.Liquidity.add( + poolAmounts.Liquidity, + amountMaths.Liquidity.make(liquidityExtentIn), + ), + }); + + liqTokenSupply -= liquidityExtentIn; zoe.reallocate( harden([inviteHandle, poolHandle]), @@ -209,14 +242,40 @@ export const makeContract = harden((zoe, terms) => { /** * `getPrice` 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 {object} amountInObj - the amount of digital + * assets to be sent in, keyed by keyword */ - getPrice: amountIn => getPrice(getPoolAmounts(), amountIn).amountOut, + getPrice: amountInObj => { + const inKeywords = Object.getOwnPropertyNames(amountInObj); + assert( + inKeywords.length === 1, + details`argument to 'getPrice' must have one keyword`, + ); + const [inKeyword] = inKeywords; + assert( + ['TokenA', 'TokenB'].includes(inKeyword), + details`keyword ${inKeyword} was not valid`, + ); + const inputExtent = amountMaths[inKeyword].getExtent( + amountInObj[inKeyword], + ); + const poolAmounts = getPoolAmounts(); + const inputReserve = poolAmounts[inKeyword].extent; + const outKeyword = inKeyword === 'TokenA' ? 'TokenB' : 'TokenA'; + const outputReserve = poolAmounts[outKeyword].extent; + const { outputExtent } = getPrice( + harden({ + inputExtent, + inputReserve, + outputReserve, + }), + ); + return amountMaths[outKeyword].make(outputExtent); + }, getLiquidityIssuer: () => liquidityIssuer, getPoolAmounts, makeInvite, }, - terms: { issuers }, }); }); }); diff --git a/packages/zoe/src/contracts/coveredCall.js b/packages/zoe/src/contracts/coveredCall.js index 07b8e3e16ab..fba986d0db1 100644 --- a/packages/zoe/src/contracts/coveredCall.js +++ b/packages/zoe/src/contracts/coveredCall.js @@ -7,37 +7,44 @@ import harden from '@agoric/harden'; // date, at which point the contract is cancelled. // In this contract, the expiry date is represented by the deadline at -// which the owner of the digital asset's offer is cancelled. -// Therefore, the owner of the digital asset's offer exitRules must be -// of the kind "afterDeadline". +// which 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 that the creator of the covered call receives is the // call option and has the following additional information in the // extent of the invite: // { expirationDate, timerAuthority, underlyingAsset, strikePrice } -import { makeHelpers } from './helpers/userFlow'; +import { makeZoeHelpers } from './helpers/zoeHelpers'; -export const makeContract = harden((zoe, terms) => { - const { rejectOffer, hasValidPayoutRules, swap } = makeHelpers( - zoe, - terms.issuers, - ); - const ASSET_INDEX = 0; - const PRICE_INDEX = 1; +export const makeContract = harden(zoe => { + const { swap, assertKeywords, rejectIfNotProposal } = makeZoeHelpers(zoe); + assertKeywords(harden(['UnderlyingAsset', 'StrikePrice'])); const makeCallOptionInvite = sellerHandle => { const seat = harden({ - exercise: () => - swap(sellerHandle, inviteHandle, `The covered call option is expired.`), + exercise: () => { + const expected = harden({ + give: ['StrikePrice'], + want: ['UnderlyingAsset'], + }); + rejectIfNotProposal(inviteHandle, expected); + const rejectMsg = `The covered call option is expired.`; + return swap(sellerHandle, inviteHandle, rejectMsg); + }, }); - const { payoutRules, exitRule } = zoe.getOffer(sellerHandle); + + const { + proposal: { want, give, exit }, + } = zoe.getOffer(sellerHandle); + const { invite: callOption, inviteHandle } = zoe.makeInvite(seat, { seatDesc: 'exerciseOption', - expirationDate: exitRule.deadline, - timerAuthority: exitRule.timer, - underlyingAsset: payoutRules[ASSET_INDEX].amount, - strikePrice: payoutRules[PRICE_INDEX].amount, + expirationDate: exit.afterDeadline.deadline, + timerAuthority: exit.afterDeadline.timer, + underlyingAsset: give.UnderlyingAsset, + strikePrice: want.StrikePrice, }); return callOption; }; @@ -45,13 +52,12 @@ export const makeContract = harden((zoe, terms) => { const makeCoveredCallInvite = () => { const seat = harden({ makeCallOption: () => { - const { exitRule } = zoe.getOffer(inviteHandle); - if ( - !hasValidPayoutRules(['offerAtMost', 'wantAtLeast'], inviteHandle) || - exitRule.kind !== 'afterDeadline' - ) { - throw rejectOffer(inviteHandle); - } + const expected = harden({ + give: ['UnderlyingAsset'], + want: ['StrikePrice'], + exit: ['afterDeadline'], + }); + rejectIfNotProposal(inviteHandle, expected); return makeCallOptionInvite(inviteHandle); }, }); @@ -63,6 +69,5 @@ export const makeContract = harden((zoe, terms) => { return harden({ invite: makeCoveredCallInvite(), - terms, }); }); diff --git a/packages/zoe/src/contracts/helpers/auctions.js b/packages/zoe/src/contracts/helpers/auctions.js index 775331d6026..d67ed71941f 100644 --- a/packages/zoe/src/contracts/helpers/auctions.js +++ b/packages/zoe/src/contracts/helpers/auctions.js @@ -1,18 +1,18 @@ import harden from '@agoric/harden'; -export const secondPriceLogic = (bidExtentOps, bidOfferHandles, bids) => { - let highestBid = bidExtentOps.getEmpty(); - let secondHighestBid = bidExtentOps.getEmpty(); +export const secondPriceLogic = (bidAmountMath, bidOfferHandles, bids) => { + 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]; // If the bid is greater than the highestBid, it's the new highestBid - if (bidExtentOps.isGTE(bid, highestBid)) { + if (bidAmountMath.isGTE(bid, highestBid)) { secondHighestBid = highestBid; highestBid = bid; highestBidOfferHandle = offerHandle; - } else if (bidExtentOps.isGTE(bid, secondHighestBid)) { + } 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. secondHighestBid = bid; @@ -25,63 +25,23 @@ export const secondPriceLogic = (bidExtentOps, bidOfferHandles, bids) => { }); }; -export const firstPriceLogic = (bidExtentOps, bidOfferHandles, bids) => { - let highestBid = bidExtentOps.getEmpty(); - let highestBidOfferHandle; - // eslint-disable-next-line array-callback-return - bidOfferHandles.map((offerHandle, i) => { - const bid = bids[i]; - // If the bid is greater than the highestBid, it's the new highestBid - if (bidExtentOps.isGTE(bid, highestBid)) { - highestBid = bid; - highestBidOfferHandle = offerHandle; - } - }); - return harden({ - winnerOfferHandle: highestBidOfferHandle, - winnerBid: highestBid, - price: highestBid, - }); -}; - -export const isOverMinimumBid = ( - zoe, - issuers, - bidIndex, - creatorOfferHandle, - bidOfferHandle, -) => { - const { amount: creatorAmounts } = zoe.getOffer(creatorOfferHandle); - const { amount: bidAmounts } = zoe.getOffer(bidOfferHandle); - const bidAmountMath = zoe.getAmountMathForIssuers(issuers)[bidIndex]; - const minimumBid = creatorAmounts[bidIndex]; - const bidMade = bidAmounts[bidIndex]; - return bidAmountMath.isGTE(bidMade, minimumBid); -}; - export const closeAuction = ( zoe, - issuers, - { auctionLogicFn, itemIndex, bidIndex, sellerInviteHandle, allBidHandles }, + { auctionLogicFn, sellerInviteHandle, allBidHandles }, ) => { - const amountMathArray = zoe.getAmountMathForIssuers(issuers); - const bidAmountMath = amountMathArray[bidIndex]; - const itemAmountMath = amountMathArray[itemIndex]; + const { issuerKeywordRecord } = zoe.getInstanceRecord(); + const { Bid: bidAmountMath, Asset: assetAmountMath } = zoe.getAmountMaths( + issuerKeywordRecord, + ); // Filter out any inactive bids const { active: activeBidHandles } = zoe.getOfferStatuses( harden(allBidHandles), ); - const getAmounts = offer => offer.amounts; - const getBids = amount => amount[bidIndex]; - const bids = zoe - .getOffers(activeBidHandles) - .map(getAmounts) - .map(getBids); - const itemAmountsUpForAuction = zoe.getOffer(sellerInviteHandle).payoutRules[ - itemIndex - ].amount; + const getBids = offerRecord => offerRecord.amounts.Bid; + const bids = zoe.getOffers(activeBidHandles).map(getBids); + const assetAmount = zoe.getOffer(sellerInviteHandle).proposal.give.Asset; const { winnerOfferHandle: winnerInviteHandle, @@ -93,15 +53,15 @@ export const closeAuction = ( // price paid. const winnerRefund = bidAmountMath.subtract(winnerBid, price); - const newCreatorAmounts = [itemAmountMath.getEmpty(), price]; - const newWinnerAmounts = [itemAmountsUpForAuction, winnerRefund]; + const newSellerAmounts = { Asset: assetAmountMath.getEmpty(), Bid: price }; + const newWinnerAmounts = { Asset: assetAmount, Bid: winnerRefund }; // Everyone else gets a refund so their extents remain the // same. zoe.reallocate( harden([sellerInviteHandle, winnerInviteHandle]), - harden([newCreatorAmounts, newWinnerAmounts]), + harden([newSellerAmounts, newWinnerAmounts]), ); const allOfferHandles = harden([sellerInviteHandle, ...activeBidHandles]); - zoe.complete(allOfferHandles, issuers); + zoe.complete(allOfferHandles); }; diff --git a/packages/zoe/src/contracts/helpers/bondingCurves.js b/packages/zoe/src/contracts/helpers/bondingCurves.js index 8f86b786dbc..cd8197d5086 100644 --- a/packages/zoe/src/contracts/helpers/bondingCurves.js +++ b/packages/zoe/src/contracts/helpers/bondingCurves.js @@ -1,102 +1,81 @@ -import Nat from '@agoric/nat'; import harden from '@agoric/harden'; -import { assert, details } from '@agoric/assert'; import { natSafeMath } from './safeMath'; const { add, subtract, multiply, floorDivide } = natSafeMath; -export const makeConstProductBC = (zoe, issuers) => { - const amountMathArray = zoe.getAmountMathForIssuers(issuers); - const brands = zoe.getBrandsForIssuers(issuers); +export const makeConstProductBC = zoe => { return harden({ /** * Contains the logic for calculating how much should be given * back to the user in exchange for what they sent in. It also * calculates the new amount of the assets in the pool. Reused in - * several different places, including to check whether an offer is - * valid, getting the current price for an asset on user request, and - * to do the actual reallocation after an offer has been made. - * @param {amount[]} poolAmountsArray - an array of the current amount in the - * liquidity pool - * @param {amount} amountIn - the amount sent in by a user - * @param {number} feeInTenthOfPercent=3 - the fee taken in tenths of - * a percent. The default is 0.3%. The fee is taken from amountIn + * several different places, including to check whether an offer + * is valid, getting the current price for an asset on user + * request, and to do the actual reallocation after an offer has + * been made. + * @param {extent} inputExtent - the extent of the assets sent in + * to be swapped + * @param {extent} inputReserve - the extent in the liquidity + * pool of the kind of asset sent in + * @param {extent} outputReserve - the extent in the liquidity + * pool of the kind of asset to be sent out + * @param {number} feeBasisPoints=30 - the fee taken in + * basis points. The default is 0.3% or 30 basis points. The fee is taken from + * inputExtent */ - getPrice: (poolAmountsArray, amountIn, feeInTenthOfPercent = 3) => { - Nat(feeInTenthOfPercent); - assert( - feeInTenthOfPercent < 1000, - details`fee ${feeInTenthOfPercent} is not less than 1000`, - ); - const oneMinusFeeInThousandths = subtract(1000, feeInTenthOfPercent); + getPrice: ({ + inputExtent, + inputReserve, + outputReserve, + feeBasisPoints = 30, + }) => { + const oneMinusFeeInTenThousandths = subtract(10000, feeBasisPoints); + const inputWithFee = multiply(inputExtent, oneMinusFeeInTenThousandths); + const numerator = multiply(inputWithFee, outputReserve); + const denominator = add(multiply(inputReserve, 10000), inputWithFee); - // Calculates how much can be bought by selling input - const getInputPrice = (input, inputReserve, outputReserve) => { - const inputWithFee = multiply(input, oneMinusFeeInThousandths); - const numerator = multiply(inputWithFee, outputReserve); - const denominator = add(multiply(inputReserve, 1000), inputWithFee); - return floorDivide(numerator, denominator); - }; - - const brandIn = amountIn.brand; - const X = 0; - const Y = 1; - const [xAmountMath, yAmountMath] = zoe.getAmountMathForIssuers(issuers); - const xReserve = poolAmountsArray[X].extent; - const yReserve = poolAmountsArray[Y].extent; - if (brandIn === brands[X]) { - const xExtentIn = amountIn.extent; - const yExtentOut = getInputPrice(xExtentIn, xReserve, yReserve); - const newPoolAmountsArray = [...poolAmountsArray]; - newPoolAmountsArray[X] = xAmountMath.make(add(xReserve, xExtentIn)); - newPoolAmountsArray[Y] = yAmountMath.make( - subtract(yReserve, yExtentOut), - ); - return { - amountOut: yAmountMath.make(yExtentOut), - newPoolAmountsArray: harden(newPoolAmountsArray), - }; - } - if (brandIn === brands[Y]) { - const yExtentIn = amountIn.extent; - const xExtentOut = getInputPrice(yExtentIn, yReserve, xReserve); - const newPoolAmountsArray = [...poolAmountsArray]; - newPoolAmountsArray[X] = xAmountMath.make( - subtract(xReserve, xExtentOut), - ); - newPoolAmountsArray[Y] = yAmountMath.make(add(yReserve, yExtentIn)); - return { - amountOut: xAmountMath.make(xExtentOut), - newPoolAmountsArray: harden(newPoolAmountsArray), - }; - } - throw new Error(`amountIn ${amountIn} were malformed`); + const outputExtent = floorDivide(numerator, denominator); + const newOutputReserve = subtract(outputReserve, outputExtent); + const newInputReserve = add(inputReserve, inputExtent); + return harden({ outputExtent, newInputReserve, newOutputReserve }); }, // Calculate how many liquidity tokens we should be minting to // send back to the user when adding liquidity. Calculations are - // based on the extents represented by index 0. If the current - // supply is zero, start off by just taking the extent at index 0 + // based on the extents represented by TokenA. If the current + // supply is zero, start off by just taking the extent at TokenA // and using it as the extent for the liquidity token. - calcLiqExtentToMint: (liqTokenSupply, poolAmounts, userAmounts) => + calcLiqExtentToMint: ({ liqTokenSupply, inputExtent, inputReserve }) => liqTokenSupply > 0 - ? floorDivide( - multiply(userAmounts[0].extent, liqTokenSupply), - poolAmounts[0].extent, - ) - : userAmounts[0].extent, + ? floorDivide(multiply(inputExtent, liqTokenSupply), inputReserve) + : inputExtent, // Calculate how many underlying tokens (in the form of amount) // should be returned when removing liquidity. - calcAmountsToRemove: (liqTokenSupply, poolAmounts, liquidityAmountsIn) => - poolAmounts.map((amount, i) => - amountMathArray[i].make( + calcAmountsToRemove: ({ + liqTokenSupply, + poolAmounts, + liquidityExtentIn, + }) => { + const { issuerKeywordRecord } = zoe.getInstanceRecord(); + const amountMaths = zoe.getAmountMaths(issuerKeywordRecord); + const newUserAmounts = harden({ + TokenA: amountMaths.TokenA.make( + floorDivide( + multiply(liquidityExtentIn, poolAmounts.TokenA.extent), + liqTokenSupply, + ), + ), + TokenB: amountMaths.TokenB.make( floorDivide( - multiply(liquidityAmountsIn.extent, amount.extent), + multiply(liquidityExtentIn, poolAmounts.TokenB.extent), liqTokenSupply, ), ), - ), + Liquidity: amountMaths.Liquidity.getEmpty(), + }); + return newUserAmounts; + }, }); }; diff --git a/packages/zoe/src/contracts/helpers/userFlow.js b/packages/zoe/src/contracts/helpers/userFlow.js deleted file mode 100644 index 7b1d3154865..00000000000 --- a/packages/zoe/src/contracts/helpers/userFlow.js +++ /dev/null @@ -1,105 +0,0 @@ -import harden from '@agoric/harden'; - -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 hasKinds = (kinds, newPayoutRules) => - kinds.every((kind, i) => kind === newPayoutRules[i].kind); - -const hasBrands = (brands, newPayoutRules) => - brands.every((brand, i) => brand === newPayoutRules[i].amount.brand); - -export const makeHelpers = (zoe, issuers) => { - const amountMathArray = zoe.getAmountMathForIssuers(issuers); - const brands = zoe.getBrandsForIssuers(issuers); - const zoeService = zoe.getZoeService(); - const helpers = harden({ - getActiveOffers: handles => - zoe.getOffers(zoe.getOfferStatuses(handles).active), - rejectOffer: (inviteHandle, msg = defaultRejectMsg) => { - zoe.complete(harden([inviteHandle])); - throw new Error(msg); - }, - areAssetsEqualAtIndex: (index, leftHandle, rightHandle) => - amountMathArray[index].isEqual( - zoe.getOffer(leftHandle).payoutRules[index].amount, - zoe.getOffer(rightHandle).payoutRules[index].amount, - ), - canTradeWith: (leftInviteHandle, rightInviteHandle) => { - const { payoutRules: leftPayoutRules } = zoe.getOffer(leftInviteHandle); - const { payoutRules: rightPayoutRules } = zoe.getOffer(rightInviteHandle); - const satisfied = (wants, offers) => - wants.every((want, i) => { - if (want.kind === 'wantAtLeast') { - return ( - offers[i].kind === 'offerAtMost' && - amountMathArray[i].isGTE(offers[i].amount, want.amount) - ); - } - return true; - }); - return ( - satisfied(leftPayoutRules, rightPayoutRules) && - satisfied(rightPayoutRules, leftPayoutRules) - ); - }, - hasValidPayoutRules: (kinds, inviteHandle) => { - const { payoutRules } = zoe.getOffer(inviteHandle); - return hasKinds(kinds, payoutRules) && hasBrands(brands, payoutRules); - }, - swap: ( - keepHandle, - tryHandle, - keepHandleInactiveMsg = 'prior offer is unavailable', - ) => { - if (!zoe.isOfferActive(keepHandle)) { - throw helpers.rejectOffer(tryHandle, keepHandleInactiveMsg); - } - if (!helpers.canTradeWith(keepHandle, tryHandle)) { - throw helpers.rejectOffer(tryHandle); - } - const keepAmounts = zoe.getOffer(keepHandle).amounts; - const tryAmounts = zoe.getOffer(tryHandle).amounts; - // reallocate by switching the amount - const handles = harden([keepHandle, tryHandle]); - zoe.reallocate(handles, harden([tryAmounts, keepAmounts])); - zoe.complete(handles); - return defaultAcceptanceMsg; - }, - // Vector addition of two amount arrays - vectorWith: (leftAmountsArray, rightAmountsArray) => { - const withAmounts = leftAmountsArray.map((leftAmounts, i) => - amountMathArray[i].add(leftAmounts, rightAmountsArray[i]), - ); - return withAmounts; - }, - // Vector subtraction of two amount arrays - vectorWithout: (leftAmountsArray, rightAmountsArray) => { - const withoutAmounts = leftAmountsArray.map((leftAmounts, i) => - amountMathArray[i].subtract(leftAmounts, rightAmountsArray[i]), - ); - return withoutAmounts; - }, - makeEmptyAmounts: () => - amountMathArray.map(amountMath => amountMath.getEmpty()), - makeEmptyOffer: () => { - const { inviteHandle, invite } = zoe.makeInvite(); - const offerRules = harden({ - payoutRules: amountMathArray.map(amountMath => { - return { - kind: 'wantAtLeast', - amount: amountMath.getEmpty(), - }; - }), - exitRule: { - kind: 'waived', - }, - }); - const offerPayments = amountMathArray.map(() => undefined); - return zoeService - .redeem(invite, offerRules, offerPayments) - .then(() => inviteHandle); - }, - }); - return helpers; -}; diff --git a/packages/zoe/src/contracts/helpers/zoeHelpers.js b/packages/zoe/src/contracts/helpers/zoeHelpers.js new file mode 100644 index 00000000000..ad521ba2a63 --- /dev/null +++ b/packages/zoe/src/contracts/helpers/zoeHelpers.js @@ -0,0 +1,116 @@ +import harden from '@agoric/harden'; +import { assert, details } from '@agoric/assert'; +import { sameStructure } from '@agoric/same-structure'; + +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`; + +export const getKeys = obj => harden(Object.getOwnPropertyNames(obj || {})); + +export const makeZoeHelpers = zoe => { + const { issuerKeywordRecord } = zoe.getInstanceRecord(); + const amountMaths = zoe.getAmountMaths(issuerKeywordRecord); + const zoeService = zoe.getZoeService(); + + const rejectOffer = (inviteHandle, msg = defaultRejectMsg) => { + zoe.complete(harden([inviteHandle])); + throw new Error(msg); + }; + + // Compare the keys of actual with expected keys and reject offer if + // not sameStructure. If expectedKeys is undefined, no comparison occurs. + const rejectIf = ( + inviteHandle, + actual, + expectedKeys, + msg = defaultRejectMsg, + // eslint-disable-next-line consistent-return + ) => { + if (expectedKeys !== undefined) { + if (!sameStructure(getKeys(actual), expectedKeys)) { + return rejectOffer(inviteHandle, msg); + } + } + }; + // Compare actual to expected keys. If expectedKeys is + // undefined, return true trivially. + const check = (actual, expectedKeys) => { + if (expectedKeys === undefined) { + return true; + } + return sameStructure(getKeys(actual), expectedKeys); + }; + const helpers = harden({ + assertKeywords: expected => { + // 'actual' is sorted in alphabetical order by Zoe + const { keywords: actual } = zoe.getInstanceRecord(); + expected = [...expected]; // in case hardened + expected.sort(); + assert( + sameStructure(actual, harden(expected)), + details`keywords: ${actual} were not as expected: ${expected}`, + ); + }, + rejectIfNotProposal: (inviteHandle, expected) => { + const { proposal: actual } = zoe.getOffer(inviteHandle); + rejectIf(inviteHandle, actual.give, expected.give); + rejectIf(inviteHandle, actual.want, expected.want); + rejectIf(inviteHandle, actual.exit, expected.exit); + }, + checkIfProposal: (inviteHandle, expected) => { + const { proposal: actual } = zoe.getOffer(inviteHandle); + return ( + // Check that the "give" keys match expected keys. + check(actual.give, expected.give) && + // Check that the "want" keys match expected keys. + check(actual.want, expected.want) && + // Check that the "exit" key (i.e. "onDemand") matches the expected key. + check(actual.exit, expected.exit) + ); + }, + getActiveOffers: handles => + zoe.getOffers(zoe.getOfferStatuses(handles).active), + rejectOffer, + canTradeWith: (leftInviteHandle, rightInviteHandle) => { + const { proposal: left } = zoe.getOffer(leftInviteHandle); + const { proposal: right } = zoe.getOffer(rightInviteHandle); + const { keywords } = zoe.getInstanceRecord(); + const satisfied = (want, give) => + keywords.every(keyword => { + if (want[keyword]) { + return amountMaths[keyword].isGTE(give[keyword], want[keyword]); + } + return true; + }); + return ( + satisfied(left.want, right.give) && satisfied(right.want, left.give) + ); + }, + swap: ( + keepHandle, + tryHandle, + keepHandleInactiveMsg = 'prior offer is unavailable', + ) => { + if (!zoe.isOfferActive(keepHandle)) { + throw helpers.rejectOffer(tryHandle, keepHandleInactiveMsg); + } + if (!helpers.canTradeWith(keepHandle, tryHandle)) { + throw helpers.rejectOffer(tryHandle); + } + const keepAmounts = zoe.getOffer(keepHandle).amounts; + const tryAmounts = zoe.getOffer(tryHandle).amounts; + // reallocate by switching the amount + const handles = harden([keepHandle, tryHandle]); + zoe.reallocate(handles, harden([tryAmounts, keepAmounts])); + zoe.complete(handles); + return defaultAcceptanceMsg; + }, + makeEmptyOffer: () => { + const { inviteHandle, invite } = zoe.makeInvite(); + return zoeService + .redeem(invite, harden({}), harden({})) + .then(() => inviteHandle); + }, + }); + return helpers; +}; diff --git a/packages/zoe/src/contracts/myFirstDapp.js b/packages/zoe/src/contracts/myFirstDapp.js deleted file mode 100644 index 8176e2800d8..00000000000 --- a/packages/zoe/src/contracts/myFirstDapp.js +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint-disable no-use-before-define */ -import harden from '@agoric/harden'; -import makePromise from '@agoric/make-promise'; -import { makeHelpers, defaultAcceptanceMsg } from './helpers/userFlow'; - -/** EDIT THIS CONTRACT WITH YOUR OWN BUSINESS LOGIC */ - -/** - * This contract is like the simpleExchange contract. The exchange only accepts - * limit orders. A limit order is an order with payoutRules that specifies - * wantAtLeast on one side and offerAtMost on the other: - * [ { kind: 'wantAtLeast', amount2 }, { kind: 'offerAtMost', amount1 }] - * [ { kind: 'wantAtLeast', amount1 }, { kind: 'offerAtMost', amount2 }] - * - * Note that the asset specified as wantAtLeast is treated as the exact amount - * to be exchanged, while the amount specified as offerAtMost is a limit that - * may be improved on. This simple exchange does not partially fill orders. - */ -export const makeContract = harden((zoe, terms) => { - const ASSET_INDEX = 0; - let sellInviteHandles = []; - let buyInviteHandles = []; - let nextChangePromise = makePromise(); - - const { issuers } = terms; - const { - rejectOffer, - hasValidPayoutRules, - swap, - areAssetsEqualAtIndex, - canTradeWith, - getActiveOffers, - } = makeHelpers(zoe, issuers); - - function flattenRule(r) { - switch (r.kind) { - case 'offerAtMost': - return { offer: r.amount }; - case 'wantAtLeast': - return { want: r.amount }; - default: - throw new Error(`${r.kind} not supported.`); - } - } - - function flattenOffer(o) { - return harden([ - flattenRule(o.payoutRules[0]), - flattenRule(o.payoutRules[1]), - ]); - } - - function flattenOrders(offerHandles) { - const result = zoe - .getOffers(zoe.getOfferStatuses(offerHandles).active) - .map(offer => flattenOffer(offer)); - return result; - } - - function getBookOrders() { - return { - changed: nextChangePromise.p, - buys: flattenOrders(buyInviteHandles), - sells: flattenOrders(sellInviteHandles), - }; - } - - function getOffer(inviteHandle) { - for (const handle of [...sellInviteHandles, ...buyInviteHandles]) { - if (inviteHandle === handle) { - return flattenOffer(getActiveOffers([inviteHandle])[0]); - } - } - return 'not an active offer'; - } - - // This is a really simple update protocol, which merely provides a promise - // in getBookOrders() that will resolve when the state changes. Clients - // subscribe to the promise and are notified at some future point. A much - // nicer protocol is in /~https://github.com/Agoric/agoric-sdk/issues/253 - function bookOrdersChanged() { - nextChangePromise.res(true); - nextChangePromise = makePromise(); - } - - function swapOrAddToBook(inviteHandles, inviteHandle) { - for (const iHandle of inviteHandles) { - if ( - areAssetsEqualAtIndex(ASSET_INDEX, inviteHandle, iHandle) && - canTradeWith(inviteHandle, iHandle) - ) { - bookOrdersChanged(); - return swap(inviteHandle, iHandle); - } - } - bookOrdersChanged(); - return defaultAcceptanceMsg; - } - - const makeInvite = () => { - const seat = harden({ - // This code might be modified to support immediate_or_cancel. Current - // implementation is effectively fill_or_kill. - addOrder: () => { - // Is it a valid sell offer? - if (hasValidPayoutRules(['offerAtMost', 'wantAtLeast'], inviteHandle)) { - // Save the valid offer and try to match - - // IDEA: to implement matching against the best price, the orders - // should be sorted. (We'd also want to allow partial matches.) - sellInviteHandles.push(inviteHandle); - buyInviteHandles = [...zoe.getOfferStatuses(buyInviteHandles).active]; - return swapOrAddToBook(buyInviteHandles, inviteHandle); - } - // Is it a valid buy offer? - if (hasValidPayoutRules(['wantAtLeast', 'offerAtMost'], inviteHandle)) { - // Save the valid offer and try to match - buyInviteHandles.push(inviteHandle); - sellInviteHandles = [ - ...zoe.getOfferStatuses(sellInviteHandles).active, - ]; - return swapOrAddToBook(sellInviteHandles, inviteHandle); - } - // Eject because the offer must be invalid - throw rejectOffer(inviteHandle); - }, - }); - const { invite, inviteHandle } = zoe.makeInvite(seat); - return { invite, inviteHandle }; - }; - - return harden({ - invite: makeInvite(), - publicAPI: { makeInvite, getBookOrders, getOffer }, - terms, - }); -}); diff --git a/packages/zoe/src/contracts/publicAuction.js b/packages/zoe/src/contracts/publicAuction.js index 59361aafee3..e47e9559491 100644 --- a/packages/zoe/src/contracts/publicAuction.js +++ b/packages/zoe/src/contracts/publicAuction.js @@ -2,59 +2,52 @@ import harden from '@agoric/harden'; import Nat from '@agoric/nat'; -import { defaultAcceptanceMsg, makeHelpers } from './helpers/userFlow'; +import { defaultAcceptanceMsg, makeZoeHelpers } from './helpers/zoeHelpers'; import { secondPriceLogic, closeAuction } from './helpers/auctions'; -export const makeContract = harden((zoe, terms) => { - const { issuers } = terms; - const { rejectOffer, canTradeWith, hasValidPayoutRules } = makeHelpers( - zoe, - issuers, - ); - const numBidsAllowed = - terms.numBidsAllowed !== undefined ? Nat(terms.numBidsAllowed) : Nat(3); +export const makeContract = harden(zoe => { + const { + rejectOffer, + canTradeWith, + assertKeywords, + rejectIfNotProposal, + } = makeZoeHelpers(zoe); + + let { + terms: { numBidsAllowed }, + } = zoe.getInstanceRecord(); + numBidsAllowed = Nat(numBidsAllowed !== undefined ? numBidsAllowed : 3); let sellerInviteHandle; let minimumBid; let auctionedAssets; const allBidHandles = []; - // The item up for auction is described first in the payoutRules array - const ITEM_INDEX = 0; - const BID_INDEX = 1; + assertKeywords(harden(['Asset', 'Bid'])); const makeBidderInvite = () => { const seat = harden({ bid: () => { // Check that the item is still up for auction if (!zoe.isOfferActive(sellerInviteHandle)) { - throw rejectOffer( - inviteHandle, - `The item up for auction has been withdrawn or the auction has completed`, - ); + const rejectMsg = `The item up for auction is not available or the auction has completed`; + throw rejectOffer(inviteHandle, rejectMsg); } if (allBidHandles.length >= numBidsAllowed) { throw rejectOffer(inviteHandle, `No further bids allowed.`); } - if ( - !hasValidPayoutRules(['wantAtLeast', 'offerAtMost'], inviteHandle) - ) { - throw rejectOffer(inviteHandle); - } + const expected = harden({ give: ['Bid'], want: ['Asset'] }); + rejectIfNotProposal(inviteHandle, expected); if (!canTradeWith(sellerInviteHandle, inviteHandle)) { - throw rejectOffer( - inviteHandle, - `Bid was under minimum bid or for the wrong assets`, - ); + const rejectMsg = `Bid was under minimum bid or for the wrong assets`; + throw rejectOffer(inviteHandle, rejectMsg); } // Save valid bid and try to close. allBidHandles.push(inviteHandle); if (allBidHandles.length >= numBidsAllowed) { - closeAuction(zoe, issuers, { + closeAuction(zoe, { auctionLogicFn: secondPriceLogic, - itemIndex: ITEM_INDEX, - bidIndex: BID_INDEX, sellerInviteHandle, allBidHandles, }); @@ -73,18 +66,16 @@ export const makeContract = harden((zoe, terms) => { const makeSellerInvite = () => { const seat = harden({ sellAssets: () => { - if ( - auctionedAssets || - !hasValidPayoutRules(['offerAtMost', 'wantAtLeast'], inviteHandle) - ) { - throw rejectOffer(inviteHandle); + if (auctionedAssets) { + throw rejectOffer(inviteHandle, `assets already present`); } - + const expected = harden({ give: ['Asset'], want: ['Bid'] }); + rejectIfNotProposal(inviteHandle, expected); // Save the valid offer sellerInviteHandle = inviteHandle; - const { payoutRules } = zoe.getOffer(inviteHandle); - auctionedAssets = payoutRules[0].amount; - minimumBid = payoutRules[1].amount; + const { proposal } = zoe.getOffer(inviteHandle); + auctionedAssets = proposal.give.Asset; + minimumBid = proposal.want.Bid; return defaultAcceptanceMsg; }, }); @@ -110,6 +101,5 @@ export const makeContract = harden((zoe, terms) => { getAuctionedAssetsAmounts: () => auctionedAssets, getMinimumBid: () => minimumBid, }, - terms, }); }); diff --git a/packages/zoe/src/contracts/simpleExchange.js b/packages/zoe/src/contracts/simpleExchange.js index c178d7282df..4981e5ed21e 100644 --- a/packages/zoe/src/contracts/simpleExchange.js +++ b/packages/zoe/src/contracts/simpleExchange.js @@ -1,57 +1,54 @@ /* eslint-disable no-use-before-define */ import harden from '@agoric/harden'; import makePromise from '@agoric/make-promise'; -import { makeHelpers, defaultAcceptanceMsg } from './helpers/userFlow'; +import { makeZoeHelpers, defaultAcceptanceMsg } from './helpers/zoeHelpers'; /** - * The SimpleExchange only accepts limit orders. A limit order is an order with - * payoutRules that specifies wantAtLeast on one side and offerAtMost on the - * other: - * [ { kind: 'wantAtLeast', amount2 }, { kind: 'offerAtMost', amount1 }] - * [ { kind: 'wantAtLeast', amount1 }, { kind: 'offerAtMost', amount2 }] + * The SimpleExchange uses Asset and Price as its keywords. In usage, + * they're somewhat symmetrical. Participants will be buying or + * selling in both directions. * - * Note that the asset specified as wantAtLeast is treated as the exact amount - * to be exchanged, while the amount specified as offerAtMost is a limit that - * may be improved on. This simple exchange does not partially fill orders. + * { give: { 'Asset', simoleans(5) }, want: { 'Price', quatloos(3) } } + * { give: { 'Price', quatloos(8) }, want: { 'Asset', simoleans(3) } } + * + * The Asset is treated as an exact amount to be exchanged, while the + * Price is a limit that may be improved on. This simple exchange does + * not partially fill orders. */ -export const makeContract = harden((zoe, terms) => { - const ASSET_INDEX = 0; +export const makeContract = harden(zoe => { + const PRICE = 'Price'; + const ASSET = 'Asset'; + let sellInviteHandles = []; let buyInviteHandles = []; let nextChangePromise = makePromise(); - const { issuers } = terms; const { rejectOffer, - hasValidPayoutRules, + checkIfProposal, swap, - areAssetsEqualAtIndex, canTradeWith, getActiveOffers, - } = makeHelpers(zoe, issuers); + assertKeywords, + } = makeZoeHelpers(zoe); + + assertKeywords(harden([ASSET, PRICE])); function flattenRule(r) { - switch (r.kind) { - case 'offerAtMost': - return { offer: r.amount }; - case 'wantAtLeast': - return { want: r.amount }; - default: - throw new Error(`${r.kind} not supported.`); - } + const keyword = Object.getOwnPropertyNames(r)[0]; + const struct = {}; + struct[keyword] = r[keyword].extent; + return harden(struct); } function flattenOffer(o) { - return harden([ - flattenRule(o.payoutRules[0]), - flattenRule(o.payoutRules[1]), - ]); + return harden([flattenRule(o.proposal.want), flattenRule(o.proposal.give)]); } function flattenOrders(offerHandles) { const result = zoe .getOffers(zoe.getOfferStatuses(offerHandles).active) - .map(offer => flattenOffer(offer)); + .map(offerRecord => flattenOffer(offerRecord)); return result; } @@ -81,12 +78,9 @@ export const makeContract = harden((zoe, terms) => { nextChangePromise = makePromise(); } - function swapOrAddToBook(inviteHandles, inviteHandle) { + function swapIfCanTrade(inviteHandles, inviteHandle) { for (const iHandle of inviteHandles) { - if ( - areAssetsEqualAtIndex(ASSET_INDEX, inviteHandle, iHandle) && - canTradeWith(inviteHandle, iHandle) - ) { + if (canTradeWith(inviteHandle, iHandle)) { bookOrdersChanged(); return swap(inviteHandle, iHandle); } @@ -98,24 +92,31 @@ export const makeContract = harden((zoe, terms) => { const makeInvite = () => { const seat = harden({ addOrder: () => { - // Is it a valid sell offer? - if (hasValidPayoutRules(['offerAtMost', 'wantAtLeast'], inviteHandle)) { + const buyAssetForPrice = harden({ + give: [PRICE], + want: [ASSET], + }); + const sellAssetForPrice = harden({ + give: [ASSET], + want: [PRICE], + }); + if (checkIfProposal(inviteHandle, sellAssetForPrice)) { // Save the valid offer and try to match sellInviteHandles.push(inviteHandle); buyInviteHandles = [...zoe.getOfferStatuses(buyInviteHandles).active]; - return swapOrAddToBook(buyInviteHandles, inviteHandle); - } - // Is it a valid buy offer? - if (hasValidPayoutRules(['wantAtLeast', 'offerAtMost'], inviteHandle)) { + return swapIfCanTrade(buyInviteHandles, inviteHandle); + /* eslint-disable no-else-return */ + } else if (checkIfProposal(inviteHandle, buyAssetForPrice)) { // Save the valid offer and try to match buyInviteHandles.push(inviteHandle); sellInviteHandles = [ ...zoe.getOfferStatuses(sellInviteHandles).active, ]; - return swapOrAddToBook(sellInviteHandles, inviteHandle); + return swapIfCanTrade(sellInviteHandles, inviteHandle); + } else { + // Eject because the offer must be invalid + return rejectOffer(inviteHandle); } - // Eject because the offer must be invalid - throw rejectOffer(inviteHandle); }, }); const { invite, inviteHandle } = zoe.makeInvite(seat); @@ -125,6 +126,5 @@ export const makeContract = harden((zoe, terms) => { return harden({ invite: makeInvite(), publicAPI: { makeInvite, getBookOrders, getOffer }, - terms, }); }); diff --git a/packages/zoe/src/isOfferSafe.js b/packages/zoe/src/isOfferSafe.js deleted file mode 100644 index 076c99d884d..00000000000 --- a/packages/zoe/src/isOfferSafe.js +++ /dev/null @@ -1,85 +0,0 @@ -import { assert, details } from '@agoric/assert'; - -/** - * `isOfferSafeForOffer` checks offer safety for a single offer. - * - * Note: This implementation checks whether we refund for all rules or - * return winnings for all rules. It does not allow some refunds and - * some winnings, which is what would happen if you checked the rules - * independently. It *does* allow for returning a full refund plus - * full winnings. - * - * @param {amountMath[]} amountMaths - an array of amountMath ordered in - * the same order as the issuers for the payoutRules - * @param {payoutRule[]} payoutRules - the rules that accompanied the - * escrow of payments that dictate what the user expected to get back - * from Zoe. The payoutRules are an array of objects that have a kind - * and amount, in the same order as the corresponding issuers. The - * offerRules, including the payoutRules, are a player's understanding - * of the contract that they are entering when they make an offer. A - * payoutRule is structured in the form `{ kind: descriptionString, - * amount}` - * @param {amount[]} amounts - an array of amounts ordered in the same - * order as the issuers for the payoutRules. This array of amount is the - * reallocation to be given to a player. - */ -function isOfferSafeForOffer(amountMaths, payoutRules, amounts) { - assert( - amountMaths.length === payoutRules.length && - amountMaths.length === amounts.length, - details`amountMaths, payoutRules, and amounts must be arrays of the same length`, - ); - - const allowedRules = ['offerAtMost', 'wantAtLeast']; - - for (const payoutRule of payoutRules) { - if (payoutRule === null || payoutRule === undefined) { - throw new Error(`payoutRule must be specified`); - } - assert( - allowedRules.includes(payoutRule.kind), - details`The kind ${payoutRule.kind} was not recognized`, - ); - } - - // For this allocation to count as a full refund, the allocated - // amount must be greater than or equal to what was originally - // offered. - const refundOk = payoutRules.every((payoutRule, i) => { - if (payoutRule.kind === 'offerAtMost') { - return amountMaths[i].isGTE(amounts[i], payoutRule.amount); - } - // If the kind is 'want', anything we give back is fine for a refund. - return true; - }); - - // For this allocation to count as a full payout of what the user - // wanted, their allocated amount must be greater than or equal to - // what the payoutRules said they wanted. - const winningsOk = payoutRules.every((payoutRule, i) => { - if (payoutRule.kind === 'wantAtLeast') { - return amountMaths[i].isGTE(amounts[i], payoutRule.amount); - } - // If the kind is 'offer anything we give back is fine for a payout. - return true; - }); - return refundOk || winningsOk; -} - -/** - * @param {amountMath[]} amountMaths - an array of amountMath ordered in - * the same order as the issuers for the payoutRules - * @param {payoutRules[][]} payoutRulesMatrix - an array of arrays. Each of the - * element arrays is the payout rules that a single player - * made. - * @param {amount[][]} amountMatrix - an array of arrays. Each of the - * element arrays is the array of amount that a single player will - * get, in the same order as the issuers for the payoutRules - */ -const isOfferSafeForAll = (amountMaths, payoutRuleMatrix, amountMatrix) => - payoutRuleMatrix.every((payoutRules, i) => - isOfferSafeForOffer(amountMaths, payoutRules, amountMatrix[i]), - ); - -// `isOfferSafeForOffer` is only exported for testing -export { isOfferSafeForOffer, isOfferSafeForAll }; diff --git a/packages/zoe/src/objArrayConversion.js b/packages/zoe/src/objArrayConversion.js new file mode 100644 index 00000000000..389b77e0884 --- /dev/null +++ b/packages/zoe/src/objArrayConversion.js @@ -0,0 +1,20 @@ +import { assert, details } from '@agoric/assert'; + +export const arrayToObj = (array, keywords) => { + assert( + array.length === keywords.length, + details`array and keywords must be of equal length`, + ); + const obj = {}; + keywords.forEach((keyword, i) => (obj[keyword] = array[i])); + return obj; +}; + +export const objToArray = (obj, keywords) => { + const keys = Object.getOwnPropertyNames(obj); + assert( + keys.length === keywords.length, + `object keys and keywords must be of equal length`, + ); + return keywords.map(keyword => obj[keyword]); +}; diff --git a/packages/zoe/src/offerSafety.js b/packages/zoe/src/offerSafety.js new file mode 100644 index 00000000000..89fc6c538e5 --- /dev/null +++ b/packages/zoe/src/offerSafety.js @@ -0,0 +1,68 @@ +/** + * `isOfferSafeForOffer` checks offer safety for a single offer. + * + * Note: This implementation checks whether we refund for all rules or + * return winnings for all rules. It does not allow some refunds and + * some winnings, which is what would happen if you checked the rules + * independently. It *does* allow for returning a full refund plus + * full winnings. + * + * @param {object} amountMathKeywordRecord - a record with keywords as + * keys and amountMath as values + * @param {object} 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 player's + * understanding of the contract that they are entering when they make + * an offer. + * @param {object} newAmountKeywordRecord - a record with keywords as keys and + * amounts as values. These amounts are the reallocation to be given to a user. + */ +function isOfferSafeForOffer( + amountMathKeywordRecord, + proposal, + newAmountKeywordRecord, +) { + const isGTEByKeyword = ([keyword, amount]) => + amountMathKeywordRecord[keyword].isGTE( + newAmountKeywordRecord[keyword], + amount, + ); + // For this allocation to count as a full refund, the allocated + // amount must be greater than or equal to what was originally + // offered. + const refundOk = Object.entries(proposal.give).every(isGTEByKeyword); + + // For this allocation to count as a full payout of what the user + // wanted, their allocated amount must be greater than or equal to + // what the payoutRules said they wanted. + const winningsOk = Object.entries(proposal.want).every(isGTEByKeyword); + return refundOk || winningsOk; +} + +/** + * @param {object} amountMathKeywordRecord - a record with keywords + * as keys and amountMath as values + * @param {proposal[]} proposals - an array of records which are the + * proposal for a single player. Each proposal has keys `give`, + * `want`, and `exit`. + * @param {newAmountKeywordRecord[]} newAmountKeywordRecords- an array of + * records. Each of the records (amountKeywordRecord) has keywords for + * keys and the values are the amount that a single user will get. + */ +const isOfferSafeForAll = ( + amountMathKeywordRecord, + proposals, + newAmountKeywordRecords, +) => + proposals.every((proposal, i) => + isOfferSafeForOffer( + amountMathKeywordRecord, + proposal, + newAmountKeywordRecords[i], + ), + ); + +// `isOfferSafeForOffer` is only exported for testing +export { isOfferSafeForOffer, isOfferSafeForAll }; diff --git a/packages/zoe/src/areRightsConserved.js b/packages/zoe/src/rightsConservation.js similarity index 83% rename from packages/zoe/src/areRightsConserved.js rename to packages/zoe/src/rightsConservation.js index c93b8373532..5aeedb99dc6 100644 --- a/packages/zoe/src/areRightsConserved.js +++ b/packages/zoe/src/rightsConservation.js @@ -34,9 +34,9 @@ const sumByIssuer = (amountMathArray, amountMatrix) => * @param {amount[]} rightAmounts - an array of total amount per issuer * indexed by issuer */ -const isEqualPerIssuer = (amountMaths, leftAmounts, rightAmounts) => +const isEqualPerIssuer = (amountMathArray, leftAmounts, rightAmounts) => leftAmounts.every((leftAmount, i) => - amountMaths[i].isEqual(leftAmount, rightAmounts[i]), + amountMathArray[i].isEqual(leftAmount, rightAmounts[i]), ); /** @@ -51,13 +51,13 @@ const isEqualPerIssuer = (amountMaths, leftAmounts, rightAmounts) => * issuer */ function areRightsConserved( - amountMaths, + amountMathArray, previousAmountsMatrix, newAmountsMatrix, ) { - const sumsPrevAmounts = sumByIssuer(amountMaths, previousAmountsMatrix); - const sumsNewAmounts = sumByIssuer(amountMaths, newAmountsMatrix); - return isEqualPerIssuer(amountMaths, sumsPrevAmounts, sumsNewAmounts); + const sumsPrevAmounts = sumByIssuer(amountMathArray, previousAmountsMatrix); + const sumsNewAmounts = sumByIssuer(amountMathArray, newAmountsMatrix); + return isEqualPerIssuer(amountMathArray, sumsPrevAmounts, sumsNewAmounts); } export { areRightsConserved, transpose }; diff --git a/packages/zoe/src/state.js b/packages/zoe/src/state.js index 41befbd6020..bcd81cb611e 100644 --- a/packages/zoe/src/state.js +++ b/packages/zoe/src/state.js @@ -74,52 +74,35 @@ const makeInstallationTable = () => { }; // Instance Table -// Columns: handle | installationHandle | publicAPI | terms | issuers +// Columns: handle | installationHandle | publicAPI | +// terms | issuerKeywordRecord | keywords const makeInstanceTable = () => { // TODO: make sure this validate function protects against malicious // misshapen objects rather than just a general check. const validateSomewhat = makeValidateProperties( - harden(['installationHandle', 'publicAPI', 'terms', 'issuers']), + harden([ + 'installationHandle', + 'publicAPI', + 'terms', + 'issuerKeywordRecord', + 'keywords', + ]), ); + return makeTable(validateSomewhat); }; // Offer Table -// Columns: handle | instanceHandle | issuers | payoutRules | exitRule -// | amounts +// Columns: handle | instanceHandle | proposal | amounts const makeOfferTable = () => { - const insistValidPayoutRuleKinds = payoutRules => { - const acceptedKinds = ['offerAtMost', 'wantAtLeast']; - for (const payoutRule of payoutRules) { - assert( - acceptedKinds.includes(payoutRule.kind), - details`${payoutRule.kind} must be one of the accepted kinds.`, - ); - } - }; - const insistValidExitRule = exitRule => { - const acceptedExitRuleKinds = [ - 'waived', - 'onDemand', - 'afterDeadline', - // 'onDemandAfterDeadline', // not yet supported - ]; - assert( - acceptedExitRuleKinds.includes(exitRule.kind), - details`exitRule.kind ${exitRule.kind} is not one of the accepted options`, - ); - }; - // TODO: make sure this validate function protects against malicious // misshapen objects rather than just a general check. const validateProperties = makeValidateProperties( - harden(['instanceHandle', 'issuers', 'payoutRules', 'exitRule', 'amounts']), + harden(['instanceHandle', 'proposal', 'amounts']), ); const validateSomewhat = obj => { validateProperties(obj); - insistValidPayoutRuleKinds(obj.payoutRules); - insistValidExitRule(obj.exitRule); - // TODO: Should check the rest of the representation of the payout rule + // TODO: Should check the rest of the representation of the proposal // TODO: Should check that the deadline representation is itself valid. return true; }; @@ -145,9 +128,9 @@ const makeOfferTable = () => { isOfferActive: offerHandle => table.has(offerHandle), deleteOffers: offerHandles => offerHandles.map(offerHandle => table.delete(offerHandle)), - updateAmountMatrix: (offerHandles, newAmountMatrix) => + updateAmounts: (offerHandles, newAmountKeywordRecords) => offerHandles.map((offerHandle, i) => - table.update(offerHandle, { amounts: newAmountMatrix[i] }), + table.update(offerHandle, { amounts: newAmountKeywordRecords[i] }), ), }); return customMethods; @@ -161,7 +144,7 @@ const makeOfferTable = () => { const makePayoutMap = makeStore; // Issuer Table -// Columns: issuer | brand| purse | amountMath +// Columns: issuer | brand | purse | amountMath const makeIssuerTable = () => { // TODO: make sure this validate function protects against malicious // misshapen objects rather than just a general check. @@ -173,12 +156,14 @@ const makeIssuerTable = () => { const issuersInProgress = makeStore(); const customMethods = harden({ - getAmountMathForIssuers: issuers => - issuers.map(issuer => table.get(issuer).amountMath), - - getBrandsForIssuers: issuers => - issuers.map(issuer => table.get(issuer).brand), - + getAmountMaths: issuerKeywordRecord => { + const amountMathKeywordRecord = {}; + Object.entries(issuerKeywordRecord).map( + ([keyword, issuer]) => + (amountMathKeywordRecord[keyword] = table.get(issuer).amountMath), + ); + return harden(amountMathKeywordRecord); + }, getPursesForIssuers: issuers => issuers.map(issuer => table.get(issuer).purse), diff --git a/packages/zoe/src/zoe.chainmail b/packages/zoe/src/zoe.chainmail index ff9a6d7c9c9..df705b3e4a3 100644 --- a/packages/zoe/src/zoe.chainmail +++ b/packages/zoe/src/zoe.chainmail @@ -26,14 +26,19 @@ interface ZoeService { /** * 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 `terms` of the contract. The contract terms - * are the arguments to the contract, and must include - * the expected issuers for the underlying rights. (Other than the - * `issuers` property of `terms`, the `terms` properties are up to - * the discretion of the smart contract.) We get back an invite (an - * ERTP payment) to participate in the contract. + * 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.) Parties to the + * contract will use the keywords to index their proposal and + * their payments. + * The payout users receive from Zoe will be in the form of an object + * with keywords as keys. 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. */ - makeInstance(installationHandle :InstallationHandle, terms :Object) -> (Invite); + makeInstance(installationHandle :InstallationHandle, issuerKeywordRecord :IssuerKeywordRecord, terms :Object) -> (Invite); /** * Credibly get information about the instance (such as the installation @@ -42,74 +47,71 @@ interface ZoeService { getInstance(instanceHandle :InstanceHandle) -> (InstanceRecord); /** - * To redeem an invite, the user must provide a list of payments as well as - * their rules for the offer. + * To redeem an invite, the user must provide a proposal (their rules for the + * offer) as well as payments to be escrowed by Zoe. * - * The rules for the offer are in two parts: `payoutRules` are used - * by Zoe to enforce offer safety, and `exitRule` is used to specify + * The proposal has three parts: `want` and `give` are used + * by Zoe to enforce offer safety, and `exit` is used to specify * the extent of payout liveness that Zoe can guarantee. - * `payoutRules` is a list of objects, each with a `kind` property - * (such as 'offerAtMost') and an amount property. The objects in the - * `payoutRules` must be in the same order - * as the issuers associated with a smart contract. That is, the - * amount in index 0 of `payoutRules` should be an amount for the issuer - * in index 0 of the issuers array. `payments` is an array of the - * actual payments to be escrowed, following the rules in the - * payoutRules. If the payoutRules kind is 'offerAtMost', - * then a payment matching the amount is expected. If the payoutRules - * kind is 'wantAtLeast' then the payment will be - * ignored and should be `undefined`. + * `want` and `give` are objects with keywords as keys and amounts + * as values. `payments` 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`. */ - redeem (invite :Invite, offerRules :OfferRules, payments :List(Payment)) -> (SeatAndPayout); + redeem (invite :Invite, proposal :Proposal, payments :PaymentKeywordRecord)) -> (SeatAndPayout); } /** * This is returned by a call to `redeem` on Zoe. * A seat is an arbitrary object whose methods allow the user to take - * certain actions in a contract. The payout is a promise that resolves to an array - * of promises for payments. Note that while the payout promise - * resolves when an offer is completed, the promises for each payment - * resolves after the remote issuer successfully withdraws the payment. + * certain actions in a contract. The payout is a promise that resolves + * to an object 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. */ struct SeatAndPayout ( ) { seat :Object; payout :List(Payment); } -struct OfferRules ( ) { - payoutRules :List(PayoutRule); - exitRule :ExitRule; +struct Proposal ( ) { + want :AmountKeywordRecord; + give :AmountKeywordRecord; + exit :ExitRule; } /** - * payoutRules are an array of PayoutRule. The possible - * kinds are 'offerAtMost' and 'wantAtLeast'. + * The keys are keywords, and the values are amounts. For example: + * { Asset: amountMath.make(5), Price: amountMath.make(9) } */ -struct PayoutRule ( ) { - kind :PayoutRuleKind; - amount :Amount; +struct AmountKeywordRecord ( ) { } /** - * The possible kinds are 'waived', 'onDemand', and 'afterDeadline'. - * `timer` and `deadline` only are used for the `afterDeadline` kind. + * The possible keys are 'waived', 'onDemand', and 'afterDeadline'. + * `timer` and `deadline` only are used for the `afterDeadline` key. + * The possible records are: + * `{ waived: null }` + * `{ onDemand: null }` + * `{ afterDeadline: { timer :Timer, deadline :Deadline } } */ struct ExitRule ( ) { - kind :ExitRuleKind; timer :Timer; - deadline :Number; + deadline :Deadline; } interface ZoeContractFacet () { /** * Instruct Zoe to try reallocating for the given offerHandles. - * Reallocation is a matrix (array of arrays) where the rows are the + * Reallocation is an array of AmountKeywordRecords, which are objects where + * the keys are keywords and the values are amounts. The * amounts to be paid to the player who made the offer at the same * index in the offerHandles array. The reallocation will only happen if * 'offer safety' and conservation of rights are true, as enforced by Zoe. */ - reallocate (offerHandles :List(OfferHandle), reallocation :List(List(Amount))); + reallocate (offerHandles :List(OfferHandle), reallocation :List(AmountKeywordRecord)); /** * Eject the offer, taking the current allocation for that offer and @@ -125,7 +127,7 @@ interface ZoeContractFacet () { * Inform Zoe about new issuers. Returns a promise for acknowledging * when the issuer is added and ready. */ - addNewIssuer (issuer :Issuer) -> (Promise(Undefined)); + addNewIssuer (issuer :Issuer, keyword :String) -> (Promise(Undefined)); /** * Expose the user-facing Zoe Service API to the contracts as well. @@ -137,8 +139,9 @@ interface ZoeContractFacet () { /** Get the Zoe inviteIssuer */ getInviteIssuer ( ) -> (Issuer); - /** Get a list of local amountMath per issuer */ - getAmountMathForIssuers (issuers :List(Issuer)) -> (List(AmountMath)); + /** Get a list of local amountMath for each keyword in the issuerKeywordRecord + object */ + getAmountMaths (issuerKeywordRecord :IssuerKeywordRecord) -> (AmountMathKeywordRecord)); /** Divide the offerHandles into 'active' and 'inactive' lists */ getOfferStatuses ( offerHandles :List(OfferHandle)) -> @@ -156,6 +159,9 @@ interface ZoeContractFacet () { /** Get the offer record */ getOffer ( offerHandle :OfferHandle) -> (List(OfferRecord)); + /** Get instance record */ + getInstanceRecord ( ) -> InstanceRecord; + } /** diff --git a/packages/zoe/src/zoe.js b/packages/zoe/src/zoe.js index de94e63bed2..bcb9bb4d19d 100644 --- a/packages/zoe/src/zoe.js +++ b/packages/zoe/src/zoe.js @@ -5,8 +5,10 @@ import produceIssuer from '@agoric/ertp'; import { assert, details } from '@agoric/assert'; import makePromise from '@agoric/make-promise'; -import { isOfferSafeForAll } from './isOfferSafe'; -import { areRightsConserved } from './areRightsConserved'; +import { cleanProposal } from './cleanProposal'; +import { arrayToObj, objToArray } from './objArrayConversion'; +import { isOfferSafeForAll } from './offerSafety'; +import { areRightsConserved } from './rightsConservation'; import { evalContractCode } from './evalContractCode'; import { makeTables } from './state'; @@ -38,24 +40,65 @@ const makeZoe = (additionalEndowments = {}) => { if (inactive.length > 0) { throw new Error(`offer has already completed`); } - const offers = offerTable.getOffers(offerHandles); + const offerRecords = offerTable.getOffers(offerHandles); - const { issuers } = instanceTable.get(instanceHandle); + const { issuerKeywordRecord, keywords } = instanceTable.get(instanceHandle); // Remove the offers from the offerTable so that they are no // longer active. offerTable.deleteOffers(offerHandles); - // Resolve the payout promises with the payouts + // Resolve the payout promises with promises for the payouts + const issuers = objToArray(issuerKeywordRecord, keywords); const pursePs = issuerTable.getPursesForIssuers(issuers); - for (const offer of offers) { - const payout = offer.amounts.map((amount, j) => - E(pursePs[j]).withdraw(amount, 'payout'), - ); - payoutMap.get(offer.handle).res(payout); + for (const offerRecord of offerRecords) { + const payout = {}; + keywords.forEach((keyword, i) => { + payout[keyword] = E(pursePs[i]).withdraw(offerRecord.amounts[keyword]); + }); + harden(payout); + payoutMap.get(offerRecord.handle).res(payout); } }; + const assertKeyword = keyword => { + assert.typeof(keyword, 'string'); + const firstCapASCII = /^[A-Z][a-zA-Z0-9_$]*$/; + assert( + firstCapASCII.test(keyword), + details`keyword must be ascii and must start with a capital letter.`, + ); + }; + + const getKeywords = issuerKeywordRecord => { + // `getOwnPropertyNames` returns all the non-symbol properties + // (both enumerable and non-enumerable). + const keywords = Object.getOwnPropertyNames(issuerKeywordRecord); + // We sort to get a deterministic order that is not based on key + // insertion order or object creation order. + keywords.sort(); + harden(keywords); + + // Insist that there are no symbol properties. + assert( + Object.getOwnPropertySymbols(issuerKeywordRecord).length === 0, + details`no symbol properties allowed`, + ); + + // Assert all key characters are ascii and keys start with a + // capital letter. + keywords.forEach(assertKeyword); + + return keywords; + }; + + // Make a safe(r) object from keys that have been validated + const makeCleanedObj = (rawObj, cleanedKeys) => { + const cleanedObj = {}; + cleanedKeys.forEach(key => (cleanedObj[key] = rawObj[key])); + return harden(cleanedObj); + }; + // Make a Zoe invite with an extent that is a mix of credible // information from Zoe (the `handle` and `instanceHandle`) and // other information defined by the smart contract. Note that the @@ -89,43 +132,63 @@ const makeZoe = (additionalEndowments = {}) => { const makeContractFacet = instanceHandle => { const contractFacet = harden({ /** - * The contract can propose a reallocation of extents per - * offer, which will only succeed if the reallocation 1) - * conserves rights, and 2) is 'offer-safe' for all parties - * involved. This reallocation is partial, meaning that it - * applies only to the amount associated with the offerHandles - * that are passed in. We are able to ensure that with - * each reallocation, rights are conserved and offer safety is - * enforced for all offers, even though the reallocation is - * partial, because once these invariants are true, they will - * remain true until changes are made. + * The contract can propose a reallocation of extents per offer, + * which will only succeed if the reallocation 1) conserves + * rights, and 2) is 'offer-safe' for all parties involved. This + * reallocation is partial, meaning that it applies only to the + * amount associated with the offerHandles that are passed in. + * We are able to ensure that with each reallocation, rights are + * conserved and offer safety is enforced for all offers, even + * though the reallocation is partial, because once these + * invariants are true, they will remain true until changes are + * made. * @param {object[]} offerHandles - an array of offerHandles - * @param {amount[][]} newAmountMatrix - a matrix of amount, with - * one array of amount per offerHandle. + * @param {amountKeywordRecord[]} amountKeywordRecords - an + * array of amountKeywordRecords - objects with keyword keys + * and amount values, with one keywordRecord per offerHandle. */ - reallocate: (offerHandles, newAmountMatrix) => { - const { issuers } = instanceTable.get(instanceHandle); + reallocate: (offerHandles, amountKeywordRecords) => { + const { keywords, issuerKeywordRecord } = instanceTable.get( + instanceHandle, + ); + + const newAmountMatrix = amountKeywordRecords.map(amountObj => + objToArray(amountObj, keywords), + ); - const offers = offerTable.getOffers(offerHandles); + const offerRecords = offerTable.getOffers(offerHandles); - const payoutRuleMatrix = offers.map(offer => offer.payoutRules); - const currentAmountMatrix = offers.map(offer => offer.amounts); - const amountMaths = issuerTable.getAmountMathForIssuers(issuers); + const proposals = offerRecords.map(offerRecord => offerRecord.proposal); + const currentAmountMatrix = offerRecords.map(offerRecord => + objToArray(offerRecord.amounts, keywords), + ); + const amountMathKeywordRecord = issuerTable.getAmountMaths( + issuerKeywordRecord, + ); + const amountMathsArray = objToArray(amountMathKeywordRecord, keywords); // 1) ensure that rights are conserved assert( - areRightsConserved(amountMaths, currentAmountMatrix, newAmountMatrix), + areRightsConserved( + amountMathsArray, + currentAmountMatrix, + newAmountMatrix, + ), details`Rights are not conserved in the proposed reallocation`, ); // 2) ensure 'offer safety' for each player assert( - isOfferSafeForAll(amountMaths, payoutRuleMatrix, newAmountMatrix), + isOfferSafeForAll( + amountMathKeywordRecord, + proposals, + amountKeywordRecords, + ), details`The proposed reallocation was not offer safe`, ); // 3) save the reallocation - offerTable.updateAmountMatrix(offerHandles, harden(newAmountMatrix)); + offerTable.updateAmounts(offerHandles, harden(amountKeywordRecords)); }, /** @@ -160,18 +223,25 @@ const makeZoe = (additionalEndowments = {}) => { makeInvite: (seat, customProperties) => makeInvite(instanceHandle, seat, customProperties), - // informs Zoe about an issuer and returns a promise for acknowledging + // Informs Zoe about an issuer and returns a promise for acknowledging // when the issuer is added and ready. - addNewIssuer: issuer => - issuerTable.getPromiseForIssuerRecord(issuer).then(_ => { - const { issuers, terms } = instanceTable.get(instanceHandle); - const newIssuers = [...issuers, issuer]; + addNewIssuer: (issuerP, keyword) => + issuerTable.getPromiseForIssuerRecord(issuerP).then(issuerRecord => { + assertKeyword(keyword); + const { issuerKeywordRecord, keywords } = instanceTable.get( + instanceHandle, + ); + assert(!keywords.includes(keyword), details`keyword must be unique`); + const newIssuerKeywordRecord = { + ...issuerKeywordRecord, + [keyword]: issuerRecord.issuer, + }; + // We append the new keyword and new issuer to the end of + // the arrays. + const newKeywords = [...keywords, keyword]; instanceTable.update(instanceHandle, { - issuers: newIssuers, - terms: { - ...terms, - issuers: newIssuers, - }, + issuerKeywordRecord: newIssuerKeywordRecord, + keywords: newKeywords, }); }), @@ -180,12 +250,12 @@ const makeZoe = (additionalEndowments = {}) => { // The methods below are pure and have no side-effects // getInviteIssuer: () => inviteIssuer, - getAmountMathForIssuers: issuerTable.getAmountMathForIssuers, - getBrandsForIssuers: issuerTable.getBrandsForIssuers, + getAmountMaths: issuerTable.getAmountMaths, getOfferStatuses: offerTable.getOfferStatuses, isOfferActive: offerTable.isOfferActive, getOffers: offerTable.getOffers, getOffer: offerTable.get, + getInstanceRecord: () => instanceTable.get(instanceHandle), }); return contractFacet; }; @@ -231,38 +301,42 @@ const makeZoe = (additionalEndowments = {}) => { * other information, such as the terms used in the instance. * @param {object} installationHandle - the unique handle for the * installation + * @param {object} issuerKeywordRecord - a record mapping keyword keys to + * issuer values * @param {object} terms - arguments to the contract. These - * arguments depend on the contract, apart from the `issuers` - * property, which is required. + * arguments depend on the contract. */ - makeInstance: (installationHandle, userProvidedTerms) => { + makeInstance: ( + installationHandle, + issuerKeywordRecord, + terms = harden({}), + ) => { const { installation } = installationTable.get(installationHandle); const instanceHandle = harden({}); const contractFacet = makeContractFacet(instanceHandle); - const makeContractInstance = issuerRecords => { - const terms = { - ...userProvidedTerms, - issuers: issuerRecords.map(record => record.issuer), - }; + const keywords = getKeywords(issuerKeywordRecord); + // Take the cleaned keywords and produce a safe(r) issuerKeywordRecord + const cleanedRecord = makeCleanedObj(issuerKeywordRecord, keywords); + const issuersP = keywords.map(keyword => cleanedRecord[keyword]); + const makeInstanceRecord = issuerRecords => { + const issuers = issuerRecords.map(record => record.issuer); + const cleanedIssuerKeywordRecord = arrayToObj(issuers, keywords); const instanceRecord = harden({ installationHandle, publicAPI: undefined, - issuers: terms.issuers, terms, + issuerKeywordRecord: cleanedIssuerKeywordRecord, + keywords, }); - // We create the instanceRecord before the contract is made - // that the contract can query Zoe for information about the - // instance (get issuers, amountMaths, etc.) instanceTable.create(instanceRecord, instanceHandle); return Promise.resolve() - .then(_ => installation.makeContract(contractFacet, terms)) - .then(value => { + .then(_ => installation.makeContract(contractFacet)) + .then(({ invite, publicAPI }) => { // Once the contract is made, we add the publicAPI to the // contractRecord - const { invite, publicAPI } = value; instanceTable.update(instanceHandle, { publicAPI }); return invite; }); @@ -271,8 +345,8 @@ const makeZoe = (additionalEndowments = {}) => { // The issuers may not have been seen before, so we must wait for // the issuer records to be available synchronously return issuerTable - .getPromiseForIssuerRecords(userProvidedTerms.issuers) - .then(makeContractInstance); + .getPromiseForIssuerRecords(issuersP) + .then(makeInstanceRecord); }, /** * Credibly retrieves an instance record given an instanceHandle. @@ -282,68 +356,17 @@ const makeZoe = (additionalEndowments = {}) => { getInstance: instanceTable.get, /** - * Redeem the invite to receive a seat and a payout promise. + * Redeem the invite to receive a seat and a payout + * promise. * @param {payment} invite - an invite (ERTP payment) to join a * Zoe smart contract instance - * @param {offerRule[]} offerRules - the offer rules, an object - * with properties `payoutRules` and `exitRule`. - * @param {payment[]} offerPayments - payments corresponding to - * the offer rules. A payment may be `undefined` in the case of - * specifying a `wantAtLeast`. + * @param {object} proposal - the proposal, a record + * with properties `want`, `give`, and `exit`. The keys of + * `want` and `give` are keywords and the values are amounts. + * @param {object} paymentKeywordRecord - a record with keyword + * keys and values which are payments that will be escrowed by Zoe. */ - redeem: (invite, offerRules, offerPayments) => { - // Create result to be returned. Depends on exitRule - const makeRedemptionResult = ({ instanceHandle, offerHandle }) => { - const redemptionResult = { - seat: handleToSeat.get(offerHandle), - payout: payoutMap.get(offerHandle).p, - }; - const { exitRule } = offerRules; - // Automatically cancel on deadline. - if (exitRule.kind === 'afterDeadline') { - E(exitRule.timer).setWakeup( - exitRule.deadline, - harden({ - wake: () => completeOffers(instanceHandle, harden([offerHandle])), - }), - ); - } - - // Add an object with a cancel method to redemptionResult in - // order to cancel on demand. - if (exitRule.kind === 'onDemand') { - redemptionResult.cancelObj = { - cancel: () => completeOffers(instanceHandle, harden([offerHandle])), - }; - } - - // if the exitRule.kind is 'waived' the user has no - // possibility of cancelling - return harden(redemptionResult); - }; - - // if 'offerAtMost', deposit payout and return coerced amounts; else empty - function depositPayout(issuer, i) { - const issuerRecordP = issuerTable.getPromiseForIssuerRecord(issuer); - const payoutRule = offerRules.payoutRules[i]; - const offerPayment = offerPayments[i]; - - return issuerRecordP.then(({ purse, amountMath }) => { - if (payoutRule.kind === 'offerAtMost') { - // We cannot trust these amounts since they come directly - // from the remote issuer and so we must coerce them. - return E(purse) - .deposit(offerPayment, payoutRule.amount) - .then(_ => amountMath.coerce(payoutRule.amount)); - } - assert( - offerPayments[i] === undefined, - details`payment was included, but the rule kind was ${payoutRule.kind}`, - ); - return Promise.resolve(amountMath.getEmpty()); - }); - } - + redeem: (invite, proposal, paymentKeywordRecord) => { return inviteIssuer.burn(invite).then(inviteAmount => { assert( inviteAmount.extent.length === 1, @@ -353,31 +376,88 @@ const makeZoe = (additionalEndowments = {}) => { const { extent: [{ instanceHandle, handle: offerHandle }], } = inviteAmount; + const { keywords, issuerKeywordRecord } = instanceTable.get( + instanceHandle, + ); + + const amountMathKeywordRecord = issuerTable.getAmountMaths( + issuerKeywordRecord, + ); - const { issuers } = instanceTable.get(instanceHandle); - // Promise flow = issuer -> purse -> deposit payment -> seat + payout - const paymentDepositedPs = issuers.map(depositPayout); + proposal = cleanProposal(keywords, amountMathKeywordRecord, proposal); + // Promise flow = issuer -> purse -> deposit payment -> seat/payout + const giveKeywords = Object.getOwnPropertyNames(proposal.give); + const paymentDepositedPs = keywords.map(keyword => { + const issuer = issuerKeywordRecord[keyword]; + const issuerRecordP = issuerTable.getPromiseForIssuerRecord(issuer); + return issuerRecordP.then(({ purse, amountMath }) => { + if (giveKeywords.includes(keyword)) { + // We cannot trust these amounts since they come directly + // from the remote issuer and so we must coerce them. + return E(purse) + .deposit(paymentKeywordRecord[keyword], proposal.give[keyword]) + .then(_ => amountMath.coerce(proposal.give[keyword])); + } + // If any other payments are included, they are ignored. + return Promise.resolve(amountMathKeywordRecord[keyword].getEmpty()); + }); + }); - function recordOfferAndPayout(amounts) { + const recordOffer = amountsArray => { const offerImmutableRecord = { instanceHandle, - payoutRules: offerRules.payoutRules, - exitRule: offerRules.exitRule, - issuers, - amounts, + proposal, + amounts: arrayToObj(amountsArray, keywords), }; // Since we have redeemed an invite, the inviteHandle is // also the offerHandle. offerTable.create(offerImmutableRecord, offerHandle); payoutMap.init(offerHandle, makePromise()); - return { instanceHandle, offerHandle }; - } + }; + + // Create result to be returned. Depends on `exit` + const makeRedemptionResult = _ => { + const redemptionResult = { + seat: handleToSeat.get(offerHandle), + payout: payoutMap.get(offerHandle).p, + }; + const { exit } = proposal; + const [exitKind] = Object.getOwnPropertyNames(exit); + // Automatically cancel on deadline. + if (exitKind === 'afterDeadline') { + E(exit.afterDeadline.timer).setWakeup( + exit.afterDeadline.deadline, + harden({ + wake: () => + completeOffers(instanceHandle, harden([offerHandle])), + }), + ); + // Add an object with a cancel method to redemptionResult in + // order to cancel on demand. + } else if (exitKind === 'onDemand') { + redemptionResult.cancelObj = { + cancel: () => + completeOffers(instanceHandle, harden([offerHandle])), + }; + } else { + assert( + exitKind === 'waived', + details`exit kind was not recognized: ${exitKind}`, + ); + } + // if the exitRule.kind is 'waived' the user has no + // possibility of cancelling + return harden(redemptionResult); + }; return Promise.all(paymentDepositedPs) - .then(recordOfferAndPayout) + .then(recordOffer) .then(makeRedemptionResult); }); }, + isOfferActive: offerTable.isOfferActive, + getOffers: offerTable.getOffers, + getOffer: offerTable.get, }); return zoeService; }; diff --git a/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js b/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js index f0ceec6f2c5..725b0cf74c7 100644 --- a/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js +++ b/packages/zoe/test/swingsetTests/zoe-metering/bootstrap.js @@ -38,8 +38,9 @@ function build(E, log) { log(`installing ${testName}`); const installId = await installations[testName](); log(`instantiating ${testName}`); - const invite = await E(zoe).makeInstance(installId, { issuers: [] }); const inviteIssuer = E(zoe).getInviteIssuer(); + const issuerKeywordRecord = harden({ Keyword1: inviteIssuer }); + const invite = await E(zoe).makeInstance(installId, issuerKeywordRecord); const { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(invite); diff --git a/packages/zoe/test/swingsetTests/zoe-metering/infiniteTestLoop.js b/packages/zoe/test/swingsetTests/zoe-metering/infiniteTestLoop.js index 171438f357e..388a1b3a609 100644 --- a/packages/zoe/test/swingsetTests/zoe-metering/infiniteTestLoop.js +++ b/packages/zoe/test/swingsetTests/zoe-metering/infiniteTestLoop.js @@ -1,6 +1,6 @@ import harden from '@agoric/harden'; -export const makeContract = (zoe, terms) => { +export const makeContract = (zoe, _terms) => { const seat = harden({}); const { invite } = zoe.makeInvite(seat, { seatDesc: 'tester', @@ -14,6 +14,5 @@ export const makeContract = (zoe, terms) => { }, }, invite, - terms, }); }; 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 bf379cd2ac3..4fafeaa4558 100644 --- a/packages/zoe/test/swingsetTests/zoe-metering/test-zoe-metering.js +++ b/packages/zoe/test/swingsetTests/zoe-metering/test-zoe-metering.js @@ -41,13 +41,12 @@ async function main(basedir, withSES, argv) { const infiniteInstallLoopLog = ['installing infiniteInstallLoop']; test('zoe - metering - infinite loop in installation', async t => { + t.plan(1); try { const dump = await main('zoe-metering', true, ['infiniteInstallLoop']); t.deepEquals(dump.log, infiniteInstallLoopLog, 'log is correct'); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -56,13 +55,12 @@ const infiniteInstanceLoopLog = [ 'instantiating infiniteInstanceLoop', ]; test('zoe - metering - infinite loop in instantiation', async t => { + t.plan(1); try { const dump = await main('zoe-metering', true, ['infiniteInstanceLoop']); t.deepEquals(dump.log, infiniteInstanceLoopLog, 'log is correct'); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -72,13 +70,12 @@ const infiniteTestLoopLog = [ 'invoking infiniteTestLoop.doTest()', ]; test('zoe - metering - infinite loop in contract method', async t => { + t.plan(1); try { const dump = await main('zoe-metering', true, ['infiniteTestLoop']); t.deepEquals(dump.log, infiniteTestLoopLog, 'log is correct'); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -88,12 +85,11 @@ const testBuiltinsLog = [ 'invoking testBuiltins.doTest()', ]; test('zoe - metering - expensive builtins in contract method', async t => { + t.plan(1); try { const dump = await main('zoe-metering', true, ['testBuiltins']); t.deepEquals(dump.log, testBuiltinsLog, 'log is correct'); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); diff --git a/packages/zoe/test/swingsetTests/zoe/test-zoe.js b/packages/zoe/test/swingsetTests/zoe/test-zoe.js index 8f48b91f723..2eb093e72cc 100644 --- a/packages/zoe/test/swingsetTests/zoe/test-zoe.js +++ b/packages/zoe/test/swingsetTests/zoe/test-zoe.js @@ -54,6 +54,7 @@ const expectedAutomaticRefundOkLog = [ ]; test('zoe - automaticRefund - valid inputs - with SES', async t => { + t.plan(1); try { const startingExtents = [ [3, 0, 0], @@ -66,8 +67,6 @@ test('zoe - automaticRefund - valid inputs - with SES', async t => { t.deepEquals(dump.log, expectedAutomaticRefundOkLog); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -83,6 +82,7 @@ const expectedCoveredCallOkLog = [ ]; test('zoe - coveredCall - valid inputs - with SES', async t => { + t.plan(1); try { const startingExtents = [ [3, 0, 0], @@ -92,8 +92,6 @@ test('zoe - coveredCall - valid inputs - with SES', async t => { t.deepEquals(dump.log, expectedCoveredCallOkLog); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -116,6 +114,7 @@ const expectedSwapForOptionOkLog = [ ]; test('zoe - swapForOption - valid inputs - with SES', async t => { + t.plan(1); try { const startingExtents = [ [3, 0, 0], // Alice starts with 3 moola @@ -127,8 +126,6 @@ test('zoe - swapForOption - valid inputs - with SES', async t => { t.deepEquals(dump.log, expectedSwapForOptionOkLog); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -148,6 +145,7 @@ const expectedPublicAuctionOkLog = [ 'aliceSimoleanPurse: balance {"brand":{},"extent":7}', ]; test('zoe - publicAuction - valid inputs - with SES', async t => { + t.plan(1); try { const startingExtents = [ [1, 0, 0], @@ -159,8 +157,6 @@ test('zoe - publicAuction - valid inputs - with SES', async t => { t.deepEquals(dump.log, expectedPublicAuctionOkLog); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -173,6 +169,7 @@ const expectedAtomicSwapOkLog = [ 'bobSimoleanPurse: balance {"brand":{},"extent":0}', ]; test('zoe - atomicSwap - valid inputs - with SES', async t => { + t.plan(1); try { const startingExtents = [ [3, 0, 0], @@ -182,8 +179,6 @@ test('zoe - atomicSwap - valid inputs - with SES', async t => { t.deepEquals(dump.log, expectedAtomicSwapOkLog); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -198,6 +193,7 @@ const expectedSimpleExchangeOkLog = [ ]; test('zoe - simpleExchange - valid inputs - with SES', async t => { + t.plan(1); try { const startingExtents = [ [3, 0, 0], @@ -207,8 +203,6 @@ test('zoe - simpleExchange - valid inputs - with SES', async t => { t.deepEquals(dump.log, expectedSimpleExchangeOkLog); } catch (e) { t.isNot(e, e, 'unexpected exception'); - } finally { - t.end(); } }); @@ -216,7 +210,7 @@ const expectedSimpleExchangeUpdateLog = [ '=> alice, bob, carol and dave are set up', 'Order update: b:[], s:[]', 'The offer has been accepted. Once the contract has been completed, please check your payout', - 'Order update: b:[], s:moola:3 for simoleans:4', + 'Order update: b:[], s:simoleans:4 for moola:3', 'The offer has been accepted. Once the contract has been completed, please check your payout', 'bobMoolaPurse: balance {"brand":{},"extent":0}', 'bobSimoleanPurse: balance {"brand":{},"extent":17}', @@ -259,7 +253,7 @@ const expectedAutoswapOkLog = [ 'bobMoolaPurse: balance {"brand":{},"extent":5}', 'bobSimoleanPurse: balance {"brand":{},"extent":5}', 'Liquidity successfully removed.', - 'poolAmounts[{"brand":{},"extent":0},{"brand":{},"extent":0},{"brand":{},"extent":10}]', + 'poolAmounts{"TokenA":{"brand":{},"extent":0},"TokenB":{"brand":{},"extent":0},"Liquidity":{"brand":{},"extent":10}}', 'aliceMoolaPurse: balance {"brand":{},"extent":8}', 'aliceSimoleanPurse: balance {"brand":{},"extent":7}', 'aliceLiquidityTokenPurse: balance {"brand":{},"extent":0}', diff --git a/packages/zoe/test/swingsetTests/zoe/vat-alice.js b/packages/zoe/test/swingsetTests/zoe/vat-alice.js index 1c301059d71..7ecc15cff92 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-alice.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-alice.js @@ -11,37 +11,28 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const doAutomaticRefund = async bobP => { log(`=> alice.doCreateAutomaticRefund called`); const installId = installations.automaticRefund; - const invite = await E(zoe).makeInstance(installId, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + Contribution1: moolaIssuer, + Contribution2: simoleanIssuer, }); - + const invite = await E(zoe).makeInstance(installId, issuerKeywordRecord); const { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(invite); const instanceRecord = await E(zoe).getInstance(instanceHandle); const { publicAPI } = instanceRecord; - const offerRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + give: { Contribution1: moola(3) }, + want: { Contribution2: simoleans(7) }, + exit: { onDemand: null }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = { Contribution1: moolaPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const outcome = await E(seat).makeOffer(); log(outcome); @@ -49,7 +40,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const bobInvite = E(publicAPI).makeInvite(); await E(bobP).doAutomaticRefund(bobInvite); const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.Contribution1; + const simoleanPayout = await payout.Contribution2; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -61,39 +53,30 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const doCoveredCall = async bobP => { log(`=> alice.doCreateCoveredCall called`); const installId = installations.coveredCall; - const invite = await E(zoe).makeInstance(installId, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + UnderlyingAsset: moolaIssuer, + StrikePrice: simoleanIssuer, }); + const invite = await E(zoe).makeInstance(installId, issuerKeywordRecord); - const offerRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 1, - timer, - }, + const proposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { afterDeadline: { deadline: 1, timer } }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = { UnderlyingAsset: moolaPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const option = await E(seat).makeCallOption(); await E(bobP).doCoveredCall(option); const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.UnderlyingAsset; + const simoleanPayout = await payout.StrikePrice; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -104,40 +87,39 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const doSwapForOption = async (bobP, _carolP, daveP) => { log(`=> alice.doSwapForOption called`); - const invite = await E(zoe).makeInstance(installations.coveredCall, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + UnderlyingAsset: moolaIssuer, + StrikePrice: simoleanIssuer, }); + const invite = await E(zoe).makeInstance( + installations.coveredCall, + issuerKeywordRecord, + ); - const offerRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), + const proposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { + afterDeadline: { + deadline: 100, + timer, }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 100, - timer, }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = harden({ UnderlyingAsset: moolaPayment }); const { seat, payout: payoutP } = await E(zoe).redeem( invite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const option = await E(seat).makeCallOption(); log('call option made'); await E(bobP).doSwapForOption(option, daveP); const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.UnderlyingAsset; + const simoleanPayout = await payout.StrikePrice; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -148,35 +130,31 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const doPublicAuction = async (bobP, carolP, daveP) => { const numBidsAllowed = 3; - const invite = await E(zoe).makeInstance(installations.publicAuction, { - issuers: [moolaIssuer, simoleanIssuer], - numBidsAllowed, + const issuerKeywordRecord = harden({ + Asset: moolaIssuer, + Bid: simoleanIssuer, }); + const terms = harden({ numBidsAllowed }); + const invite = await E(zoe).makeInstance( + installations.publicAuction, + issuerKeywordRecord, + terms, + ); const { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(invite); const { publicAPI } = await E(zoe).getInstance(instanceHandle); - const offerRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(1), - }, - { - kind: 'wantAtLeast', - amount: simoleans(3), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + give: { Asset: moola(1) }, + want: { Bid: simoleans(3) }, + exit: { onDemand: null }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = { Asset: moolaPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const offerResult = await E(seat).sellAssets(); @@ -193,7 +171,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { await Promise.all([bobDoneP, carolDoneP, daveDoneP]); const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.Asset; + const simoleanPayout = await payout.Bid; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -203,37 +182,33 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { }; const doAtomicSwap = async bobP => { - const invite = await E(zoe).makeInstance(installations.atomicSwap, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + Asset: moolaIssuer, + Price: simoleanIssuer, }); + const invite = await E(zoe).makeInstance( + installations.atomicSwap, + issuerKeywordRecord, + ); - const offerRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(7) }, + exit: { onDemand: null }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = { Asset: moolaPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const bobInviteP = await E(seat).makeFirstOffer(); E(bobP).doAtomicSwap(bobInviteP); const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.Asset; + const simoleanPayout = await payout.Price; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -243,34 +218,30 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { }; const doSimpleExchange = async bobP => { - const { invite } = await E(zoe).makeInstance(installations.simpleExchange, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + Price: simoleanIssuer, + Asset: moolaIssuer, }); + const { simpleExchange } = installations; + const { invite } = await E(zoe).makeInstance( + simpleExchange, + issuerKeywordRecord, + ); const { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(invite); const { publicAPI } = await E(zoe).getInstance(instanceHandle); - const aliceSellOrderOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(4), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const aliceSellOrderProposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = { Asset: moolaPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - aliceSellOrderOfferRules, - offerPayments, + aliceSellOrderProposal, + paymentKeywordRecord, ); const offerResult = await E(seat).addOrder(); @@ -281,7 +252,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { await E(bobP).doSimpleExchange(bobInviteP); const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.Asset; + const simoleanPayout = await payout.Price; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -290,19 +262,20 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { await showPurseBalance(simoleanPurseP, 'aliceSimoleanPurse', log); }; - function printOrders(orders, brandPetnames, brands, offerIndex = 0) { + function printOrders(orders, brandPetnames, brands, giveIndex = 0) { if (orders.length === 0) { return '[]'; } - const wantIndex = 1 - offerIndex; + const wantIndex = 1 - giveIndex; + const keywords = ['Price', 'Asset']; const descs = []; for (const o of orders) { - const offerBrandPetname = - brandPetnames[brands.indexOf(o[offerIndex].offer.brand)]; - const wantBrandPetname = - brandPetnames[brands.indexOf(o[wantIndex].want.brand)]; + const giveKeyword = Object.getOwnPropertyNames(o[giveIndex])[0]; + const wantKeyword = Object.getOwnPropertyNames(o[wantIndex])[0]; + const giveBrandPetname = brandPetnames[keywords.indexOf(giveKeyword)]; + const wantBrandPetname = brandPetnames[keywords.indexOf(wantKeyword)]; descs.push( - `${offerBrandPetname}:${o[offerIndex].offer.extent} for ${wantBrandPetname}:${o[wantIndex].want.extent}`, + `${giveBrandPetname}:${o[giveIndex][giveKeyword]} for ${wantBrandPetname}:${o[wantIndex][wantKeyword]}`, ); } return descs; @@ -315,54 +288,45 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const { changed: p, buys, sells } = orderResult; p.then(() => { pollForBookOrders(publicAPI, petnames, brands); - log( - `Order update: b:${printOrders( - buys, - petnames, - brands, - 1, - )}, s:${printOrders(sells, petnames, brands, 0)}`, - ); + const buyOrders = printOrders(buys, petnames, brands, 1); + const sellOrders = printOrders(sells, petnames, brands, 0); + log(`Order update: b:${buyOrders}, s:${sellOrders}`); }); }); } const doSimpleExchangeUpdates = async bobP => { - const { invite } = await E(zoe).makeInstance(installations.simpleExchange, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + Price: simoleanIssuer, + Asset: moolaIssuer, }); + const { simpleExchange } = installations; + const { invite } = await E(zoe).makeInstance( + simpleExchange, + issuerKeywordRecord, + ); const { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(invite); const { publicAPI } = await E(zoe).getInstance(instanceHandle); - const petnames = ['moola', 'simoleans']; + const petnames = ['simoleans', 'moola']; const brands = await Promise.all([ - E(moolaIssuer).getBrand(), E(simoleanIssuer).getBrand(), + E(moolaIssuer).getBrand(), ]); pollForBookOrders(publicAPI, petnames, brands); - const aliceSellOrderOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(4), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const aliceSellOrderProposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, }); - const offerPayments = [moolaPayment, undefined]; + const paymentKeywordRecord = { Asset: moolaPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - aliceSellOrderOfferRules, - offerPayments, + aliceSellOrderProposal, + paymentKeywordRecord, ); const offerResult = await E(seat).addOrder(); @@ -376,9 +340,10 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const payout = await payoutP; - await E(moolaPurseP).deposit(await payout[0]); - await E(simoleanPurseP).deposit(await payout[1]); - + const moolaPayout = await payout.Asset; + const simoleanPayout = await payout.Price; + await E(moolaPurseP).deposit(moolaPayout); + await E(simoleanPurseP).deposit(simoleanPayout); const { invite: bobInvite3P } = await E(publicAPI).makeInvite(); await E(bobP).doSimpleExchangeUpdates(bobInvite3P, 20, 13); const { invite: bobInvite4P } = await E(publicAPI).makeInvite(); @@ -389,9 +354,14 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { }; const doAutoswap = async bobP => { - const invite = await E(zoe).makeInstance(installations.autoswap, { - issuers: [moolaIssuer, simoleanIssuer], + const issuerKeywordRecord = harden({ + TokenA: moolaIssuer, + TokenB: simoleanIssuer, }); + const invite = await E(zoe).makeInstance( + installations.autoswap, + issuerKeywordRecord, + ); const { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(invite); @@ -403,30 +373,18 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { // Alice adds liquidity // 10 moola = 5 simoleans at the time of the liquidity adding // aka 2 moola = 1 simolean - const addLiquidityOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(10), - }, - { - kind: 'offerAtMost', - amount: simoleans(5), - }, - { - kind: 'wantAtLeast', - amount: liquidity(10), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const addLiquidityProposal = harden({ + give: { TokenA: moola(10), TokenB: simoleans(5) }, + want: { Liquidity: liquidity(10) }, + }); + const paymentKeywordRecord = harden({ + TokenA: moolaPayment, + TokenB: simoleanPayment, }); - const offerPayments = [moolaPayment, simoleanPayment, undefined]; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - addLiquidityOfferRules, - offerPayments, + addLiquidityProposal, + paymentKeywordRecord, ); const addLiquidityOutcome = await E(seat).addLiquidity(); @@ -434,7 +392,7 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(addLiquidityOutcome); const addLiquidityPayments = await payoutP; - const liquidityPayout = await addLiquidityPayments[2]; + const liquidityPayout = await addLiquidityPayments.Liquidity; const liquidityTokenPurseP = E(liquidityIssuer).makeEmptyPurse(); await E(liquidityTokenPurseP).deposit(liquidityPayout); @@ -443,24 +401,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { await E(bobP).doAutoswap(bobInviteP); // remove the liquidity - const aliceRemoveLiquidityPayoutRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(0), - }, - { - kind: 'wantAtLeast', - amount: simoleans(0), - }, - { - kind: 'offerAtMost', - amount: liquidity(10), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const aliceRemoveLiquidityProposal = harden({ + give: { Liquidity: liquidity(10) }, }); const liquidityTokenPayment = await E(liquidityTokenPurseP).withdraw( @@ -473,8 +415,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { payout: aliceRemoveLiquidityPayoutP, } = await E(zoe).redeem( removeLiquidityInvite, - aliceRemoveLiquidityPayoutRules, - harden([undefined, undefined, liquidityTokenPayment]), + aliceRemoveLiquidityProposal, + harden({ Liquidity: liquidityTokenPayment }), ); const removeLiquidityOutcome = await E( @@ -483,7 +425,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(removeLiquidityOutcome); const payout = await aliceRemoveLiquidityPayoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); + const moolaPayout = await payout.TokenA; + const simoleanPayout = await payout.TokenB; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); diff --git a/packages/zoe/test/swingsetTests/zoe/vat-bob.js b/packages/zoe/test/swingsetTests/zoe/vat-bob.js index d0bc6524c4f..cfbb53f3deb 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-bob.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-bob.js @@ -1,7 +1,7 @@ import harden from '@agoric/harden'; import { assert, details } from '@agoric/assert'; import { sameStructure } from '@agoric/same-structure'; -import { showPurseBalance, setupIssuers, getLocalAmountMath } from './helpers'; +import { showPurseBalance, setupIssuers } from './helpers'; const build = async (E, log, zoe, issuers, payments, installations, timer) => { const { @@ -25,9 +25,9 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { extent: [{ instanceHandle }], } = await E(inviteIssuer).getAmountOf(exclInvite); - const { installationHandle, terms } = await E(zoe).getInstance( - instanceHandle, - ); + const { installationHandle, issuerKeywordRecord } = await E( + zoe, + ).getInstance(instanceHandle); // Bob ensures it's the contract he expects assert( @@ -36,36 +36,26 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { ); assert( - terms.issuers[0] === moolaIssuer, + issuerKeywordRecord.Contribution1 === moolaIssuer, details`The first issuer should be the moola issuer`, ); assert( - terms.issuers[1] === simoleanIssuer, + issuerKeywordRecord.Contribution2 === simoleanIssuer, details`The second issuer should be the simolean issuer`, ); // 1. Bob escrows his offer - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(15), - }, - { - kind: 'offerAtMost', - amount: simoleans(17), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobProposal = harden({ + want: { Contribution1: moola(15) }, + give: { Contribution2: simoleans(17) }, + exit: { onDemand: null }, }); - const bobPayments = [undefined, simoleanPayment]; + const bobPayments = { Contribution2: simoleanPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( exclInvite, - bobOfferRules, + bobProposal, bobPayments, ); @@ -75,7 +65,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(outcome); const bobResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(bobResult); + const moolaPayout = await bobResult.Contribution1; + const simoleanPayout = await bobResult.Contribution2; // 5: Bob deposits his winnings await E(moolaPurseP).deposit(moolaPayout); @@ -90,20 +81,9 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { const invite = await inviteP; const exclInvite = await E(inviteIssuer).claim(invite); - const bobIntendedOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobIntendedProposal = harden({ + want: { UnderlyingAsset: moola(3) }, + give: { StrikePrice: simoleans(7) }, }); // Bob checks that the invite is for the right covered call @@ -135,21 +115,23 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { ); assert(optionExtent[0].timerAuthority === timer, 'wrong timer'); + const { UnderlyingAsset, StrikePrice } = instanceInfo.issuerKeywordRecord; + assert( - instanceInfo.terms.issuers[0] === moolaIssuer, - details`The first issuer should be the moola issuer`, + UnderlyingAsset === moolaIssuer, + details`The underlying asset issuer should be the moola issuer`, ); assert( - instanceInfo.terms.issuers[1] === simoleanIssuer, - details`The second issuer should be the simolean issuer`, + StrikePrice === simoleanIssuer, + details`The strike price issuer should be the simolean issuer`, ); - const bobPayments = [undefined, simoleanPayment]; + const bobPayments = { StrikePrice: simoleanPayment }; // Bob escrows const { seat, payout: payoutP } = await E(zoe).redeem( exclInvite, - bobIntendedOfferRules, + bobIntendedProposal, bobPayments, ); @@ -158,7 +140,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(bobOutcome); const bobResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(bobResult); + const moolaPayout = await bobResult.UnderlyingAsset; + const simoleanPayout = await bobResult.StrikePrice; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -199,48 +182,41 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { details`wrong expiration date`, ); assert(optionExtent[0].timerAuthority === timer, details`wrong timer`); + const { UnderlyingAsset, StrikePrice } = instanceInfo.issuerKeywordRecord; assert( - instanceInfo.terms.issuers[0] === moolaIssuer, - details`The first issuer should be the moola issuer`, + UnderlyingAsset === moolaIssuer, + details`The underlyingAsset issuer should be the moola issuer`, ); assert( - instanceInfo.terms.issuers[1] === simoleanIssuer, - details`The second issuer should be the simolean issuer`, + StrikePrice === simoleanIssuer, + details`The strikePrice issuer should be the simolean issuer`, ); // Let's imagine that Bob wants to create a swap to trade this // invite for bucks. He wants to invite Dave as the // counter-party. - const swapIssuers = harden([inviteIssuer, bucksIssuer]); + const issuerKeywordRecord = harden({ + Asset: inviteIssuer, + Price: bucksIssuer, + }); const bobSwapInvite = await E(zoe).makeInstance( installations.atomicSwap, - { issuers: swapIssuers }, + issuerKeywordRecord, ); // Bob wants to swap an invite with the same amount as his // current invite from Alice. He wants 1 buck in return. - const bobOfferRulesSwap = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: optionAmounts, - }, - { - kind: 'wantAtLeast', - amount: bucks(1), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobProposalSwap = harden({ + give: { Asset: optionAmounts }, + want: { Price: bucks(1) }, }); - const bobSwapPayments = [exclInvite, undefined]; + const bobSwapPayments = harden({ Asset: exclInvite }); // Bob escrows his option in the swap const { seat: bobSwapSeat, payout: payoutP } = await E(zoe).redeem( bobSwapInvite, - bobOfferRulesSwap, + bobProposalSwap, bobSwapPayments, ); @@ -250,7 +226,7 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { await E(daveP).doSwapForOption(daveSwapInviteP, optionAmounts); const bobResult = await payoutP; - const [_, bucksPayout] = await Promise.all(bobResult); + const bucksPayout = await bobResult.Price; // Bob deposits his winnings await E(bucksPurseP).deposit(bucksPayout); @@ -266,41 +242,34 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { exclInvite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, issuerKeywordRecord, terms } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.publicAuction, details`wrong installation`, ); assert( - sameStructure(harden([moolaIssuer, simoleanIssuer]), terms.issuers), - details`issuers were not as expected`, + sameStructure( + harden({ Asset: moolaIssuer, Bid: simoleanIssuer }), + issuerKeywordRecord, + ), + details`issuerKeywordRecord was not as expected`, ); + assert(terms.numBidsAllowed === 3, details`terms not as expected`); assert(sameStructure(inviteExtent[0].minimumBid, simoleans(3))); assert(sameStructure(inviteExtent[0].auctionedAssets, moola(1))); - const offerRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(11), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + want: { Asset: moola(1) }, + give: { Bid: simoleans(11) }, }); - const offerPayments = [undefined, simoleanPayment]; + const paymentKeywordRecord = { Bid: simoleanPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( exclInvite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const offerResult = await E(seat).bid(); @@ -308,7 +277,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(offerResult); const bobResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(bobResult); + const moolaPayout = await bobResult.Asset; + const simoleanPayout = await bobResult.Bid; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -323,57 +293,40 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { exclInvite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.atomicSwap, details`wrong installation`, ); assert( - sameStructure(harden([moolaIssuer, simoleanIssuer]), terms.issuers), + sameStructure( + harden({ Asset: moolaIssuer, Price: simoleanIssuer }), + issuerKeywordRecord, + ), details`issuers were not as expected`, ); - const expectedFirstOfferPayoutRules = harden([ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ]); assert( - sameStructure( - inviteExtent[0].offerMadeRules, - expectedFirstOfferPayoutRules, - ), + sameStructure(inviteExtent[0].asset, moola(3)), + details`Alice made a different offer than expected`, + ); + assert( + sameStructure(inviteExtent[0].price, simoleans(7)), details`Alice made a different offer than expected`, ); - const offerRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + want: { Asset: moola(3) }, + give: { Price: simoleans(7) }, }); - const offerPayments = [undefined, simoleanPayment]; + const paymentKeywordRecord = { Price: simoleanPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( exclInvite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const offerResult = await E(seat).matchOffer(); @@ -381,7 +334,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(offerResult); const bobResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(bobResult); + const moolaPayout = await bobResult.Asset; + const simoleanPayout = await bobResult.Price; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -396,39 +350,33 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { exclInvite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.simpleExchange, details`wrong installation`, ); assert( - sameStructure(harden([moolaIssuer, simoleanIssuer]), terms.issuers), - details`issuers were not as expected`, + issuerKeywordRecord.Asset === moolaIssuer, + details`The first issuer should be the moola issuer`, + ); + assert( + issuerKeywordRecord.Price === simoleanIssuer, + details`The second issuer should be the simolean issuer`, ); - const bobBuyOrderOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobBuyOrderProposal = harden({ + want: { Asset: moola(3) }, + give: { Price: simoleans(7) }, + exit: { onDemand: null }, }); - const offerPayments = [undefined, simoleanPayment]; + const paymentKeywordRecord = { Price: simoleanPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( exclInvite, - bobBuyOrderOfferRules, - offerPayments, + bobBuyOrderProposal, + paymentKeywordRecord, ); const offerResult = await E(seat).addOrder(); @@ -436,7 +384,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(offerResult); const bobResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(bobResult); + const moolaPayout = await bobResult.Asset; + const simoleanPayout = await bobResult.Price; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -446,47 +395,38 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { }, doSimpleExchangeUpdates: async (inviteP, m, s) => { const invite = await E(inviteIssuer).claim(inviteP); - const { - extent: [inviteExtent], - } = await E(inviteIssuer).getAmountOf(invite); - - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent.instanceHandle, + const { extent: inviteExtent } = await E(inviteIssuer).getAmountOf( + invite, ); + const { installationHandle, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.simpleExchange, details`wrong installation`, ); assert( - sameStructure(harden([moolaIssuer, simoleanIssuer]), terms.issuers), - details`issuers were not as expected`, + issuerKeywordRecord.Asset === moolaIssuer, + details`The first issuer should be the moola issuer`, ); - - const bobBuyOrderOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(m), - }, - { - kind: 'offerAtMost', - amount: simoleans(s), - }, - ], - exitRule: { - kind: 'onDemand', - }, + assert( + issuerKeywordRecord.Price === simoleanIssuer, + details`The second issuer should be the simolean issuer`, + ); + const bobBuyOrderProposal = harden({ + want: { Asset: moola(m) }, + give: { Price: simoleans(s) }, + exit: { onDemand: null }, }); if (m === 3 && s === 7) { await E(simoleanPurseP).deposit(simoleanPayment); } const simoleanPayment2 = await E(simoleanPurseP).withdraw(simoleans(s)); - const offerPayments = [undefined, simoleanPayment2]; - + const paymentKeywordRecord = { Price: simoleanPayment2 }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - bobBuyOrderOfferRules, - offerPayments, + bobBuyOrderProposal, + paymentKeywordRecord, ); const offerResult = await E(seat).addOrder(); @@ -494,8 +434,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(offerResult); payoutP.then(async bobResult => { - E(moolaPurseP).deposit(await bobResult[0]); - E(simoleanPurseP).deposit(await bobResult[1]); + E(moolaPurseP).deposit(await bobResult.Asset); + E(simoleanPurseP).deposit(await bobResult.Price); }); await showPurseBalance(moolaPurseP, 'bobMoolaPurse', log); @@ -508,9 +448,9 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { exclInvite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.autoswap, details`wrong installation`, @@ -520,42 +460,33 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { } = await E(inviteIssuer).getAmountOf(exclInvite); const { publicAPI } = await E(zoe).getInstance(instanceHandle); const liquidityIssuer = await E(publicAPI).getLiquidityIssuer(); - const liquidityAmountMath = await getLocalAmountMath(liquidityIssuer); - const liquidity = liquidityAmountMath.make; - const allIssuers = harden([moolaIssuer, simoleanIssuer, liquidityIssuer]); assert( - sameStructure(allIssuers, terms.issuers), + sameStructure( + harden({ + TokenA: moolaIssuer, + TokenB: simoleanIssuer, + Liquidity: liquidityIssuer, + }), + issuerKeywordRecord, + ), details`issuers were not as expected`, ); // bob checks the price of 3 moola. The price is 1 simolean - const simoleanAmounts = await E(publicAPI).getPrice(moola(3)); + const simoleanAmounts = await E(publicAPI).getPrice( + harden({ TokenA: moola(3) }), + ); log(`simoleanAmounts `, simoleanAmounts); - const moolaForSimOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(1), - }, - { - kind: 'wantAtLeast', - amount: liquidity(0), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const moolaForSimProposal = harden({ + give: { TokenA: moola(3) }, + want: { TokenB: simoleans(1) }, }); - const moolaForSimPayments = [moolaPayment, undefined, undefined]; + const moolaForSimPayments = harden({ TokenA: moolaPayment }); const { seat, payout: moolaForSimPayoutP } = await E(zoe).redeem( exclInvite, - moolaForSimOfferRules, + moolaForSimProposal, moolaForSimPayments, ); @@ -564,53 +495,38 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(offerResult); const moolaForSimPayout = await moolaForSimPayoutP; - const [moolaPayout1, simoleanPayout1] = await Promise.all( - moolaForSimPayout, - ); + const moolaPayout1 = await moolaForSimPayout.TokenA; + const simoleanPayout1 = await moolaForSimPayout.TokenB; await E(moolaPurseP).deposit(moolaPayout1); await E(simoleanPurseP).deposit(simoleanPayout1); // Bob looks up the price of 3 simoleans. It's 5 moola - const moolaAmounts = await E(publicAPI).getPrice(simoleans(3)); + const moolaAmounts = await E(publicAPI).getPrice( + harden({ TokenB: simoleans(3) }), + ); log(`moolaAmounts `, moolaAmounts); // Bob makes another offer and swaps - const bobSimsForMoolaOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(5), - }, - { - kind: 'offerAtMost', - amount: simoleans(3), - }, - { - kind: 'wantAtLeast', - amount: liquidity(0), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobSimsForMoolaProposal = harden({ + want: { TokenA: moola(5) }, + give: { TokenB: simoleans(3) }, }); await E(simoleanPurseP).deposit(simoleanPayment); const bobSimoleanPayment = await E(simoleanPurseP).withdraw(simoleans(3)); - const simsForMoolaPayments = [undefined, bobSimoleanPayment, undefined]; + const simsForMoolaPayments = harden({ TokenB: bobSimoleanPayment }); const invite2 = await E(publicAPI).makeInvite(); const { seat: seat2, payout: bobSimsForMoolaPayoutP } = await E( zoe, - ).redeem(invite2, bobSimsForMoolaOfferRules, simsForMoolaPayments); + ).redeem(invite2, bobSimsForMoolaProposal, simsForMoolaPayments); const simsForMoolaOutcome = await E(seat2).swap(); log(simsForMoolaOutcome); const simsForMoolaPayout = await bobSimsForMoolaPayoutP; - const [moolaPayout2, simoleanPayout2] = await Promise.all( - simsForMoolaPayout, - ); + const moolaPayout2 = await simsForMoolaPayout.TokenA; + const simoleanPayout2 = await simsForMoolaPayout.TokenB; await E(moolaPurseP).deposit(moolaPayout2); await E(simoleanPurseP).deposit(simoleanPayout2); diff --git a/packages/zoe/test/swingsetTests/zoe/vat-carol.js b/packages/zoe/test/swingsetTests/zoe/vat-carol.js index 581cbacb7e0..d078092f8f5 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-carol.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-carol.js @@ -17,41 +17,35 @@ const build = async (E, log, zoe, issuers, payments, installations) => { invite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, terms, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.publicAuction, details`wrong installation`, ); assert( - sameStructure(harden([moolaIssuer, simoleanIssuer]), terms.issuers), - details`issuers were not as expected`, + sameStructure( + harden({ Asset: moolaIssuer, Bid: simoleanIssuer }), + issuerKeywordRecord, + ), + details`issuerKeywordRecord were not as expected`, ); + assert(terms.numBidsAllowed === 3, details`terms not as expected`); assert(sameStructure(inviteExtent[0].minimumBid, simoleans(3))); assert(sameStructure(inviteExtent[0].auctionedAssets, moola(1))); - const offerRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + want: { Asset: moola(1) }, + give: { Bid: simoleans(7) }, + exit: { onDemand: null }, }); - const offerPayments = [undefined, simoleanPayment]; + const paymentKeywordRecord = { Bid: simoleanPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( invite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const offerResult = await E(seat).bid(); @@ -59,7 +53,8 @@ const build = async (E, log, zoe, issuers, payments, installations) => { log(offerResult); const carolResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(carolResult); + const moolaPayout = await carolResult.Asset; + const simoleanPayout = await carolResult.Bid; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); diff --git a/packages/zoe/test/swingsetTests/zoe/vat-dave.js b/packages/zoe/test/swingsetTests/zoe/vat-dave.js index 395f26f8af8..588e21ddd33 100644 --- a/packages/zoe/test/swingsetTests/zoe/vat-dave.js +++ b/packages/zoe/test/swingsetTests/zoe/vat-dave.js @@ -25,41 +25,35 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { exclInvite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, terms, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.publicAuction, details`wrong installation`, ); assert( - sameStructure(harden([moolaIssuer, simoleanIssuer]), terms.issuers), - details`issuers were not as expected`, + sameStructure( + harden({ Asset: moolaIssuer, Bid: simoleanIssuer }), + issuerKeywordRecord, + ), + details`issuerKeywordRecord were not as expected`, ); + assert(terms.numBidsAllowed === 3, details`terms not as expected`); assert(sameStructure(inviteExtent[0].minimumBid, simoleans(3))); assert(sameStructure(inviteExtent[0].auctionedAssets, moola(1))); - const offerRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(5), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const proposal = harden({ + want: { Asset: moola(1) }, + give: { Bid: simoleans(5) }, + exit: { onDemand: null }, }); - const offerPayments = [undefined, simoleanPayment]; + const paymentKeywordRecord = { Bid: simoleanPayment }; const { seat, payout: payoutP } = await E(zoe).redeem( exclInvite, - offerRules, - offerPayments, + proposal, + paymentKeywordRecord, ); const offerResult = await E(seat).bid(); @@ -67,7 +61,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(offerResult); const daveResult = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(daveResult); + const moolaPayout = await daveResult.Asset; + const simoleanPayout = await daveResult.Bid; await E(moolaPurseP).deposit(moolaPayout); await E(simoleanPurseP).deposit(simoleanPayout); @@ -78,38 +73,35 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { doSwapForOption: async (inviteP, optionAmounts) => { // Dave is looking to buy the option to trade his 7 simoleans for // 3 moola, and is willing to pay 1 buck for the option. - const invite = await inviteP; const exclInvite = await E(inviteIssuer).claim(invite); const { extent: inviteExtent } = await E(inviteIssuer).getAmountOf( exclInvite, ); - const { installationHandle, terms } = await E(zoe).getInstance( - inviteExtent[0].instanceHandle, - ); + const { installationHandle, issuerKeywordRecord } = await E( + zoe, + ).getInstance(inviteExtent[0].instanceHandle); assert( installationHandle === installations.atomicSwap, details`wrong installation`, ); assert( - sameStructure(harden([inviteIssuer, bucksIssuer]), terms.issuers), - details`issuers were not as expected`, + sameStructure( + harden({ Asset: inviteIssuer, Price: bucksIssuer }), + issuerKeywordRecord, + ), + details`issuerKeywordRecord were not as expected`, ); // Dave expects that Bob has already made an offer in the swap // with the following rules: - const expectedBobPayoutRules = harden([ - { - kind: 'offerAtMost', - amount: optionAmounts, - }, - { - kind: 'wantAtLeast', - amount: bucks(1), - }, - ]); assert( - sameStructure(inviteExtent[0].offerMadeRules, expectedBobPayoutRules), + sameStructure(inviteExtent[0].asset, optionAmounts), + details`asset is the option`, + ); + assert( + sameStructure(inviteExtent[0].price, bucks(1)), + details`price is 1 buck`, ); const optionExtent = optionAmounts.extent; assert( @@ -130,58 +122,37 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { ); assert(optionExtent[0].timerAuthority === timer, details`wrong timer`); - // Dave escrows his 1 buck with Zoe and forms his offerRules - const daveSwapOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: optionAmounts, - }, - { - kind: 'offerAtMost', - amount: bucks(1), - }, - ], - exitRule: { - kind: 'onDemand', - }, + // Dave escrows his 1 buck with Zoe and forms his proposal + const daveSwapProposal = harden({ + want: { Asset: optionAmounts }, + give: { Price: bucks(1) }, }); - const daveSwapPayments = [undefined, bucksPayment]; + const daveSwapPayments = harden({ Price: bucksPayment }); const { seat: daveSwapSeat, payout: daveSwapPayoutP } = await E( zoe, - ).redeem(exclInvite, daveSwapOfferRules, daveSwapPayments); + ).redeem(exclInvite, daveSwapProposal, daveSwapPayments); const daveSwapOutcome = await E(daveSwapSeat).matchOffer(); log(daveSwapOutcome); const daveSwapPayout = await daveSwapPayoutP; - const [daveOption, daveBucksPayout] = await Promise.all(daveSwapPayout); + const daveOption = await daveSwapPayout.Asset; + const daveBucksPayout = await daveSwapPayout.Price; // Dave exercises his option by making an offer to the covered // call. First, he escrows with Zoe. - const daveCoveredCallOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const daveCoveredCallProposal = harden({ + want: { UnderlyingAsset: moola(3) }, + give: { StrikePrice: simoleans(7) }, }); - const daveCoveredCallPayments = [undefined, simoleanPayment]; + const daveCoveredCallPayments = harden({ StrikePrice: simoleanPayment }); const { seat: daveCoveredCallSeat, payout: daveCoveredCallPayoutP, } = await E(zoe).redeem( daveOption, - daveCoveredCallOfferRules, + daveCoveredCallProposal, daveCoveredCallPayments, ); @@ -189,9 +160,8 @@ const build = async (E, log, zoe, issuers, payments, installations, timer) => { log(daveCoveredCallOutcome); const daveCoveredCallResult = await daveCoveredCallPayoutP; - const [moolaPayout, simoleanPayout] = await Promise.all( - daveCoveredCallResult, - ); + const moolaPayout = await daveCoveredCallResult.UnderlyingAsset; + const simoleanPayout = await daveCoveredCallResult.StrikePrice; await E(bucksPurseP).deposit(daveBucksPayout); await E(moolaPurseP).deposit(moolaPayout); diff --git a/packages/zoe/test/unitTests/contracts/helpers/test-bondingCurves.js b/packages/zoe/test/unitTests/contracts/helpers/test-bondingCurves.js index 0d59d3c7f12..5f0380f774c 100644 --- a/packages/zoe/test/unitTests/contracts/helpers/test-bondingCurves.js +++ b/packages/zoe/test/unitTests/contracts/helpers/test-bondingCurves.js @@ -3,131 +3,108 @@ import { test } from 'tape-promise/tape'; import harden from '@agoric/harden'; import { makeConstProductBC } from '../../../../src/contracts/helpers/bondingCurves'; -import { setup } from '../../setupBasicMints'; -const testGetPrice = (t, input, output) => { - const { issuers, moola, simoleans, amountMaths, brands } = setup(); - const zoe = harden({ - getAmountMathForIssuers: _ => amountMaths, - getBrandsForIssuers: _ => brands, - }); +const testGetPrice = (t, input, expectedOutput) => { + const zoe = harden({}); + const { getPrice } = makeConstProductBC(zoe); - const { getPrice } = makeConstProductBC(zoe, issuers); - const poolAmountsArray = [moola(input.xReserve), simoleans(input.yReserve)]; - let amountIn; - let expectedAmountsOut; - if (input.xIn > 0) { - amountIn = moola(input.xIn); - expectedAmountsOut = simoleans(output.yOut); - } else { - amountIn = simoleans(input.yIn); - expectedAmountsOut = moola(output.xOut); - } - - const { amountOut, newPoolAmountsArray } = getPrice( - poolAmountsArray, - amountIn, - ); - - t.deepEquals(amountOut, expectedAmountsOut, 'amountOut'); - t.deepEquals( - newPoolAmountsArray, - [moola(output.xReserve), simoleans(output.yReserve)], - 'newPoolAmountsArray', - ); + const output = getPrice(input); + t.deepEquals(output, expectedOutput); }; test('getPrice ok 1', t => { + t.plan(1); try { const input = { - xReserve: 0, - yReserve: 0, - xIn: 1, - yIn: 0, + inputReserve: 0, + outputReserve: 0, + inputExtent: 1, }; - const expectedOutput1 = { - xReserve: 1, - yReserve: 0, - xOut: 0, - yOut: 0, + const expectedOutput = { + outputExtent: 0, + newInputReserve: 1, + newOutputReserve: 0, }; - testGetPrice(t, input, expectedOutput1); + testGetPrice(t, input, expectedOutput); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); test('getPrice ok 2', t => { + t.plan(1); try { const input = { - xReserve: 5984, - yReserve: 3028, - xIn: 1398, - yIn: 0, + inputReserve: 5984, + outputReserve: 3028, + inputExtent: 1398, }; - const expectedOutput1 = { - xReserve: 7382, - yReserve: 2456, - xOut: 0, - yOut: 572, + const expectedOutput = { + outputExtent: 572, + newInputReserve: 7382, + newOutputReserve: 2456, }; - testGetPrice(t, input, expectedOutput1); + testGetPrice(t, input, expectedOutput); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); test('getPrice ok 3', t => { + t.plan(1); try { - const input = { xReserve: 8160, yReserve: 7743, xIn: 6635, yIn: 0 }; - const expectedOutput1 = { - xReserve: 14795, - yReserve: 4277, - xOut: 0, - yOut: 3466, + const input = { + inputReserve: 8160, + outputReserve: 7743, + inputExtent: 6635, + }; + const expectedOutput = { + outputExtent: 3466, + newInputReserve: 14795, + newOutputReserve: 4277, }; - testGetPrice(t, input, expectedOutput1); + testGetPrice(t, input, expectedOutput); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); test('getPrice reverse x and y amounts', t => { + t.plan(1); try { - const input = { xReserve: 7743, yReserve: 8160, xIn: 0, yIn: 6635 }; - const expectedOutput1 = { - xReserve: 4277, - yReserve: 14795, - xOut: 3466, - yOut: 0, + // Note: this is now the same test as the one above because we are + // only using extents + const input = { + inputReserve: 8160, + outputReserve: 7743, + inputExtent: 6635, + }; + const expectedOutput = { + outputExtent: 3466, + newInputReserve: 14795, + newOutputReserve: 4277, }; - testGetPrice(t, input, expectedOutput1); + testGetPrice(t, input, expectedOutput); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); test('getPrice ok 4', t => { + t.plan(1); try { - const input = { xReserve: 10, yReserve: 10, xIn: 0, yIn: 1000 }; - const expectedOutput1 = { - xReserve: 1, - yReserve: 1010, - xOut: 9, - yOut: 0, + const input = { + inputReserve: 10, + outputReserve: 10, + inputExtent: 1000, + }; + const expectedOutput = { + outputExtent: 9, + newInputReserve: 1010, + newOutputReserve: 1, }; - testGetPrice(t, input, expectedOutput1); + testGetPrice(t, input, expectedOutput); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); diff --git a/packages/zoe/test/unitTests/contracts/helpers/test-stateMachine.js b/packages/zoe/test/unitTests/contracts/helpers/test-stateMachine.js index 631bc3911ba..e4a0cb3104f 100644 --- a/packages/zoe/test/unitTests/contracts/helpers/test-stateMachine.js +++ b/packages/zoe/test/unitTests/contracts/helpers/test-stateMachine.js @@ -4,6 +4,7 @@ import { test } from 'tape-promise/tape'; import { makeStateMachine } from '../../../../src/contracts/helpers/stateMachine'; test('stateMachine', t => { + t.plan(4); try { const startState = 'empty'; const allowedTransitions = [ @@ -22,7 +23,5 @@ test('stateMachine', t => { t.equal(stateMachine.getStatus(), 'open'); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); diff --git a/packages/zoe/test/unitTests/contracts/helpers/test-zoeHelpers.js b/packages/zoe/test/unitTests/contracts/helpers/test-zoeHelpers.js new file mode 100644 index 00000000000..38e72d323e4 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/helpers/test-zoeHelpers.js @@ -0,0 +1,662 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from 'tape-promise/tape'; +import harden from '@agoric/harden'; + +import { setup } from '../../setupBasicMints2'; +import { + makeZoeHelpers, + defaultAcceptanceMsg, + defaultRejectMsg, +} from '../../../../src/contracts/helpers/zoeHelpers'; + +test('ZoeHelpers messages', t => { + t.plan(2); + try { + t.equals( + defaultAcceptanceMsg, + `The offer has been accepted. Once the contract has been completed, please check your payout`, + ); + t.equals( + defaultRejectMsg, + `The offer was invalid. Please check your refund.`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers assertKeywords', t => { + t.plan(5); + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + keywords: ['Asset', 'Price'], + }), + getAmountMaths: () => {}, + getZoeService: () => {}, + }); + const { assertKeywords } = makeZoeHelpers(mockZoe); + t.doesNotThrow( + () => assertKeywords(['Asset', 'Price']), + `Asset and Price are the correct keywords`, + ); + t.doesNotThrow( + () => assertKeywords(['Price', 'Asset']), + `Order doesn't matter`, + ); + t.throws( + () => assertKeywords(['TokenA', 'TokenB']), + /were not as expected/, + `The wrong keywords will throw`, + ); + t.throws( + () => assertKeywords(['Asset', 'Price', 'Price2']), + /were not as expected/, + `An extra keyword will throw`, + ); + t.throws( + () => assertKeywords(['Asset']), + /were not as expected/, + `a missing keyword will throw`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers rejectIfNotProposal', t => { + t.plan(8); + const { moolaR, simoleanR, moola, simoleans } = setup(); + const completedOfferHandles = []; + const offerHandles = harden([{}, {}, {}, {}, {}, {}, {}]); + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + }), + getAmountMaths: () => {}, + getZoeService: () => {}, + getOffer: handle => { + if (offerHandles.indexOf(handle) === 4) { + return harden({ + proposal: { + want: { Asset: moola(4) }, + give: { Price: simoleans(16) }, + exit: { Waived: null }, + }, + }); + } + if (offerHandles.indexOf(handle) === 5) { + return harden({ + proposal: { + want: { Asset2: moola(4) }, + give: { Price: simoleans(16) }, + exit: { waived: null }, + }, + }); + } + return harden({ + proposal: { + want: { Asset: moola(4) }, + give: { Price: simoleans(16) }, + exit: { onDemand: null }, + }, + }); + }, + complete: handles => completedOfferHandles.push(...handles), + }); + const { rejectIfNotProposal } = makeZoeHelpers(mockZoe); + // Vary expected. + t.doesNotThrow(() => + rejectIfNotProposal( + offerHandles[0], + harden({ want: ['Asset'], give: ['Price'] }), + ), + ); + t.throws( + () => + rejectIfNotProposal( + offerHandles[1], + harden({ want: ['Assets'], give: ['Price'] }), + ), + /The offer was invalid. Please check your refund./, + `had the wrong wants`, + ); + t.throws( + () => + rejectIfNotProposal( + offerHandles[2], + harden({ want: ['Asset'], give: ['Price2'] }), + ), + /The offer was invalid. Please check your refund./, + `had the wrong offer`, + ); + t.throws( + () => + rejectIfNotProposal( + offerHandles[3], + harden({ want: ['Asset'], give: ['Price'], exit: ['Waived'] }), + ), + /The offer was invalid. Please check your refund./, + `had the wrong exit rule`, + ); + t.deepEquals( + completedOfferHandles, + [offerHandles[1], offerHandles[2], offerHandles[3]], + `offers 1, 2, 3, (zero-indexed) should be completed`, + ); + + // Now vary the offer. + t.throws( + () => + rejectIfNotProposal( + offerHandles[4], + harden({ want: ['Asset'], give: ['Price'], exit: ['waived'] }), + ), + /The offer was invalid. Please check your refund./, + `had the wrong exit rule`, + ); + t.throws( + () => + rejectIfNotProposal( + offerHandles[5], + harden({ want: ['Asset'], give: ['Price'], exit: ['waived'] }), + ), + /The offer was invalid. Please check your refund./, + `had the wrong want`, + ); + t.deepEquals( + completedOfferHandles, + [ + offerHandles[1], + offerHandles[2], + offerHandles[3], + offerHandles[4], + offerHandles[5], + ], + `offers 1, 2, 3, 4, and 5 (zero indexed) should be completed`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers checkIfProposal', t => { + t.plan(3); + const { moolaR, simoleanR, moola, simoleans } = setup(); + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + }), + getAmountMaths: () => {}, + getZoeService: () => {}, + getOffer: _handle => { + return harden({ + proposal: { + want: { Asset: moola(4) }, + give: { Price: simoleans(16) }, + exit: { onDemand: null }, + }, + }); + }, + }); + const { checkIfProposal } = makeZoeHelpers(mockZoe); + t.ok( + checkIfProposal( + harden({}), + harden({ want: ['Asset'], give: ['Price'], exit: ['onDemand'] }), + ), + `want, give, and exit match expected`, + ); + t.notOk( + checkIfProposal( + harden({}), + harden({ want: ['Asset2'], give: ['Price'] }), + ), + `want was not as expected`, + ); + t.ok( + checkIfProposal(harden({}), harden({})), + `having no expectations passes trivially`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers getActiveOffers', t => { + t.plan(1); + const { moolaR, simoleanR } = setup(); + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + }), + getAmountMaths: () => {}, + getZoeService: () => {}, + getOfferStatuses: handles => { + const [firstHandle, restHandles] = handles; + return harden({ + active: [firstHandle], + inactive: restHandles, + }); + }, + getOffers: handles => + handles.map((handle, i) => harden({ handle, id: i })), + }); + const { getActiveOffers } = makeZoeHelpers(mockZoe); + const offerHandles = harden([{}, {}]); + t.deepEquals( + getActiveOffers(offerHandles), + harden([{ handle: offerHandles[0], id: 0 }]), + `active offers gotten`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers rejectOffer', t => { + t.plan(4); + const { moolaR, simoleanR } = setup(); + const completedOfferHandles = []; + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + }), + getAmountMaths: () => {}, + getZoeService: () => {}, + complete: handles => completedOfferHandles.push(...handles), + }); + const { rejectOffer } = makeZoeHelpers(mockZoe); + const offerHandles = harden([{}, {}]); + t.throws( + () => rejectOffer(offerHandles[0]), + /Error: The offer was invalid. Please check your refund./, + `rejectOffer intentionally throws`, + ); + t.deepEquals(completedOfferHandles, harden([offerHandles[0]])); + t.throws( + () => rejectOffer(offerHandles[1], 'offer was wrong'), + /Error: offer was wrong/, + `rejectOffer throws with custom msg`, + ); + t.deepEquals(completedOfferHandles, harden(offerHandles)); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers canTradeWith', t => { + t.plan(2); + const { moolaR, simoleanR, moola, simoleans } = setup(); + const leftInviteHandle = harden({}); + const rightInviteHandle = harden({}); + const cantTradeRightInviteHandle = harden({}); + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + keywords: ['Asset', 'Price'], + }), + getAmountMaths: () => + harden({ Asset: moolaR.amountMath, Price: simoleanR.amountMath }), + getZoeService: () => {}, + getOffer: handle => { + if (handle === leftInviteHandle) { + return harden({ + proposal: { + give: { Asset: moola(10) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, + }, + }); + } + if (handle === rightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(7) }, + exit: { onDemand: null }, + }, + }); + } + if (handle === cantTradeRightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(100) }, + exit: { onDemand: null }, + }, + }); + } + throw new Error('unexpected handle'); + }, + }); + const { canTradeWith } = makeZoeHelpers(mockZoe); + t.ok(canTradeWith(leftInviteHandle, rightInviteHandle)); + t.notOk(canTradeWith(leftInviteHandle, cantTradeRightInviteHandle)); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers swap ok', t => { + t.plan(4); + const { moolaR, simoleanR, moola, simoleans } = setup(); + const leftInviteHandle = harden({}); + const rightInviteHandle = harden({}); + const cantTradeRightInviteHandle = harden({}); + const reallocatedHandles = []; + const reallocatedAmountObjs = []; + const completedHandles = []; + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + keywords: ['Asset', 'Price'], + }), + getAmountMaths: () => + harden({ Asset: moolaR.amountMath, Price: simoleanR.amountMath }), + getZoeService: () => {}, + isOfferActive: () => true, + getOffer: handle => { + if (handle === leftInviteHandle) { + return harden({ + proposal: { + give: { Asset: moola(10) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, + }, + amounts: 'leftInviteAmounts', + }); + } + if (handle === rightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(7) }, + exit: { onDemand: null }, + }, + amounts: 'rightInviteAmounts', + }); + } + if (handle === cantTradeRightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(100) }, + exit: { onDemand: null }, + }, + amounts: 'cantTradeRightInviteAmounts', + }); + } + throw new Error('unexpected handle'); + }, + reallocate: (handles, amountObjs) => { + reallocatedHandles.push(...handles); + reallocatedAmountObjs.push(...amountObjs); + }, + complete: handles => completedHandles.push(...handles), + }); + const { swap } = makeZoeHelpers(mockZoe); + t.ok( + swap( + leftInviteHandle, + rightInviteHandle, + 'prior offer no longer available', + ), + ); + t.deepEquals( + reallocatedHandles, + harden([leftInviteHandle, rightInviteHandle]), + `both handles reallocated`, + ); + t.deepEquals( + reallocatedAmountObjs, + harden(['rightInviteAmounts', 'leftInviteAmounts']), + `amounts reallocated passed to reallocate were as expected`, + ); + t.deepEquals( + completedHandles, + harden([leftInviteHandle, rightInviteHandle]), + `both handles were completed`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers swap keep inactive', t => { + t.plan(4); + const { moolaR, simoleanR, moola, simoleans } = setup(); + const leftInviteHandle = harden({}); + const rightInviteHandle = harden({}); + const cantTradeRightInviteHandle = harden({}); + const reallocatedHandles = []; + const reallocatedAmountObjs = []; + const completedHandles = []; + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + keywords: ['Asset', 'Price'], + }), + getAmountMaths: () => + harden({ Asset: moolaR.amountMath, Price: simoleanR.amountMath }), + getZoeService: () => {}, + isOfferActive: () => false, + getOffer: handle => { + if (handle === leftInviteHandle) { + return harden({ + proposal: { + give: { Asset: moola(10) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, + }, + amounts: 'leftInviteAmounts', + }); + } + if (handle === rightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(7) }, + exit: { onDemand: null }, + }, + amounts: 'rightInviteAmounts', + }); + } + if (handle === cantTradeRightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(100) }, + exit: { onDemand: null }, + }, + amounts: 'cantTradeRightInviteAmounts', + }); + } + throw new Error('unexpected handle'); + }, + reallocate: (handles, amountObjs) => { + reallocatedHandles.push(...handles); + reallocatedAmountObjs.push(...amountObjs); + }, + complete: handles => handles.map(handle => completedHandles.push(handle)), + }); + const { swap } = makeZoeHelpers(mockZoe); + t.throws( + () => + swap( + leftInviteHandle, + rightInviteHandle, + 'prior offer no longer available', + ), + /Error: prior offer no longer available/, + `throws if keepHandle offer is not active`, + ); + t.deepEquals(reallocatedHandles, harden([]), `nothing reallocated`); + t.deepEquals(reallocatedAmountObjs, harden([]), `no amounts reallocated`); + t.deepEquals( + completedHandles, + harden([rightInviteHandle]), + `only tryHandle (right) was completed`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test(`ZoeHelpers swap - can't trade with`, t => { + t.plan(4); + const { moolaR, simoleanR, moola, simoleans } = setup(); + const leftInviteHandle = harden({}); + const rightInviteHandle = harden({}); + const cantTradeRightInviteHandle = harden({}); + const reallocatedHandles = []; + const reallocatedAmountObjs = []; + const completedHandles = []; + try { + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + keywords: ['Asset', 'Price'], + }), + getAmountMaths: () => + harden({ Asset: moolaR.amountMath, Price: simoleanR.amountMath }), + getZoeService: () => {}, + isOfferActive: () => true, + getOffer: handle => { + if (handle === leftInviteHandle) { + return harden({ + proposal: { + give: { Asset: moola(10) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, + }, + amounts: 'leftInviteAmounts', + }); + } + if (handle === rightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(7) }, + exit: { onDemand: null }, + }, + amounts: 'rightInviteAmounts', + }); + } + if (handle === cantTradeRightInviteHandle) { + return harden({ + proposal: { + give: { Price: simoleans(6) }, + want: { Asset: moola(100) }, + exit: { onDemand: null }, + }, + amounts: 'cantTradeRightInviteAmounts', + }); + } + throw new Error('unexpected handle'); + }, + reallocate: (handles, amountObjs) => { + reallocatedHandles.push(...handles); + reallocatedAmountObjs.push(...amountObjs); + }, + complete: handles => handles.map(handle => completedHandles.push(handle)), + }); + const { swap } = makeZoeHelpers(mockZoe); + t.throws( + () => + swap( + leftInviteHandle, + cantTradeRightInviteHandle, + 'prior offer no longer available', + ), + /Error: The offer was invalid. Please check your refund./, + `throws if can't trade with left and right`, + ); + t.deepEquals(reallocatedHandles, harden([]), `nothing reallocated`); + t.deepEquals(reallocatedAmountObjs, harden([]), `no amounts reallocated`); + t.deepEquals( + completedHandles, + harden([rightInviteHandle]), + `only tryHandle (right) was completed`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('ZoeHelpers makeEmptyOffer', async t => { + t.plan(2); + const { moolaR, simoleanR } = setup(); + const redeemedInvites = []; + try { + const inviteHandle = harden({}); + const mockZoe = harden({ + getInstanceRecord: () => + harden({ + issuerKeywordRecord: { + Asset: moolaR.issuer, + Price: simoleanR.issuer, + }, + }), + getAmountMaths: () => {}, + getZoeService: () => + harden({ + redeem: invite => { + redeemedInvites.push(invite); + return Promise.resolve(); + }, + }), + makeInvite: () => + harden({ + inviteHandle, + invite: 'anInvite', + }), + }); + const { makeEmptyOffer } = makeZoeHelpers(mockZoe); + const result = await makeEmptyOffer(); + t.deepEquals(result, inviteHandle, `inviteHandle was returned`); + t.deepEquals(redeemedInvites, harden(['anInvite']), `invite was redeemed`); + } catch (e) { + t.assert(false, e); + } +}); diff --git a/packages/zoe/test/unitTests/contracts/test-publicSwap.js b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js similarity index 62% rename from packages/zoe/test/unitTests/contracts/test-publicSwap.js rename to packages/zoe/test/unitTests/contracts/test-atomicSwap.js index c6346c43292..2dc7ce4cff4 100644 --- a/packages/zoe/test/unitTests/contracts/test-publicSwap.js +++ b/packages/zoe/test/unitTests/contracts/test-atomicSwap.js @@ -11,12 +11,13 @@ import { setup } from '../setupBasicMints'; const atomicSwapRoot = `${__dirname}/../../../src/contracts/atomicSwap`; test('zoe - atomicSwap', async t => { + t.plan(11); try { - const { issuers: defaultIssuers, mints, moola, simoleans } = setup(); - const issuers = defaultIssuers.slice(0, 2); + const { issuers, mints, moola, simoleans } = setup(); const zoe = makeZoe({ require }); const inviteIssuer = zoe.getInviteIssuer(); const [moolaIssuer, simoleanIssuer] = issuers; + const [moolaMint, simoleanMint] = mints; // pack the contract const { source, moduleFormat } = await bundleSource(atomicSwapRoot); @@ -24,42 +25,37 @@ test('zoe - atomicSwap', async t => { const installationHandle = zoe.install(source, moduleFormat); // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(moola(3)); + const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); // Setup Bob - const bobSimoleanPayment = mints[1].mintPayment(simoleans(7)); + const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); // 1: Alice creates an atomicSwap instance - const aliceInvite = await zoe.makeInstance(installationHandle, { - issuers, + const issuerKeywordRecord = harden({ + Asset: moolaIssuer, + Price: simoleanIssuer, }); + const aliceInvite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); // 2: Alice escrows with zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const aliceProposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(7) }, + exit: { onDemand: null }, }); - const alicePayments = [aliceMoolaPayment, undefined]; + const alicePayments = { Asset: aliceMoolaPayment }; // 3: Alice redeems her invite and escrows with Zoe const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( aliceInvite, - aliceOfferRules, + aliceProposal, alicePayments, ); @@ -71,39 +67,31 @@ test('zoe - atomicSwap', async t => { // counter-party. const bobExclusiveInvite = await inviteIssuer.claim(bobInviteP); - const bobExclAmount = await inviteIssuer.getAmountOf(bobExclusiveInvite); - const bobInviteExtent = bobExclAmount.extent[0]; + const { + extent: [bobInviteExtent], + } = await inviteIssuer.getAmountOf(bobExclusiveInvite); const { installationHandle: bobInstallationId, - terms: bobTerms, + issuerKeywordRecord: bobIssuers, } = zoe.getInstance(bobInviteExtent.instanceHandle); - t.equals(bobInstallationId, installationHandle); - t.deepEquals(bobTerms.issuers, issuers); - t.deepEquals(bobInviteExtent.offerMadeRules, aliceOfferRules.payoutRules); - - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); + t.deepEquals(bobIssuers, { Asset: moolaIssuer, Price: simoleanIssuer }); + t.deepEquals(bobInviteExtent.asset, moola(3)); + t.deepEquals(bobInviteExtent.price, simoleans(7)); + + const bobProposal = harden({ + give: { Price: simoleans(7) }, + want: { Asset: moola(3) }, + exit: { onDemand: null }, }); - const bobPayments = [undefined, bobSimoleanPayment]; + const bobPayments = { Price: bobSimoleanPayment }; // 6: Bob escrows with zoe const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( bobExclusiveInvite, - bobOfferRules, + bobProposal, bobPayments, ); @@ -117,18 +105,20 @@ test('zoe - atomicSwap', async t => { const bobPayout = await bobPayoutP; const alicePayout = await alicePayoutP; - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobPayout); - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - alicePayout, - ); + 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 - const aliceSimAmt = await simoleanIssuer.getAmountOf(aliceSimoleanPayout); - t.deepEquals(aliceSimAmt, aliceOfferRules.payoutRules[1].amount); + t.deepEquals( + await simoleanIssuer.getAmountOf(aliceSimoleanPayout), + aliceProposal.want.Price, + ); // Alice didn't get any of what Alice put in - const aliceMoolaAmount = await moolaIssuer.getAmountOf(aliceMoolaPayout); - t.deepEquals(aliceMoolaAmount, moola(0)); + t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); // Alice deposits her payout to ensure she can await aliceMoolaPurse.deposit(aliceMoolaPayout); @@ -148,7 +138,5 @@ test('zoe - atomicSwap', async t => { } catch (e) { t.assert(false, e); console.log(e); - } finally { - t.end(); } }); diff --git a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js index 7f342694afb..a0472c10fc7 100644 --- a/packages/zoe/test/unitTests/contracts/test-automaticRefund.js +++ b/packages/zoe/test/unitTests/contracts/test-automaticRefund.js @@ -6,454 +6,440 @@ import bundleSource from '@agoric/bundle-source'; import harden from '@agoric/harden'; import { makeZoe } from '../../../src/zoe'; -import { setup } from '../setupBasicMints'; +// TODO: Remove setupBasicMints and rename setupBasicMints2 +import { setup } from '../setupBasicMints2'; const automaticRefundRoot = `${__dirname}/../../../src/contracts/automaticRefund`; test('zoe - simplest automaticRefund', async t => { t.plan(1); + try { + // Setup zoe and mints + const { moolaR, moola } = setup(); + const zoe = makeZoe({ require }); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(automaticRefundRoot); + const installationHandle = zoe.install(source, moduleFormat); + + // 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, + issuerKeywordRecord, + ); + + const aliceProposal = harden({ + give: { Contribution: moola(3) }, + exit: { onDemand: null }, + }); + const alicePayments = { Contribution: aliceMoolaPayment }; + + const { seat, payout: payoutP } = await zoe.redeem( + invite, + aliceProposal, + alicePayments, + ); + + seat.makeOffer(); + const alicePayout = await payoutP; + const aliceMoolaPayout = await alicePayout.Contribution; - // Setup zoe and mints - const { issuers, mints, moola } = setup(); - const [moolaIssuer] = issuers; - const [moolaMint] = mints; - const zoe = makeZoe({ require }); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(automaticRefundRoot); - const installationHandle = zoe.install(source, moduleFormat); - - // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - - // 1: Alice creates an automatic refund instance - const invite = await zoe.makeInstance(installationHandle, { - issuers: harden([moolaIssuer]), - }); - - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const alicePayments = [aliceMoolaPayment]; - - const { seat, payout: payoutP } = await zoe.redeem( - invite, - aliceOfferRules, - alicePayments, - ); - - seat.makeOffer(); - const alicePayout = await payoutP; - const aliceMoolaPayout = await alicePayout[0]; - - // Alice got back what she put in - moolaIssuer.getAmountOf(aliceMoolaPayout).then(moolaAmount => { - t.deepEquals(moolaAmount, aliceOfferRules.payoutRules[0].amount); - }); + // Alice got back what she put in + t.deepEquals( + await moolaR.issuer.getAmountOf(aliceMoolaPayout), + aliceProposal.give.Contribution, + ); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); test('zoe - automaticRefund same issuer', async t => { t.plan(1); + try { + // Setup zoe and mints + const { moolaR, moola } = setup(); + const zoe = makeZoe({ require }); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(automaticRefundRoot); + const installationHandle = zoe.install(source, moduleFormat); + + // Setup Alice + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(9)); + + // 1: Alice creates an automatic refund instance + const issuerKeywordRecord = harden({ + Contribution1: moolaR.issuer, + Contribution2: moolaR.issuer, + }); + const invite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + + const aliceProposal = harden({ + give: { Contribution2: moola(9) }, + exit: { onDemand: null }, + }); + const alicePayments = harden({ Contribution2: aliceMoolaPayment }); + + const { seat, payout: payoutP } = await zoe.redeem( + invite, + aliceProposal, + alicePayments, + ); + + seat.makeOffer(); + const alicePayout = await payoutP; + const aliceMoolaPayout = await alicePayout.Contribution2; - // Setup zoe and mints - const { issuers, moola } = setup(); - const [moolaIssuer] = issuers; - const zoe = makeZoe({ require }); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(automaticRefundRoot); - const installationHandle = zoe.install(source, moduleFormat); - - // 1: Alice creates an automatic refund instance - const invite = await zoe.makeInstance(installationHandle, { - issuers: harden([moolaIssuer, moolaIssuer]), - }); - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(0), - }, - { - kind: 'wantAtLeast', - amount: moola(0), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const alicePayments = [undefined, undefined]; - - const { seat, payout: payoutP } = await zoe.redeem( - invite, - aliceOfferRules, - alicePayments, - ); - - seat.makeOffer(); - const alicePayout = await payoutP; - const aliceMoolaPayout = await alicePayout[0]; - - // Alice got back what she put in - moolaIssuer.getAmountOf(aliceMoolaPayout).then(moolaAmount => { - t.deepEquals(moolaAmount, aliceOfferRules.payoutRules[0].amount); - }); + // Alice got back what she put in + t.deepEquals( + await moolaR.issuer.getAmountOf(aliceMoolaPayout), + aliceProposal.give.Contribution2, + ); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); test('zoe with automaticRefund', async t => { - t.plan(10); - - // Setup zoe and mints - const { - issuers: defaultIssuers, - mints, - amountMaths, - moola, - simoleans, - } = setup(); - const issuers = defaultIssuers.slice(0, 2); - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - - // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(amountMaths[0].make(3)); - const aliceMoolaPurse = issuers[0].makeEmptyPurse(); - const aliceSimoleanPurse = issuers[1].makeEmptyPurse(); - - // Setup Bob - const bobMoolaPurse = issuers[0].makeEmptyPurse(); - const bobSimoleanPurse = issuers[1].makeEmptyPurse(); - const bobSimoleanPayment = mints[1].mintPayment(amountMaths[1].make(17)); - - // Pack the contract. - const { source, moduleFormat } = await bundleSource(automaticRefundRoot); - - // 1: Alice creates an automatic refund instance - const installationHandle = zoe.install(source, moduleFormat); - const terms = harden({ - issuers, - }); - const aliceInvite = await zoe.makeInstance(installationHandle, terms); - const aliceInviteAmount = await inviteIssuer.getAmountOf(aliceInvite); - const { publicAPI } = zoe.getInstance( - aliceInviteAmount.extent[0].instanceHandle, - ); - - // 2: Alice escrows with zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const alicePayments = [aliceMoolaPayment, undefined]; - - // Alice gets two kinds of things back: a seat which she can use - // interact with the contract, and a payout promise - // that resolves to the array of promises for payments - const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( - aliceInvite, - aliceOfferRules, - alicePayments, - ); - - // In the 'automaticRefund' trivial contract, you just get your - // payoutRules back when you make an offer. The effect of calling - // makeOffer will vary widely depending on the smart contract. - const aliceOutcome = aliceSeat.makeOffer(); - const bobInvite = publicAPI.makeInvite(); - const count = publicAPI.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 - // expects - const exclusBobInvite = await inviteIssuer.claim(bobInvite); - const bobInviteAmount = await inviteIssuer.getAmountOf(exclusBobInvite); - const { instanceHandle } = bobInviteAmount.extent[0]; - - const { - installationHandle: bobInstallationId, - terms: bobTerms, - } = zoe.getInstance(instanceHandle); - t.equals(bobInstallationId, installationHandle); - const bobIssuers = bobTerms.issuers; - - // bob wants to know what issuers this contract is about and in - // what order. Is it what he expects? - t.deepEquals(bobIssuers, issuers); - - // 6: Bob also wants to get an automaticRefund (why? we don't - // know) so he escrows his offer and his offer payments. - - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(15), - }, - { - kind: 'offerAtMost', - amount: simoleans(17), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const bobPayments = [undefined, bobSimoleanPayment]; - - // Bob also gets two things back: a seat and a - // payout - const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( - exclusBobInvite, - bobOfferRules, - bobPayments, - ); - const bobOutcome = bobSeat.makeOffer(); - - t.equals(aliceOutcome, 'The offer was accepted'); - t.equals(bobOutcome, '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 - // payments, so we need to wait for those promises to resolve - // separately. - - // offer completes - const alicePayout = await alicePayoutP; - const bobPayout = await bobPayoutP; - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - alicePayout, - ); - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobPayout); - - // Alice got back what she put in - issuers[0].getAmountOf(aliceMoolaPayout).then(aliceMoolaAmount => { - t.deepEquals(aliceMoolaAmount, aliceOfferRules.payoutRules[0].amount); - }); - - // Alice didn't get any of what she wanted - issuers[1].getAmountOf(aliceSimoleanPayout).then(aliceSimoleanAmount => { - t.deepEquals(aliceSimoleanAmount, simoleans(0)); - }); - - // 9: Alice deposits her refund to ensure she can - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - - // 10: Bob deposits his refund to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); - - // 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().extent, 3); - t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 0); - t.equals(bobMoolaPurse.getCurrentAmount().extent, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().extent, 17); + t.plan(11); + try { + // Setup zoe and mints + const { moolaR, simoleanR, moola, simoleans } = setup(); + const zoe = makeZoe({ require }); + const inviteIssuer = zoe.getInviteIssuer(); + + // Setup Alice + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); + const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + + // Setup Bob + const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(17)); + + // Pack the contract. + const { source, moduleFormat } = await bundleSource(automaticRefundRoot); + + // 1: Alice creates an automatic refund instance + const installationHandle = zoe.install(source, moduleFormat); + const issuerKeywordRecord = harden({ + Contribution1: moolaR.issuer, + Contribution2: simoleanR.issuer, + }); + const aliceInvite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(aliceInvite); + const { publicAPI } = zoe.getInstance(instanceHandle); + + // 2: Alice escrows with zoe + const aliceProposal = harden({ + give: { Contribution1: moola(3) }, + want: { Contribution2: simoleans(7) }, + exit: { onDemand: null }, + }); + const alicePayments = { Contribution1: aliceMoolaPayment }; + + // Alice gets two kinds of things back: a seat which she can use + // interact with the contract, and a payout promise + // that resolves to the array of promises for payments + const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( + aliceInvite, + aliceProposal, + alicePayments, + ); + + // In the 'automaticRefund' trivial contract, you just get your + // payments back when you make an offer. The effect of calling + // makeOffer will vary widely depending on the smart contract. + const aliceOutcome = aliceSeat.makeOffer(); + const bobInvite = publicAPI.makeInvite(); + const count = publicAPI.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 + // expects + const exclusBobInvite = await inviteIssuer.claim(bobInvite); + const { + extent: [{ instanceHandle: bobInstanceHandle }], + } = await inviteIssuer.getAmountOf(exclusBobInvite); + + const { + installationHandle: bobInstallationId, + issuerKeywordRecord: bobIssuers, + } = zoe.getInstance(bobInstanceHandle); + t.equals(bobInstallationId, installationHandle); + + // bob wants to know what issuers this contract is about and in + // what order. Is it what he expects? + t.deepEquals(bobIssuers, { + Contribution1: moolaR.issuer, + Contribution2: simoleanR.issuer, + }); + + // 6: Bob also wants to get an automaticRefund (why? we don't + // know) so he escrows his offer payments and makes a proposal. + const bobProposal = harden({ + give: { Contribution2: simoleans(17) }, + want: { Contribution1: moola(15) }, + exit: { onDemand: null }, + }); + const bobPayments = { Contribution2: bobSimoleanPayment }; + + // Bob also gets two things back: a seat and a + // payout + const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( + exclusBobInvite, + bobProposal, + bobPayments, + ); + const bobOutcome = bobSeat.makeOffer(); + + t.equals(aliceOutcome, 'The offer was accepted'); + t.equals(bobOutcome, '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 + // payments, so we need to wait for those promises to resolve + // separately. + + // offer completes + const alicePayout = await alicePayoutP; + const bobPayout = await bobPayoutP; + const aliceMoolaPayout = await alicePayout.Contribution1; + const aliceSimoleanPayout = await alicePayout.Contribution2; + + const bobMoolaPayout = await bobPayout.Contribution1; + const bobSimoleanPayout = await bobPayout.Contribution2; + + // Alice got back what she put in + t.deepEquals( + await moolaR.issuer.getAmountOf(aliceMoolaPayout), + aliceProposal.give.Contribution1, + ); + + // Alice didn't get any of what she wanted + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), + simoleans(0), + ); + + // 9: Alice deposits her refund to ensure she can + await aliceMoolaPurse.deposit(aliceMoolaPayout); + await aliceSimoleanPurse.deposit(aliceSimoleanPayout); + + // 10: Bob deposits his refund to ensure he can + await bobMoolaPurse.deposit(bobMoolaPayout); + await bobSimoleanPurse.deposit(bobSimoleanPayout); + + // 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().extent, 3); + t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 0); + t.equals(bobMoolaPurse.getCurrentAmount().extent, 0); + t.equals(bobSimoleanPurse.getCurrentAmount().extent, 17); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); 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({ require }); + + // Setup Alice + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(30)); + const moola10 = moola(10); + const aliceMoolaPayments = await moolaR.issuer.splitMany( + aliceMoolaPayment, + [moola10, moola10, moola10], + ); + + // 1: Alice creates 3 automatic refund instances + // Pack the contract. + const { source, moduleFormat } = await bundleSource(automaticRefundRoot); + + const installationHandle = zoe.install(source, moduleFormat); + const issuerKeywordRecord = harden({ + ContributionA: moolaR.issuer, + ContributionB: simoleanR.issuer, + }); + const inviteIssuer = zoe.getInviteIssuer(); + const aliceInvite1 = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + const { + extent: [{ instanceHandle: instanceHandle1 }], + } = await inviteIssuer.getAmountOf(aliceInvite1); + const { publicAPI: publicAPI1 } = zoe.getInstance(instanceHandle1); + + const aliceInvite2 = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + const { + extent: [{ instanceHandle: instanceHandle2 }], + } = await inviteIssuer.getAmountOf(aliceInvite2); + const { publicAPI: publicAPI2 } = zoe.getInstance(instanceHandle2); + + const aliceInvite3 = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + const { + extent: [{ instanceHandle: instanceHandle3 }], + } = await inviteIssuer.getAmountOf(aliceInvite3); + const { publicAPI: publicAPI3 } = zoe.getInstance(instanceHandle3); + + // 2: Alice escrows with zoe + const aliceProposal = harden({ + give: { ContributionA: moola(10) }, + want: { ContributionB: simoleans(7) }, + }); + + const { seat: aliceSeat1, payout: payoutP1 } = await zoe.redeem( + aliceInvite1, + aliceProposal, + harden({ ContributionA: aliceMoolaPayments[0] }), + ); - // Setup zoe and mints - const { issuers: originalIssuers, mints, moola, simoleans } = setup(); - const issuers = originalIssuers.slice(0, 2); - const zoe = makeZoe({ require }); - - // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(moola(30)); - const moola10 = moola(10); - const aliceMoolaPayments = await issuers[0].splitMany(aliceMoolaPayment, [ - moola10, - moola10, - moola10, - ]); - - // 1: Alice creates 3 automatic refund instances - // Pack the contract. - const { source, moduleFormat } = await bundleSource(automaticRefundRoot); - - const installationHandle = zoe.install(source, moduleFormat); - const terms = harden({ - issuers, - }); - const inviteIssuer = zoe.getInviteIssuer(); - const aliceInvite1 = await zoe.makeInstance(installationHandle, terms); - const { publicAPI: publicAPI1 } = await inviteIssuer - .getAmountOf(aliceInvite1) - .then(invite1Amount => - zoe.getInstance(invite1Amount.extent[0].instanceHandle), + // 3: Alice escrows with zoe + const { seat: aliceSeat2, payout: payoutP2 } = await zoe.redeem( + aliceInvite2, + aliceProposal, + harden({ ContributionA: aliceMoolaPayments[1] }), ); - const aliceInvite2 = await zoe.makeInstance(installationHandle, terms); - const { publicAPI: publicAPI2 } = await inviteIssuer - .getAmountOf(aliceInvite2) - .then(invite2Amount => - zoe.getInstance(invite2Amount.extent[0].instanceHandle), + // 4: Alice escrows with zoe + const { seat: aliceSeat3, payout: payoutP3 } = await zoe.redeem( + aliceInvite3, + aliceProposal, + harden({ ContributionA: aliceMoolaPayments[2] }), ); - const aliceInvite3 = await zoe.makeInstance(installationHandle, terms); - const { publicAPI: publicAPI3 } = await inviteIssuer - .getAmountOf(aliceInvite3) - .then(invite3Amount => - zoe.getInstance(invite3Amount.extent[0].instanceHandle), + // 5: Alice makes an offer + aliceSeat1.makeOffer(); + aliceSeat2.makeOffer(); + aliceSeat3.makeOffer(); + + 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; + + // Ensure that she got what she put in for each + t.deepEquals( + await moolaR.issuer.getAmountOf(moolaPayout1), + aliceProposal.give.ContributionA, + ); + t.deepEquals( + await moolaR.issuer.getAmountOf(moolaPayout2), + aliceProposal.give.ContributionA, + ); + t.deepEquals( + await moolaR.issuer.getAmountOf(moolaPayout3), + aliceProposal.give.ContributionA, ); - // 2: Alice escrows with zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(10), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - - const { seat: aliceSeat1, payout: payoutP1 } = await zoe.redeem( - aliceInvite1, - aliceOfferRules, - [aliceMoolaPayments[0], undefined], - ); - - // 3: Alice escrows with zoe - const { seat: aliceSeat2, payout: payoutP2 } = await zoe.redeem( - aliceInvite2, - aliceOfferRules, - [aliceMoolaPayments[1], undefined], - ); - - // 4: Alice escrows with zoe - const { seat: aliceSeat3, payout: payoutP3 } = await zoe.redeem( - aliceInvite3, - aliceOfferRules, - [aliceMoolaPayments[2], undefined], - ); - - // 5: Alice makes an offer - aliceSeat1.makeOffer(); - aliceSeat2.makeOffer(); - aliceSeat3.makeOffer(); - - const [moolaPayout1P] = await payoutP1; - const [moolaPayout2P] = await payoutP2; - const [moolaPayout3P] = await payoutP3; - - const moolaPayout1 = await moolaPayout1P; - const moolaPayout2 = await moolaPayout2P; - const moolaPayout3 = await moolaPayout3P; - - // Ensure that she got what she put in for each - issuers[0].getAmountOf(moolaPayout1).then(payout1Amount => { - t.deepEquals(payout1Amount, aliceOfferRules.payoutRules[0].amount); - }); - issuers[0].getAmountOf(moolaPayout2).then(payout2Amount => { - t.deepEquals(payout2Amount, aliceOfferRules.payoutRules[0].amount); - }); - issuers[0].getAmountOf(moolaPayout3).then(payout3Amount => { - t.deepEquals(payout3Amount, aliceOfferRules.payoutRules[0].amount); - }); - - // Ensure that the number of offers received by each instance is one - t.equals(publicAPI1.getOffersCount(), 1); - t.equals(publicAPI2.getOffersCount(), 1); - t.equals(publicAPI3.getOffersCount(), 1); + // Ensure that the number of offers received by each instance is one + t.equals(publicAPI1.getOffersCount(), 1); + t.equals(publicAPI2.getOffersCount(), 1); + t.equals(publicAPI3.getOffersCount(), 1); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); test('zoe - alice cancels after completion', async t => { - t.plan(4); - - // Setup zoe and mints - const { issuers: defaultIssuers, mints, moola, simoleans } = setup(); - const issuers = defaultIssuers.slice(0, 2); - const zoe = makeZoe({ require }); - - // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(moola(3)); - const aliceMoolaPurse = issuers[0].makeEmptyPurse(); - const aliceSimoleanPurse = issuers[1].makeEmptyPurse(); - - // 2: Alice escrows with zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const alicePayments = [aliceMoolaPayment, undefined]; - - // Pack the contract. - const { source, moduleFormat } = await bundleSource(automaticRefundRoot); - const installationHandle = zoe.install(source, moduleFormat); - const terms = harden({ - issuers, - }); - const invite = await zoe.makeInstance(installationHandle, terms); - - const { seat, cancelObj, payout: payoutP } = await zoe.redeem( - invite, - aliceOfferRules, - alicePayments, - ); - - await seat.makeOffer(); - - t.throws(() => cancelObj.cancel(), /Error: offer has already completed/); - - const payout = await payoutP; - const [moolaPayout, simoleanPayout] = await Promise.all(payout); - - // Alice got back what she put in - issuers[0].getAmountOf(moolaPayout).then(aliceMoolaAmount => { - t.deepEquals(aliceMoolaAmount, aliceOfferRules.payoutRules[0].amount); - }); - - // Alice didn't get any of what she wanted - issuers[1].getAmountOf(simoleanPayout).then(aliceSimoleanAmount => { - t.deepEquals(aliceSimoleanAmount, simoleans(0)); - }); - - // 9: Alice deposits her refund to ensure she can - await aliceMoolaPurse.deposit(moolaPayout); - await aliceSimoleanPurse.deposit(simoleanPayout); - - // Assert that the correct refund was achieved. - // Alice had 3 moola and 0 simoleans. - t.equals(aliceMoolaPurse.getCurrentAmount().extent, 3); - t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 0); + t.plan(5); + try { + // Setup zoe and mints + const { moolaR, simoleanR, moola, simoleans } = setup(); + const zoe = makeZoe({ require }); + + // Setup Alice + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); + const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + + // Pack the contract. + const { source, moduleFormat } = await bundleSource(automaticRefundRoot); + const installationHandle = zoe.install(source, moduleFormat); + const issuerKeywordRecord = harden({ + ContributionA: moolaR.issuer, + ContributionB: simoleanR.issuer, + }); + const invite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + + const aliceProposal = harden({ + give: { ContributionA: moola(3) }, + want: { ContributionB: simoleans(7) }, + }); + const alicePayments = { ContributionA: aliceMoolaPayment }; + + const { seat, cancelObj, payout: payoutP } = await zoe.redeem( + invite, + aliceProposal, + alicePayments, + ); + + await seat.makeOffer(); + + t.throws(() => cancelObj.cancel(), /Error: offer has already completed/); + + const payout = await payoutP; + const moolaPayout = await payout.ContributionA; + const simoleanPayout = await payout.ContributionB; + + // Alice got back what she put in + t.deepEquals( + await moolaR.issuer.getAmountOf(moolaPayout), + aliceProposal.give.ContributionA, + ); + + // Alice didn't get any of what she wanted + t.deepEquals( + await simoleanR.issuer.getAmountOf(simoleanPayout), + simoleans(0), + ); + + // 9: Alice deposits her refund to ensure she can + await aliceMoolaPurse.deposit(moolaPayout); + await aliceSimoleanPurse.deposit(simoleanPayout); + + // Assert that the correct refund was achieved. + // Alice had 3 moola and 0 simoleans. + t.equals(aliceMoolaPurse.getCurrentAmount().extent, 3); + t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 0); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); diff --git a/packages/zoe/test/unitTests/contracts/test-autoswap.js b/packages/zoe/test/unitTests/contracts/test-autoswap.js index 9b7d1334ef7..b87b54fc5b8 100644 --- a/packages/zoe/test/unitTests/contracts/test-autoswap.js +++ b/packages/zoe/test/unitTests/contracts/test-autoswap.js @@ -6,357 +6,292 @@ import bundleSource from '@agoric/bundle-source'; import harden from '@agoric/harden'; import { makeZoe } from '../../../src/zoe'; -import { setup } from '../setupBasicMints'; +import { setup } from '../setupBasicMints2'; const autoswapRoot = `${__dirname}/../../../src/contracts/autoswap`; test('autoSwap with valid offers', async t => { t.plan(19); + try { + const { moolaR, simoleanR, moola, simoleans } = setup(); + const zoe = makeZoe({ require }); + const inviteIssuer = zoe.getInviteIssuer(); - const { mints, issuers: defaultIssuers, moola, simoleans } = setup(); - const issuers = defaultIssuers.slice(0, 2); - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - const [moolaIssuer, simoleanIssuer] = issuers; - const [moolaMint, simoleanMint] = mints; - - // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(10)); - // Let's assume that simoleans are worth 2x as much as moola - const aliceSimoleanPayment = simoleanMint.mintPayment(simoleans(5)); - - // Setup Bob - const bobMoolaPayment = moolaMint.mintPayment(moola(3)); - const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(3)); - - // Alice creates an autoswap instance - - // Pack the contract. - const { source, moduleFormat } = await bundleSource(autoswapRoot); - - const installationHandle = zoe.install(source, moduleFormat); - const aliceInvite = await zoe.makeInstance(installationHandle, { - issuers, - }); - const aliceInviteAmount = await inviteIssuer.getAmountOf(aliceInvite); - const { instanceHandle } = aliceInviteAmount.extent[0]; - const { publicAPI } = zoe.getInstance(instanceHandle); - const liquidityIssuer = publicAPI.getLiquidityIssuer(); - const liquidity = liquidityIssuer.getAmountMath().make; - - // Alice adds liquidity - // 10 moola = 5 simoleans at the time of the liquidity adding - // aka 2 moola = 1 simolean - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(10), - }, - { - kind: 'offerAtMost', - amount: simoleans(5), - }, - { - kind: 'wantAtLeast', - amount: liquidity(10), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const alicePayments = [aliceMoolaPayment, aliceSimoleanPayment, undefined]; - - const { - seat: aliceSeat, - payout: aliceAddLiquidityPayoutP, - } = await zoe.redeem(aliceInvite, aliceOfferRules, alicePayments); - - const liquidityOk = await aliceSeat.addLiquidity(); - t.equals(liquidityOk, 'Added liquidity.'); - - const liquidityPayments = await aliceAddLiquidityPayoutP; - const liquidityPayout = await liquidityPayments[2]; - - const liquidityAmount = await liquidityIssuer.getAmountOf(liquidityPayout); - t.deepEquals(liquidityAmount, liquidity(10)); - t.deepEquals(publicAPI.getPoolAmounts(), [ - moola(10), - simoleans(5), - liquidity(0), - ]); - - // Alice creates an invite for autoswap and sends it to Bob - const bobInvite = publicAPI.makeInvite(); - - // Bob claims it - const bobExclInvite = await inviteIssuer.claim(bobInvite); - const bobExclInviteAmount = await inviteIssuer.getAmountOf(bobExclInvite); - const bobInviteExtent = bobExclInviteAmount.extent[0]; - const { - publicAPI: bobAutoswap, - installationHandle: bobInstallationId, - } = zoe.getInstance(bobInviteExtent.instanceHandle); - t.equals(bobInstallationId, installationHandle); - - // Bob looks up the price of 3 moola in simoleans - const simoleanAmounts = bobAutoswap.getPrice(moola(3)); - t.deepEquals(simoleanAmounts, simoleans(1)); - - // Bob escrows - - const bobMoolaForSimOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(1), - }, - { - kind: 'wantAtLeast', - amount: liquidity(0), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const bobMoolaForSimPayments = [bobMoolaPayment, undefined, undefined]; - - const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( - bobExclInvite, - bobMoolaForSimOfferRules, - bobMoolaForSimPayments, - ); - - // Bob swaps - const offerOk = bobSeat.swap(); - t.equal(offerOk, 'Swap successfully completed.'); - - const bobPayout = await bobPayoutP; - - const [bobMoolaPayout1, bobSimoleanPayout1] = await Promise.all(bobPayout); - - moolaIssuer.getAmountOf(bobMoolaPayout1).then(bobMoolaAmount1 => { - t.deepEqual(bobMoolaAmount1, moola(0)); - }); - - simoleanIssuer.getAmountOf(bobSimoleanPayout1).then(bobSimoleanAmount => { - t.deepEqual(bobSimoleanAmount, simoleans(1)); - }); - - t.deepEquals(bobAutoswap.getPoolAmounts(), [ - moola(13), - simoleans(4), - liquidity(0), - ]); - - // Bob looks up the price of 3 simoleans - const moolaAmounts = bobAutoswap.getPrice(simoleans(3)); - t.deepEquals(moolaAmounts, moola(5)); - - // Bob makes another offer and swaps - const bobSecondInvite = bobAutoswap.makeInvite(); - const bobSimsForMoolaOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(5), - }, - { - kind: 'offerAtMost', - amount: simoleans(3), - }, - { - kind: 'wantAtLeast', - amount: liquidity(0), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const simsForMoolaPayments = [undefined, bobSimoleanPayment, undefined]; - - const { - seat: bobSeatSimsForMoola, - payout: bobSimsForMoolaPayoutP, - } = await zoe.redeem( - bobSecondInvite, - bobSimsForMoolaOfferRules, - simsForMoolaPayments, - ); - - const simsForMoolaOk = bobSeatSimsForMoola.swap(); - t.equal(simsForMoolaOk, 'Swap successfully completed.'); - - const bobsNewMoolaPayment = await bobSimsForMoolaPayoutP; - const [bobMoolaPayout2, bobSimoleanPayout2] = await Promise.all( - bobsNewMoolaPayment, - ); - - moolaIssuer.getAmountOf(bobMoolaPayout2).then(bobMoolaAmount2 => { - t.deepEqual(bobMoolaAmount2, moola(5), 'bob moola should be 5'); - }); - simoleanIssuer.getAmountOf(bobSimoleanPayout2).then(bobSimoleanAmount2 => { - t.deepEqual(bobSimoleanAmount2, simoleans(0)); - }); - - t.deepEqual(bobAutoswap.getPoolAmounts(), [ - moola(8), - simoleans(7), - liquidity(0), - ]); - - // Alice removes her liquidity - // She's not picky... - const aliceSecondInvite = publicAPI.makeInvite(); - const aliceRemoveLiquidityOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(0), - }, - { - kind: 'wantAtLeast', - amount: simoleans(0), - }, - { - kind: 'offerAtMost', - amount: liquidity(10), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - - const { - seat: aliceRemoveLiquiditySeat, - payout: aliceRemoveLiquidityPayoutP, - } = await zoe.redeem( - aliceSecondInvite, - aliceRemoveLiquidityOfferRules, - harden([undefined, undefined, liquidityPayout]), - ); - - const removeLiquidityResult = aliceRemoveLiquiditySeat.removeLiquidity(); - t.equals(removeLiquidityResult, 'Liquidity successfully removed.'); - - const alicePayoutPayments = await aliceRemoveLiquidityPayoutP; - const [ - aliceMoolaPayout, - aliceSimoleanPayout, - aliceLiquidityPayout, - ] = await Promise.all(alicePayoutPayments); - - moolaIssuer.getAmountOf(aliceMoolaPayout).then(aliceMoolaAmount => { - t.deepEquals(aliceMoolaAmount, moola(8)); - }); - simoleanIssuer.getAmountOf(aliceSimoleanPayout).then(aliceSimoleanAmount => { - t.deepEquals(aliceSimoleanAmount, simoleans(7)); - }); - liquidityIssuer.getAmountOf(aliceLiquidityPayout).then(aliceLiqAmount => { - t.deepEquals(aliceLiqAmount, liquidity(0)); - }); - - t.deepEquals(publicAPI.getPoolAmounts(), [ - moola(0), - simoleans(0), - liquidity(10), - ]); + // Setup Alice + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(10)); + // Let's assume that simoleans are worth 2x as much as moola + const aliceSimoleanPayment = simoleanR.mint.mintPayment(simoleans(5)); + + // Setup Bob + const bobMoolaPayment = moolaR.mint.mintPayment(moola(3)); + const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(3)); + + // Alice creates an autoswap instance + + // Pack the contract. + const { source, moduleFormat } = await bundleSource(autoswapRoot); + + const installationHandle = zoe.install(source, moduleFormat); + const issuerKeywordRecord = harden({ + TokenA: moolaR.issuer, + TokenB: simoleanR.issuer, + }); + const aliceInvite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(aliceInvite); + const { publicAPI } = zoe.getInstance(instanceHandle); + const liquidityIssuer = publicAPI.getLiquidityIssuer(); + const liquidity = liquidityIssuer.getAmountMath().make; + + // Alice adds liquidity + // 10 moola = 5 simoleans at the time of the liquidity adding + // aka 2 moola = 1 simolean + const aliceProposal = harden({ + want: { Liquidity: liquidity(10) }, + give: { TokenA: moola(10), TokenB: simoleans(5) }, + }); + const alicePayments = { + TokenA: aliceMoolaPayment, + TokenB: aliceSimoleanPayment, + }; + + const { + seat: aliceSeat, + payout: aliceAddLiquidityPayoutP, + } = await zoe.redeem(aliceInvite, aliceProposal, alicePayments); + + const liquidityOk = await aliceSeat.addLiquidity(); + t.equals(liquidityOk, 'Added liquidity.'); + + const liquidityPayments = await aliceAddLiquidityPayoutP; + const liquidityPayout = await liquidityPayments.Liquidity; + + t.deepEquals( + await liquidityIssuer.getAmountOf(liquidityPayout), + liquidity(10), + ); + t.deepEquals(publicAPI.getPoolAmounts(), { + TokenA: moola(10), + TokenB: simoleans(5), + Liquidity: liquidity(0), + }); + + // Alice creates an invite for autoswap and sends it to Bob + const bobInvite = publicAPI.makeInvite(); + + // Bob claims it + const bobExclInvite = await inviteIssuer.claim(bobInvite); + const { + extent: [bobInviteExtent], + } = await inviteIssuer.getAmountOf(bobExclInvite); + const { + publicAPI: bobAutoswap, + installationHandle: bobInstallationId, + } = zoe.getInstance(bobInviteExtent.instanceHandle); + t.equals(bobInstallationId, installationHandle); + + // Bob looks up the price of 3 moola in simoleans + const simoleanAmounts = bobAutoswap.getPrice(harden({ TokenA: moola(3) })); + t.deepEquals(simoleanAmounts, simoleans(1)); + + // Bob escrows + + const bobMoolaForSimProposal = harden({ + want: { TokenB: simoleans(1) }, + give: { TokenA: moola(3) }, + }); + const bobMoolaForSimPayments = harden({ TokenA: bobMoolaPayment }); + + const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( + bobExclInvite, + bobMoolaForSimProposal, + bobMoolaForSimPayments, + ); + + // Bob swaps + const offerOk = bobSeat.swap(); + t.equal(offerOk, 'Swap successfully completed.'); + + const bobPayout = await bobPayoutP; + + const bobMoolaPayout1 = await bobPayout.TokenA; + const bobSimoleanPayout1 = await bobPayout.TokenB; + + t.deepEqual(await moolaR.issuer.getAmountOf(bobMoolaPayout1), moola(0)); + t.deepEqual( + await simoleanR.issuer.getAmountOf(bobSimoleanPayout1), + simoleans(1), + ); + t.deepEquals(bobAutoswap.getPoolAmounts(), { + TokenA: moola(13), + TokenB: simoleans(4), + Liquidity: liquidity(0), + }); + + // Bob looks up the price of 3 simoleans + const moolaAmounts = bobAutoswap.getPrice(harden({ TokenB: simoleans(3) })); + t.deepEquals(moolaAmounts, moola(5)); + + // Bob makes another offer and swaps + const bobSecondInvite = bobAutoswap.makeInvite(); + const bobSimsForMoolaProposal = harden({ + want: { TokenA: moola(5) }, + give: { TokenB: simoleans(3) }, + }); + const simsForMoolaPayments = harden({ TokenB: bobSimoleanPayment }); + + const { + seat: bobSeatSimsForMoola, + payout: bobSimsForMoolaPayoutP, + } = await zoe.redeem( + bobSecondInvite, + bobSimsForMoolaProposal, + simsForMoolaPayments, + ); + + const simsForMoolaOk = bobSeatSimsForMoola.swap(); + t.equal(simsForMoolaOk, 'Swap successfully completed.'); + + const bobSimsForMoolaPayout = await bobSimsForMoolaPayoutP; + const bobMoolaPayout2 = await bobSimsForMoolaPayout.TokenA; + const bobSimoleanPayout2 = await bobSimsForMoolaPayout.TokenB; + + t.deepEqual(await moolaR.issuer.getAmountOf(bobMoolaPayout2), moola(5)); + t.deepEqual( + await simoleanR.issuer.getAmountOf(bobSimoleanPayout2), + simoleans(0), + ); + t.deepEqual(bobAutoswap.getPoolAmounts(), { + TokenA: moola(8), + TokenB: simoleans(7), + Liquidity: liquidity(0), + }); + + // Alice removes her liquidity + // She's not picky... + const aliceSecondInvite = publicAPI.makeInvite(); + const aliceRemoveLiquidityProposal = harden({ + give: { Liquidity: liquidity(10) }, + }); + + const { + seat: aliceRemoveLiquiditySeat, + payout: aliceRemoveLiquidityPayoutP, + } = await zoe.redeem( + aliceSecondInvite, + aliceRemoveLiquidityProposal, + harden({ Liquidity: liquidityPayout }), + ); + + const removeLiquidityResult = aliceRemoveLiquiditySeat.removeLiquidity(); + t.equals(removeLiquidityResult, '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 moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(8)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), + simoleans(7), + ); + t.deepEquals( + await liquidityIssuer.getAmountOf(aliceLiquidityPayout), + liquidity(0), + ); + t.deepEquals(publicAPI.getPoolAmounts(), { + TokenA: moola(0), + TokenB: simoleans(0), + Liquidity: liquidity(10), + }); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); test('autoSwap - test fee', async t => { t.plan(9); - const { - mints: defaultMints, - issuers: defaultIssuers, - moola, - simoleans, - } = setup(); - const mints = defaultMints.slice(0, 2); - const issuers = defaultIssuers.slice(0, 2); - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - const [moolaIssuer, simoleanIssuer] = issuers; - - // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(moola(10000)); - const aliceSimoleanPayment = mints[1].mintPayment(simoleans(10000)); - - // Setup Bob - const bobMoolaPayment = mints[0].mintPayment(moola(1000)); - - // Alice creates an autoswap instance - - // Pack the contract. - const { source, moduleFormat } = await bundleSource(autoswapRoot); - - const installationHandle = zoe.install(source, moduleFormat); - const aliceInvite = await zoe.makeInstance(installationHandle, { - issuers, - }); - inviteIssuer.getAmountOf(aliceInvite).then(async aliceInviteAmount => { - const { instanceHandle } = aliceInviteAmount.extent[0]; + try { + const { moolaR, simoleanR, moola, simoleans } = setup(); + const zoe = makeZoe({ require }); + const inviteIssuer = zoe.getInviteIssuer(); + + // Setup Alice + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(10000)); + const aliceSimoleanPayment = simoleanR.mint.mintPayment(simoleans(10000)); + + // Setup Bob + const bobMoolaPayment = moolaR.mint.mintPayment(moola(1000)); + + // Alice creates an autoswap instance + + // Pack the contract. + const { source, moduleFormat } = await bundleSource(autoswapRoot); + + const installationHandle = zoe.install(source, moduleFormat); + const issuerKeywordRecord = harden({ + TokenA: moolaR.issuer, + TokenB: simoleanR.issuer, + }); + const aliceInvite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + ); + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(aliceInvite); const { publicAPI } = zoe.getInstance(instanceHandle); const liquidityIssuer = publicAPI.getLiquidityIssuer(); const liquidity = liquidityIssuer.getAmountMath().make; // Alice adds liquidity - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(10000), - }, - { - kind: 'offerAtMost', - amount: simoleans(10000), - }, - { - kind: 'wantAtLeast', - amount: liquidity(0), - }, - ], - exitRule: { - kind: 'onDemand', + const aliceProposal = harden({ + give: { + TokenA: moola(10000), + TokenB: simoleans(10000), }, + want: { Liquidity: liquidity(0) }, + }); + const alicePayments = harden({ + TokenA: aliceMoolaPayment, + TokenB: aliceSimoleanPayment, }); - const alicePayments = [aliceMoolaPayment, aliceSimoleanPayment, undefined]; const { seat: aliceSeat, payout: aliceAddLiquidityPayoutP, - } = await zoe.redeem(aliceInvite, aliceOfferRules, alicePayments); + } = await zoe.redeem(aliceInvite, aliceProposal, alicePayments); const liquidityOk = await aliceSeat.addLiquidity(); t.equals(liquidityOk, 'Added liquidity.'); const liquidityPayments = await aliceAddLiquidityPayoutP; - const liquidityPayout = await liquidityPayments[2]; + const liquidityPayout = await liquidityPayments.Liquidity; - liquidityIssuer.getAmountOf(liquidityPayout).then(liquidityAmount => { - t.deepEquals(liquidityAmount, liquidity(10000)); + t.deepEquals( + await liquidityIssuer.getAmountOf(liquidityPayout), + liquidity(10000), + ); + t.deepEquals(publicAPI.getPoolAmounts(), { + TokenA: moola(10000), + TokenB: simoleans(10000), + Liquidity: liquidity(0), }); - t.deepEquals(publicAPI.getPoolAmounts(), [ - moola(10000), - simoleans(10000), - liquidity(0), - ]); - // Alice creates an invite for autoswap and sends it to Bob const bobInvite = publicAPI.makeInvite(); // Bob claims it const bobExclInvite = await inviteIssuer.claim(bobInvite); - const bobExclAmount = await inviteIssuer.getAmountOf(bobExclInvite); - const bobInviteExtent = bobExclAmount.extent[0]; + const { + extent: [bobInviteExtent], + } = await inviteIssuer.getAmountOf(bobExclInvite); const { publicAPI: bobAutoswap, installationHandle: bobInstallationId, @@ -364,34 +299,21 @@ test('autoSwap - test fee', async t => { t.equals(bobInstallationId, installationHandle); // Bob looks up the price of 1000 moola in simoleans - const simoleanAmounts = bobAutoswap.getPrice(moola(1000)); + const simoleanAmounts = bobAutoswap.getPrice( + harden({ TokenA: moola(1000) }), + ); t.deepEquals(simoleanAmounts, simoleans(906)); // Bob escrows - const bobMoolaForSimOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(1000), - }, - { - kind: 'wantAtLeast', - amount: simoleans(0), - }, - { - kind: 'wantAtLeast', - amount: liquidity(0), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobMoolaForSimProposal = harden({ + give: { TokenA: moola(1000) }, + want: { TokenB: simoleans(0) }, }); - const bobMoolaForSimPayments = [bobMoolaPayment, undefined, undefined]; + const bobMoolaForSimPayments = harden({ TokenA: bobMoolaPayment }); const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( bobExclInvite, - bobMoolaForSimOfferRules, + bobMoolaForSimProposal, bobMoolaForSimPayments, ); @@ -400,19 +322,21 @@ test('autoSwap - test fee', async t => { t.equal(offerOk, 'Swap successfully completed.'); const bobPayout = await bobPayoutP; - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobPayout); + const bobMoolaPayout = await bobPayout.TokenA; + const bobSimoleanPayout = await bobPayout.TokenB; - moolaIssuer.getAmountOf(bobMoolaPayout).then(bobMoolaAmount => { - t.deepEqual(bobMoolaAmount, moola(0)); - }); - simoleanIssuer.getAmountOf(bobSimoleanPayout).then(bobSimoleanAmount => { - t.deepEqual(bobSimoleanAmount, simoleans(906)); + t.deepEqual(await moolaR.issuer.getAmountOf(bobMoolaPayout), moola(0)); + t.deepEqual( + await simoleanR.issuer.getAmountOf(bobSimoleanPayout), + simoleans(906), + ); + t.deepEquals(bobAutoswap.getPoolAmounts(), { + TokenA: moola(11000), + TokenB: simoleans(9094), + Liquidity: liquidity(0), }); - - t.deepEquals(bobAutoswap.getPoolAmounts(), [ - moola(11000), - simoleans(9094), - liquidity(0), - ]); - }); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); diff --git a/packages/zoe/test/unitTests/contracts/test-coveredCall.js b/packages/zoe/test/unitTests/contracts/test-coveredCall.js index f4793d5524b..c5326645c45 100644 --- a/packages/zoe/test/unitTests/contracts/test-coveredCall.js +++ b/packages/zoe/test/unitTests/contracts/test-coveredCall.js @@ -8,119 +8,91 @@ import { sameStructure } from '@agoric/same-structure'; import buildManualTimer from '../../../tools/manualTimer'; import { makeZoe } from '../../../src/zoe'; -import { setup } from '../setupBasicMints'; +import { setup } from '../setupBasicMints2'; const coveredCallRoot = `${__dirname}/../../../src/contracts/coveredCall`; const atomicSwapRoot = `${__dirname}/../../../src/contracts/atomicSwap`; test('zoe - coveredCall', async t => { t.plan(13); + try { + const { moolaR, simoleanR, moola, simoleans } = setup(); + const zoe = makeZoe({ require }); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(coveredCallRoot); + const coveredCallInstallationHandle = zoe.install(source, moduleFormat); + 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 aliceInvite = await zoe.makeInstance( + coveredCallInstallationHandle, + issuerKeywordRecord, + ); - const { - mints: defaultMints, - issuers: defaultIssuers, - moola, - simoleans, - amountMaths, - } = setup(); - const mints = defaultMints.slice(0, 2); - const issuers = defaultIssuers.slice(0, 2); - const [moolaIssuer, simoleanIssuer] = issuers; - const zoe = makeZoe({ require }); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(coveredCallRoot); - const coveredCallInstallationHandle = zoe.install(source, moduleFormat); - const timer = buildManualTimer(console.log); - - // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(moola(3)); - const aliceMoolaPurse = issuers[0].makeEmptyPurse(); - const aliceSimoleanPurse = issuers[1].makeEmptyPurse(); - - // Setup Bob - const bobSimoleanPayment = mints[1].mintPayment(simoleans(7)); - const bobMoolaPurse = issuers[0].makeEmptyPurse(); - const bobSimoleanPurse = issuers[1].makeEmptyPurse(); - - // Alice creates a coveredCall instance - const terms = { - issuers, - }; - const aliceInvite = await zoe.makeInstance( - coveredCallInstallationHandle, - terms, - ); - - // Alice escrows with Zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 1, - timer, - }, - }); - const alicePayments = [aliceMoolaPayment, undefined]; - const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( - aliceInvite, - aliceOfferRules, - alicePayments, - ); - - // Alice creates a call option - - const option = aliceSeat.makeCallOption(); - - // Imagine that Alice sends the option to Bob for free (not done here - // since this test doesn't actually have separate vats/parties) - - // 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(option); - inviteIssuer.getAmountOf(bobExclOption).then(async bobExclOptionAmt => { - const optionExtent = bobExclOptionAmt.extent[0]; + // Alice escrows with Zoe + const aliceProposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { afterDeadline: { deadline: 1, timer } }, + }); + const alicePayments = { UnderlyingAsset: aliceMoolaPayment }; + const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( + aliceInvite, + aliceProposal, + alicePayments, + ); + + // Alice creates a call option + + const option = aliceSeat.makeCallOption(); + + // Imagine that Alice sends the option to Bob for free (not done here + // since this test doesn't actually have separate vats/parties) + + // 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(option); + const { + extent: [optionExtent], + } = await inviteIssuer.getAmountOf(bobExclOption); const { installationHandle } = zoe.getInstance(optionExtent.instanceHandle); t.equal(installationHandle, coveredCallInstallationHandle); t.equal(optionExtent.seatDesc, 'exerciseOption'); - t.ok(amountMaths[0].isEqual(optionExtent.underlyingAsset, moola(3))); - t.ok(amountMaths[1].isEqual(optionExtent.strikePrice, simoleans(7))); + t.ok(moolaR.amountMath.isEqual(optionExtent.underlyingAsset, moola(3))); + t.ok(simoleanR.amountMath.isEqual(optionExtent.strikePrice, simoleans(7))); t.equal(optionExtent.expirationDate, 1); t.deepEqual(optionExtent.timerAuthority, timer); - const bobPayments = [undefined, bobSimoleanPayment]; + const bobPayments = { StrikePrice: bobSimoleanPayment }; - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: optionExtent.underlyingAsset, - }, - { - kind: 'offerAtMost', - amount: optionExtent.strikePrice, - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobProposal = harden({ + want: { UnderlyingAsset: optionExtent.underlyingAsset }, + give: { StrikePrice: optionExtent.strikePrice }, + exit: { onDemand: null }, }); // Bob redeems his invite and escrows with Zoe const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( bobExclOption, - bobOfferRules, + bobProposal, bobPayments, ); @@ -135,28 +107,23 @@ test('zoe - coveredCall', async t => { const bobPayout = await bobPayoutP; const alicePayout = await alicePayoutP; - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobPayout); - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - alicePayout, - ); + 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 - simoleanIssuer - .getAmountOf(aliceSimoleanPayout) - .then(async aliceSimoleanAmt => { - t.deepEquals(aliceSimoleanAmt, aliceOfferRules.payoutRules[1].amount); - - // Alice deposits her payout to ensure she can - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); - }); + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), + aliceProposal.want.StrikePrice, + ); // Alice didn't get any of what Alice put in - moolaIssuer.getAmountOf(aliceMoolaPayout).then(async aliceMoolaAmount => { - t.deepEquals(aliceMoolaAmount, moola(0)); - await aliceMoolaPurse.deposit(aliceMoolaPayout); - t.equals(aliceMoolaPurse.getCurrentAmount().extent, 0); - }); + 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); // Bob deposits his original payments to ensure he can await bobMoolaPurse.deposit(bobMoolaPayout); @@ -165,155 +132,138 @@ test('zoe - coveredCall', async t => { // 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().extent, 0); + t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); t.equals(bobMoolaPurse.getCurrentAmount().extent, 3); t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); - }); + } catch (e) { + t.isNot(e, e, 'unexpected exception'); + } }); test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, async t => { t.plan(13); + try { + const { moolaR, simoleanR, moola, simoleans } = setup(); + const zoe = makeZoe({ require }); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(coveredCallRoot); + const coveredCallInstallationHandle = zoe.install(source, moduleFormat); + 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, + }); + const aliceInvite = await zoe.makeInstance( + coveredCallInstallationHandle, + issuerKeywordRecord, + ); - const { - mints: defaultMints, - issuers: defaultIssuers, - moola, - simoleans, - amountMaths, - } = setup(); - const mints = defaultMints.slice(0, 2); - const issuers = defaultIssuers.slice(0, 2); - const [moolaIssuer, simoleanIssuer] = issuers; - const zoe = makeZoe({ require }); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(coveredCallRoot); - const coveredCallInstallationHandle = zoe.install(source, moduleFormat); - const timer = buildManualTimer(console.log); - - // Setup Alice - const aliceMoolaPayment = mints[0].mintPayment(moola(3)); - const aliceMoolaPurse = issuers[0].makeEmptyPurse(); - const aliceSimoleanPurse = issuers[1].makeEmptyPurse(); - - // Setup Bob - const bobSimoleanPayment = mints[1].mintPayment(simoleans(7)); - const bobMoolaPurse = issuers[0].makeEmptyPurse(); - const bobSimoleanPurse = issuers[1].makeEmptyPurse(); - - // Alice creates a coveredCall instance - const terms = { - issuers, - }; - const aliceInvite = await zoe.makeInstance( - coveredCallInstallationHandle, - terms, - ); - - // Alice escrows with Zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 1, - timer, - }, - }); - const alicePayments = [aliceMoolaPayment, undefined]; - const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( - aliceInvite, - aliceOfferRules, - alicePayments, - ); - - // Alice makes an option - const option = aliceSeat.makeCallOption(); - timer.tick(); - - // Imagine that Alice sends the option to Bob for free (not done here - // since this test doesn't actually have separate vats/parties) - - // 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(option); - const bobExclOptionAmount = await inviteIssuer.getAmountOf(bobExclOption); - const optionExtent = bobExclOptionAmount.extent[0]; - const { installationHandle } = zoe.getInstance(optionExtent.instanceHandle); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionExtent.seatDesc, 'exerciseOption'); - t.ok(amountMaths[0].isEqual(optionExtent.underlyingAsset, moola(3))); - t.ok(amountMaths[1].isEqual(optionExtent.strikePrice, simoleans(7))); - t.equal(optionExtent.expirationDate, 1); - t.deepEqual(optionExtent.timerAuthority, timer); - - const bobPayments = [undefined, bobSimoleanPayment]; - - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: optionExtent.underlyingAsset, - }, - { - kind: 'offerAtMost', - amount: optionExtent.strikePrice, + // Alice escrows with Zoe + const aliceProposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { + afterDeadline: { + deadline: 1, + timer, + }, }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - - // Bob escrows - const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( - bobExclOption, - bobOfferRules, - bobPayments, - ); - - t.throws(() => bobSeat.exercise(), /The covered call option is expired/); - - const bobPayout = await bobPayoutP; - const alicePayout = await alicePayoutP; - - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobPayout); - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - alicePayout, - ); - - // Alice gets back what she put in - moolaIssuer.getAmountOf(aliceMoolaPayout).then(async aliceMoolaAmount => { - t.deepEquals(aliceMoolaAmount, moola(3)); - await aliceMoolaPurse.deposit(aliceMoolaPayout); - t.deepEquals(aliceMoolaPurse.getCurrentAmount(), moola(3)); - }); + }); + const alicePayments = { UnderlyingAsset: aliceMoolaPayment }; + const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( + aliceInvite, + aliceProposal, + alicePayments, + ); + + // Alice makes an option + const option = aliceSeat.makeCallOption(); + timer.tick(); + + // Imagine that Alice sends the option to Bob for free (not done here + // since this test doesn't actually have separate vats/parties) + + // 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. - // Alice doesn't get what she wanted - simoleanIssuer.getAmountOf(aliceSimoleanPayout).then(async simoleanAmt => { - t.deepEquals(simoleanAmt, simoleans(0)); + const inviteIssuer = zoe.getInviteIssuer(); + const bobExclOption = await inviteIssuer.claim(option); + const { + extent: [optionExtent], + } = await inviteIssuer.getAmountOf(bobExclOption); + const { installationHandle } = zoe.getInstance(optionExtent.instanceHandle); + t.equal(installationHandle, coveredCallInstallationHandle); + t.equal(optionExtent.seatDesc, 'exerciseOption'); + t.ok(moolaR.amountMath.isEqual(optionExtent.underlyingAsset, moola(3))); + t.ok(simoleanR.amountMath.isEqual(optionExtent.strikePrice, simoleans(7))); + t.equal(optionExtent.expirationDate, 1); + t.deepEqual(optionExtent.timerAuthority, timer); + + const bobPayments = { StrikePrice: bobSimoleanPayment }; + + const bobProposal = harden({ + want: { UnderlyingAsset: optionExtent.underlyingAsset }, + give: { StrikePrice: optionExtent.strikePrice }, + }); + + // Bob escrows + const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( + bobExclOption, + bobProposal, + bobPayments, + ); + + t.throws(() => bobSeat.exercise(), /The covered call option is expired/); + + 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 back what she put in + t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(3)); + + // Alice doesn't get what she wanted + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), + simoleans(0), + ); + + // Alice deposits her winnings to ensure she can + await aliceMoolaPurse.deposit(aliceMoolaPayout); await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - t.deepEquals(aliceSimoleanPurse.getCurrentAmount(), simoleans(0)); - }); - // Bob deposits his winnings to ensure he can - await bobMoolaPurse.deposit(bobMoolaPayout); - await bobSimoleanPurse.deposit(bobSimoleanPayout); + // Bob deposits his winnings to ensure he can + await bobMoolaPurse.deposit(bobMoolaPayout); + await bobSimoleanPurse.deposit(bobSimoleanPayout); - // Assert that the correct outcome was achieved. - // Alice had 3 moola and 0 simoleans. - // Bob had 0 moola and 7 simoleans. - t.deepEquals(bobMoolaPurse.getCurrentAmount(), moola(0)); - t.deepEquals(bobSimoleanPurse.getCurrentAmount(), simoleans(7)); + // 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)); + } catch (e) { + t.isNot(e, e, 'unexpected exception'); + } }); // Alice makes a covered call and escrows. She shares the invite to @@ -322,296 +272,269 @@ test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, a // offer description? test('zoe - coveredCall with swap for invite', async t => { t.plan(24); + try { + // Setup the environment + const timer = buildManualTimer(console.log); + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const zoe = makeZoe({ require }); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(coveredCallRoot); + + const coveredCallInstallationHandle = zoe.install(source, moduleFormat); + const { + source: swapSource, + moduleFormat: swapModuleFormat, + } = await bundleSource(atomicSwapRoot); + + const swapInstallationId = zoe.install(swapSource, swapModuleFormat); + + // Setup Alice + // Alice starts with 3 moola + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); + const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + + // Setup Bob + // Bob starts with nothing + const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + const bobBucksPurse = bucksR.issuer.makeEmptyPurse(); + + // Setup Dave + // Dave starts with 1 buck + const daveSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7)); + const daveBucksPayment = bucksR.mint.mintPayment(bucks(1)); + const daveMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const daveSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + const daveBucksPurse = bucksR.issuer.makeEmptyPurse(); + + // Alice creates a coveredCall instance of moola for simoleans + const issuerKeywordRecord = harden({ + UnderlyingAsset: moolaR.issuer, + StrikePrice: simoleanR.issuer, + }); + const aliceInvite = await zoe.makeInstance( + coveredCallInstallationHandle, + issuerKeywordRecord, + ); - // Setup the environment - const timer = buildManualTimer(console.log); - const { mints, issuers, moola, simoleans, bucks, amountMaths } = setup(); - const [moolaMint, simoleanMint, bucksMint] = mints; - const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; - const zoe = makeZoe({ require }); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(coveredCallRoot); - - const coveredCallInstallationHandle = zoe.install(source, moduleFormat); - const { - source: swapSource, - moduleFormat: swapModuleFormat, - } = await bundleSource(atomicSwapRoot); - - const swapInstallationId = zoe.install(swapSource, swapModuleFormat); - - // Setup Alice - // Alice starts with 3 moola - const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - - // Setup Bob - // Bob starts with nothing - const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - const bobBucksPurse = bucksIssuer.makeEmptyPurse(); - - // Setup Dave - // Dave starts with 1 buck - const daveSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); - const daveBucksPayment = bucksMint.mintPayment(bucks(1)); - const daveMoolaPurse = moolaIssuer.makeEmptyPurse(); - const daveSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - const daveBucksPurse = bucksIssuer.makeEmptyPurse(); - - // Alice creates a coveredCall instance of moola for simoleans - const terms = harden({ - issuers: [moolaIssuer, simoleanIssuer], - }); - const aliceInvite = await zoe.makeInstance( - coveredCallInstallationHandle, - terms, - ); - - // Alice escrows with Zoe. She specifies her offer offerRules, - // which include an offer description as well as the exit - // offerRules. In this case, she choses an exit condition of after - // the deadline of "100" according to a particular timer. This is - // meant to be something far in the future, and will not be - // reached in this test. - - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 100, // we will not reach this - timer, - }, - }); - const alicePayments = [aliceMoolaPayment, undefined]; - const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( - aliceInvite, - aliceOfferRules, - alicePayments, - ); - - // Alice makes an option. - const option = aliceSeat.makeCallOption(); - - // Imagine that Alice sends the invite to Bob (not done here since - // this test doesn't actually have separate vats/parties) - - // Bob inspects the invite payment and checks its information against the - // questions that he has about whether it is worth being a counter - // 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 inviteAmountMath = inviteIssuer.getAmountMath(); - const bobExclOption = await inviteIssuer.claim(option); - const optionAmount = await inviteIssuer.getAmountOf(bobExclOption); - const optionDesc = optionAmount.extent[0]; - const { installationHandle } = zoe.getInstance(optionDesc.instanceHandle); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionDesc.seatDesc, 'exerciseOption'); - t.ok(amountMaths[0].isEqual(optionDesc.underlyingAsset, moola(3))); - t.ok(amountMaths[1].isEqual(optionDesc.strikePrice, simoleans(7))); - t.equal(optionDesc.expirationDate, 100); - t.deepEqual(optionDesc.timerAuthority, timer); - - // Let's imagine that Bob wants to create a swap to trade this - // invite for bucks. - const bobSwapInvite = await zoe.makeInstance(swapInstallationId, { - issuers: harden([inviteIssuer, bucksIssuer]), - }); - - // Bob wants to swap an invite with the same amount as his - // current invite from Alice. He wants 1 buck in return. - const bobOfferRulesSwap = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: await inviteIssuer.getAmountOf(bobExclOption), - }, - { - kind: 'wantAtLeast', - amount: bucks(1), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - - const bobPayments = [bobExclOption, undefined]; - - // Bob escrows his option in the swap - const { seat: bobSwapSeat, payout: bobPayoutP } = await zoe.redeem( - bobSwapInvite, - bobOfferRulesSwap, - bobPayments, - ); - - // Bob makes an offer to the swap with his "higher order" invite - const daveSwapInvite = await bobSwapSeat.makeFirstOffer(); - - // Bob passes the swap invite to Dave and tells him the - // optionAmounts (basically, the description of the option) - - const { - extent: [{ instanceHandle: swapInstanceHandle }], - } = await inviteIssuer.getAmountOf(daveSwapInvite); - - const { - installationHandle: daveSwapInstallId, - terms: daveSwapTerms, - } = zoe.getInstance(swapInstanceHandle); - - // Dave is looking to buy the option to trade his 7 simoleans for - // 3 moola, and is willing to pay 1 buck for the option. He - // checks that this instance matches what he wants - - // Did this swap use the correct swap installation? Yes - t.equal(daveSwapInstallId, swapInstallationId); - - // Is this swap for the correct issuers and has no other terms? Yes - t.ok( - sameStructure( - daveSwapTerms, - harden({ - issuers: harden([inviteIssuer, bucksIssuer]), - }), - ), - ); - - // What's actually up to be bought? Is it the kind of invite that - // Dave wants? What's the price for that invite? Is it acceptable - // to Dave? Bob can tell Dave this out of band, and if he lies, - // Dave's offer will be rejected and he will get a refund. Dave - // knows this to be true because he knows the swap. - - // Dave escrows his 1 buck with Zoe and forms his offer offerRules - const daveSwapOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: optionAmount, - }, - { - kind: 'offerAtMost', - amount: bucks(1), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - - const daveSwapPayments = [undefined, daveBucksPayment]; - const { seat: daveSwapSeat, payout: daveSwapPayoutP } = await zoe.redeem( - daveSwapInvite, - daveSwapOfferRules, - daveSwapPayments, - ); - - const daveSwapOutcome = await daveSwapSeat.matchOffer(); - t.equals( - daveSwapOutcome, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - - const daveSwapPayout = await daveSwapPayoutP; - const [daveOption, daveBucksPayout] = await Promise.all(daveSwapPayout); - - // Dave exercises his option by making an offer to the covered - // call. First, he escrows with Zoe. - - const daveCoveredCallOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), + // Alice escrows with Zoe. She specifies her proposal, + // which includes the amounts she gives and wants as well as the exit + // conditions. In this case, she choses an exit condition of after + // the deadline of "100" according to a particular timer. This is + // meant to be something far in the future, and will not be + // reached in this test. + + const aliceProposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { + afterDeadline: { + deadline: 100, // we will not reach this + timer, + }, }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const daveCoveredCallPayments = harden([undefined, daveSimoleanPayment]); - const { - seat: daveCoveredCallSeat, - payout: daveCoveredCallPayoutP, - } = await zoe.redeem( - daveOption, - daveCoveredCallOfferRules, - daveCoveredCallPayments, - ); - - const daveCoveredCallOutcome = await daveCoveredCallSeat.exercise(); - t.equals( - daveCoveredCallOutcome, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - - // Dave should get 3 moola, Bob should get 1 buck, and Alice - // get 7 simoleans - const daveCoveredCallResult = await daveCoveredCallPayoutP; - const [daveMoolaPayout, daveSimoleanPayout] = await Promise.all( - daveCoveredCallResult, - ); - const aliceResult = await alicePayoutP; - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - aliceResult, - ); - const bobResult = await bobPayoutP; - const [bobInvitePayout, bobBucksPayout] = await Promise.all(bobResult); - - moolaIssuer.getAmountOf(daveMoolaPayout).then(async daveMoolaAmount => { - t.deepEquals(daveMoolaAmount, moola(3)); + }); + const alicePayments = { UnderlyingAsset: aliceMoolaPayment }; + const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( + aliceInvite, + aliceProposal, + alicePayments, + ); + + // Alice makes an option. + const option = aliceSeat.makeCallOption(); + + // Imagine that Alice sends the invite to Bob (not done here since + // this test doesn't actually have separate vats/parties) + + // Bob inspects the invite payment and checks its information against the + // questions that he has about whether it is worth being a counter + // 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 inviteAmountMath = inviteIssuer.getAmountMath(); + const bobExclOption = await inviteIssuer.claim(option); + const optionAmount = await inviteIssuer.getAmountOf(bobExclOption); + const optionDesc = optionAmount.extent[0]; + const { installationHandle } = zoe.getInstance(optionDesc.instanceHandle); + t.equal(installationHandle, coveredCallInstallationHandle); + t.equal(optionDesc.seatDesc, '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.deepEqual(optionDesc.timerAuthority, timer); + + // Let's imagine that Bob wants to create a swap to trade this + // invite for bucks. + const swapIssuerKeywordRecord = harden({ + Asset: inviteIssuer, + Price: bucksR.issuer, + }); + const bobSwapInvite = await zoe.makeInstance( + swapInstallationId, + swapIssuerKeywordRecord, + ); + + // Bob wants to swap an invite with the same amount as his + // current invite from Alice. He wants 1 buck in return. + const bobProposalSwap = harden({ + give: { Asset: await inviteIssuer.getAmountOf(bobExclOption) }, + want: { Price: bucks(1) }, + }); + + const bobPayments = harden({ Asset: bobExclOption }); + + // Bob escrows his option in the swap + const { seat: bobSwapSeat, payout: bobPayoutP } = await zoe.redeem( + bobSwapInvite, + bobProposalSwap, + bobPayments, + ); + + // Bob makes an offer to the swap with his "higher order" invite + const daveSwapInvite = await bobSwapSeat.makeFirstOffer(); + + // Bob passes the swap invite to Dave and tells him the + // optionAmounts (basically, the description of the option) + + const { + extent: [{ instanceHandle: swapInstanceHandle }], + } = await inviteIssuer.getAmountOf(daveSwapInvite); + + const { + installationHandle: daveSwapInstallId, + issuerKeywordRecord: daveSwapIssuers, + } = zoe.getInstance(swapInstanceHandle); + + // Dave is looking to buy the option to trade his 7 simoleans for + // 3 moola, and is willing to pay 1 buck for the option. He + // checks that this instance matches what he wants + + // Did this swap use the correct swap installation? Yes + t.equal(daveSwapInstallId, swapInstallationId); + + // Is this swap for the correct issuers and has no other terms? Yes + t.ok( + sameStructure( + daveSwapIssuers, + harden({ + Asset: inviteIssuer, + Price: bucksR.issuer, + }), + ), + ); + + // What's actually up to be bought? Is it the kind of invite that + // Dave wants? What's the price for that invite? Is it acceptable + // to Dave? Bob can tell Dave this out of band, and if he lies, + // Dave's offer will be rejected and he will get a refund. Dave + // knows this to be true because he knows the swap. + + // Dave escrows his 1 buck with Zoe and forms his proposal + const daveSwapProposal = harden({ + want: { Asset: optionAmount }, + give: { Price: bucks(1) }, + }); + + const daveSwapPayments = harden({ Price: daveBucksPayment }); + const { seat: daveSwapSeat, payout: daveSwapPayoutP } = await zoe.redeem( + daveSwapInvite, + daveSwapProposal, + daveSwapPayments, + ); + + const daveSwapOutcome = await daveSwapSeat.matchOffer(); + t.equals( + daveSwapOutcome, + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + const daveSwapPayout = await daveSwapPayoutP; + const daveOption = await daveSwapPayout.Asset; + const daveBucksPayout = await daveSwapPayout.Price; + + // Dave exercises his option by making an offer to the covered + // call. First, he escrows with Zoe. + + const daveCoveredCallProposal = harden({ + want: { UnderlyingAsset: moola(3) }, + give: { StrikePrice: simoleans(7) }, + }); + const daveCoveredCallPayments = harden({ + StrikePrice: daveSimoleanPayment, + }); + const { + seat: daveCoveredCallSeat, + payout: daveCoveredCallPayoutP, + } = await zoe.redeem( + daveOption, + daveCoveredCallProposal, + daveCoveredCallPayments, + ); + + const daveCoveredCallOutcome = await daveCoveredCallSeat.exercise(); + t.equals( + daveCoveredCallOutcome, + 'The offer has been accepted. Once the contract has been completed, please check your payout', + ); + + // Dave should get 3 moola, Bob should get 1 buck, and Alice + // get 7 simoleans + const daveCoveredCallResult = await daveCoveredCallPayoutP; + const daveMoolaPayout = await daveCoveredCallResult.UnderlyingAsset; + const daveSimoleanPayout = await daveCoveredCallResult.StrikePrice; + const aliceResult = await alicePayoutP; + const aliceMoolaPayout = await aliceResult.UnderlyingAsset; + const aliceSimoleanPayout = await aliceResult.StrikePrice; + const bobResult = await bobPayoutP; + const bobInvitePayout = await bobResult.Asset; + const bobBucksPayout = await bobResult.Price; + + t.deepEquals(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(daveSimoleanPayout), + simoleans(0), + ); + + t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), + simoleans(7), + ); + + t.deepEquals( + await inviteIssuer.getAmountOf(bobInvitePayout), + inviteAmountMath.getEmpty(), + ); + t.deepEquals(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1)); + + // Alice deposits her payouts + await aliceMoolaPurse.deposit(aliceMoolaPayout); + await aliceSimoleanPurse.deposit(aliceSimoleanPayout); + + // Bob deposits his payouts + await bobBucksPurse.deposit(bobBucksPayout); + + // Dave deposits his payouts await daveMoolaPurse.deposit(daveMoolaPayout); - t.equals(daveMoolaPurse.getCurrentAmount().extent, 3); - }); - simoleanIssuer.getAmountOf(daveSimoleanPayout).then(async daveSimAmount => { - t.deepEquals(daveSimAmount, simoleans(0)); await daveSimoleanPurse.deposit(daveSimoleanPayout); - t.equals(daveSimoleanPurse.getCurrentAmount().extent, 0); - }); + await daveBucksPurse.deposit(daveBucksPayout); - moolaIssuer.getAmountOf(aliceMoolaPayout).then(async aliceMoolaAmount => { - t.deepEquals(aliceMoolaAmount, moola(0)); - await aliceMoolaPurse.deposit(aliceMoolaPayout); t.equals(aliceMoolaPurse.getCurrentAmount().extent, 0); - }); - - simoleanIssuer.getAmountOf(aliceSimoleanPayout).then(async aliceSimAmount => { - t.deepEquals(aliceSimAmount, simoleans(7)); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); - }); - inviteIssuer.getAmountOf(bobInvitePayout).then(bobInviteAmount => { - t.deepEquals(bobInviteAmount, inviteAmountMath.getEmpty()); - }); - bucksIssuer.getAmountOf(bobBucksPayout).then(async bobBucksAmount => { - t.deepEquals(bobBucksAmount, bucks(1)); - await bobBucksPurse.deposit(bobBucksPayout); - t.equals(bobBucksPurse.getCurrentAmount().extent, 1); - }); - // Dave deposits his payouts - await daveBucksPurse.deposit(daveBucksPayout); - t.equals(daveBucksPurse.getCurrentAmount().extent, 0); + t.equals(bobMoolaPurse.getCurrentAmount().extent, 0); + t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); + t.equals(bobBucksPurse.getCurrentAmount().extent, 1); - t.equals(bobMoolaPurse.getCurrentAmount().extent, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); + t.equals(daveMoolaPurse.getCurrentAmount().extent, 3); + t.equals(daveSimoleanPurse.getCurrentAmount().extent, 0); + t.equals(daveBucksPurse.getCurrentAmount().extent, 0); + } catch (e) { + t.isNot(e, e, 'unexpected exception'); + } }); // Alice makes a covered call and escrows. She shares the invite to @@ -620,313 +543,283 @@ test('zoe - coveredCall with swap for invite', async t => { // wants in his offer description in the second covered call? test('zoe - coveredCall with coveredCall for invite', async t => { t.plan(31); + try { + // Setup the environment + const timer = buildManualTimer(console.log); + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const zoe = makeZoe({ require }); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(coveredCallRoot); + + const coveredCallInstallationHandle = zoe.install(source, moduleFormat); + + // Setup Alice + // Alice starts with 3 moola + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3)); + const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + + // Setup Bob + // Bob starts with nothing + const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + const bobBucksPurse = bucksR.issuer.makeEmptyPurse(); + + // Setup Dave + // Dave starts with 1 buck and 7 simoleans + const daveSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7)); + const daveBucksPayment = bucksR.mint.mintPayment(bucks(1)); + const daveMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const daveSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); + const daveBucksPurse = bucksR.issuer.makeEmptyPurse(); + + // Alice creates a coveredCall instance of moola for simoleans + const issuerKeywordRecord = harden({ + UnderlyingAsset: moolaR.issuer, + StrikePrice: simoleanR.issuer, + }); + const aliceCoveredCallInvite = await zoe.makeInstance( + coveredCallInstallationHandle, + issuerKeywordRecord, + ); - // Setup the environment - const timer = buildManualTimer(console.log); - const { mints, issuers, moola, simoleans, bucks, amountMaths } = setup(); - const [moolaMint, simoleanMint, bucksMint] = mints; - const [moolaIssuer, simoleanIssuer, bucksIssuer] = issuers; - const zoe = makeZoe({ require }); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(coveredCallRoot); - - const coveredCallInstallationHandle = zoe.install(source, moduleFormat); - - // Setup Alice - // Alice starts with 3 moola - const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - - // Setup Bob - // Bob starts with nothing - const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - const bobBucksPurse = bucksIssuer.makeEmptyPurse(); - - // Setup Dave - // Dave starts with 1 buck and 7 simoleans - const daveSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); - const daveBucksPayment = bucksMint.mintPayment(bucks(1)); - const daveMoolaPurse = moolaIssuer.makeEmptyPurse(); - const daveSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - const daveBucksPurse = bucksIssuer.makeEmptyPurse(); - - // Alice creates a coveredCall instance of moola for simoleans - const terms = harden({ - issuers: [moolaIssuer, simoleanIssuer], - }); - const aliceCoveredCallInvite = await zoe.makeInstance( - coveredCallInstallationHandle, - terms, - ); - - // Alice escrows with Zoe. She specifies her offer offerRules, - // which include an offer description as well as the exit - // offerRules. In this case, she choses an exit condition of after - // the deadline of "100" according to a particular timer. This is - // meant to be something far in the future, and will not be - // reached in this test. - - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(3), - }, - { - kind: 'wantAtLeast', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 100, // we will not reach this - timer, - }, - }); - const alicePayments = [aliceMoolaPayment, undefined]; - const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( - aliceCoveredCallInvite, - aliceOfferRules, - alicePayments, - ); - - // Alice makes a call option, which is an invite to join the - // covered call contract - const option = await aliceSeat.makeCallOption(); - - // Imagine that Alice sends the invite to Bob as well as the - // instanceHandle (not done here since this test doesn't actually have - // separate vats/parties) - - // Bob inspects the invite payment and checks its information against the - // questions that he has about whether it is worth being a counter - // 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 inviteAmountMath = inviteIssuer.getAmountMath(); - const bobExclOption = await inviteIssuer.claim(option); - const bobExclAmount = await inviteIssuer.getAmountOf(bobExclOption); - const optionExtent = bobExclAmount.extent[0]; - const { installationHandle } = zoe.getInstance(optionExtent.instanceHandle); - t.equal(installationHandle, coveredCallInstallationHandle); - t.equal(optionExtent.seatDesc, 'exerciseOption'); - t.ok(amountMaths[0].isEqual(optionExtent.underlyingAsset, moola(3))); - t.ok(amountMaths[1].isEqual(optionExtent.strikePrice, simoleans(7))); - t.equal(optionExtent.expirationDate, 100); - t.deepEqual(optionExtent.timerAuthority, timer); - - // Let's imagine that Bob wants to create another coveredCall, but - // this time to trade this invite for bucks. - const bobInviteForSecondCoveredCall = await zoe.makeInstance( - coveredCallInstallationHandle, - harden({ - issuers: [inviteIssuer, bucksIssuer], - }), - ); - - // Bob wants to swap an invite with the same amount as his - // current invite from Alice. He wants 1 buck in return. - const bobOfferRulesSecondCoveredCall = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: await inviteIssuer.getAmountOf(bobExclOption), + // Alice escrows with Zoe. She specifies her proposal, + // which include what she wants and gives as well as the exit + // condition. In this case, she choses an exit condition of after + // the deadline of "100" according to a particular timer. This is + // meant to be something far in the future, and will not be + // reached in this test. + + const aliceProposal = harden({ + give: { UnderlyingAsset: moola(3) }, + want: { StrikePrice: simoleans(7) }, + exit: { + afterDeadline: { + deadline: 100, // we will not reach this + timer, + }, }, - { - kind: 'wantAtLeast', - amount: bucks(1), + }); + const alicePayments = { UnderlyingAsset: aliceMoolaPayment }; + const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( + aliceCoveredCallInvite, + aliceProposal, + alicePayments, + ); + + // Alice makes a call option, which is an invite to join the + // covered call contract + const option = await aliceSeat.makeCallOption(); + + // Imagine that Alice sends the invite to Bob as well as the + // instanceHandle (not done here since this test doesn't actually have + // separate vats/parties) + + // Bob inspects the invite payment and checks its information against the + // questions that he has about whether it is worth being a counter + // 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 inviteAmountMath = inviteIssuer.getAmountMath(); + const bobExclOption = await inviteIssuer.claim(option); + const { + extent: [optionExtent], + } = await inviteIssuer.getAmountOf(bobExclOption); + const { installationHandle } = zoe.getInstance(optionExtent.instanceHandle); + t.equal(installationHandle, coveredCallInstallationHandle); + t.equal(optionExtent.seatDesc, 'exerciseOption'); + t.ok(moolaR.amountMath.isEqual(optionExtent.underlyingAsset, moola(3))); + t.ok(simoleanR.amountMath.isEqual(optionExtent.strikePrice, simoleans(7))); + t.equal(optionExtent.expirationDate, 100); + t.deepEqual(optionExtent.timerAuthority, timer); + + // Let's imagine that Bob wants to create another coveredCall, but + // this time to trade this invite for bucks. + const issuerKeywordRecord2 = harden({ + UnderlyingAsset: inviteIssuer, + StrikePrice: bucksR.issuer, + }); + const bobInviteForSecondCoveredCall = await zoe.makeInstance( + coveredCallInstallationHandle, + issuerKeywordRecord2, + ); + + // Bob wants to swap an invite with the same amount as his + // current invite from Alice. He wants 1 buck in return. + const bobProposalSecondCoveredCall = harden({ + give: { UnderlyingAsset: await inviteIssuer.getAmountOf(bobExclOption) }, + want: { StrikePrice: bucks(1) }, + exit: { + afterDeadline: { + deadline: 100, // we will not reach this + timer, + }, }, - ], - exitRule: { - kind: 'afterDeadline', - deadline: 100, // we will not reach this + }); + + const bobPayments = { UnderlyingAsset: bobExclOption }; + + // Bob escrows his invite + const { + seat: bobSecondCoveredCallSeat, + payout: bobPayoutP, + } = await zoe.redeem( + bobInviteForSecondCoveredCall, + bobProposalSecondCoveredCall, + bobPayments, + ); + + // Bob makes an offer to the swap with his "higher order" option + const inviteForDave = await bobSecondCoveredCallSeat.makeCallOption(); + + // Bob passes the higher order invite and + // optionAmounts to Dave + + // Dave is looking to buy the option to trade his 7 simoleans for + // 3 moola, and is willing to pay 1 buck for the option. He + // checks that this invite matches what he wants + const daveExclOption = await inviteIssuer.claim(inviteForDave); + const { + extent: [daveOptionExtent], + } = await inviteIssuer.getAmountOf(daveExclOption); + const { + installationHandle: daveOptionInstallationHandle, + } = zoe.getInstance(daveOptionExtent.instanceHandle); + t.equal(daveOptionInstallationHandle, coveredCallInstallationHandle); + t.equal(daveOptionExtent.seatDesc, 'exerciseOption'); + t.ok(bucksR.amountMath.isEqual(daveOptionExtent.strikePrice, bucks(1))); + t.equal(daveOptionExtent.expirationDate, 100); + t.deepEqual(daveOptionExtent.timerAuthority, timer); + + // What about the underlying asset (the other option)? + t.equal( + daveOptionExtent.underlyingAsset.extent[0].seatDesc, + 'exerciseOption', + ); + t.equal(daveOptionExtent.underlyingAsset.extent[0].expirationDate, 100); + t.ok( + simoleanR.amountMath.isEqual( + daveOptionExtent.underlyingAsset.extent[0].strikePrice, + simoleans(7), + ), + ); + t.deepEqual( + daveOptionExtent.underlyingAsset.extent[0].timerAuthority, timer, - }, - }); - - const bobPayments = [bobExclOption, undefined]; - - // Bob escrows his invite - const { - seat: bobSecondCoveredCallSeat, - payout: bobPayoutP, - } = await zoe.redeem( - bobInviteForSecondCoveredCall, - bobOfferRulesSecondCoveredCall, - bobPayments, - ); - - // Bob makes an offer to the swap with his "higher order" option - const inviteForDave = await bobSecondCoveredCallSeat.makeCallOption(); - - // Bob passes the higher order invite and - // optionAmounts to Dave - - // Dave is looking to buy the option to trade his 7 simoleans for - // 3 moola, and is willing to pay 1 buck for the option. He - // checks that this invite matches what he wants - const daveExclOption = await inviteIssuer.claim(inviteForDave); - const daveOptionAmount = await inviteIssuer.getAmountOf(daveExclOption); - const daveOptionExtent = daveOptionAmount.extent[0]; - const { installationHandle: daveOptionInstallationHandle } = zoe.getInstance( - daveOptionExtent.instanceHandle, - ); - t.equal(daveOptionInstallationHandle, coveredCallInstallationHandle); - t.equal(daveOptionExtent.seatDesc, 'exerciseOption'); - t.ok(amountMaths[2].isEqual(daveOptionExtent.strikePrice, bucks(1))); - t.equal(daveOptionExtent.expirationDate, 100); - t.deepEqual(daveOptionExtent.timerAuthority, timer); - - // What about the underlying asset (the other option)? - t.equal( - daveOptionExtent.underlyingAsset.extent[0].seatDesc, - 'exerciseOption', - ); - t.equal(daveOptionExtent.underlyingAsset.extent[0].expirationDate, 100); - t.ok( - amountMaths[1].isEqual( - daveOptionExtent.underlyingAsset.extent[0].strikePrice, + ); + + // Dave's planned proposal + const daveProposalCoveredCall = harden({ + want: { UnderlyingAsset: daveOptionExtent.underlyingAsset }, + give: { StrikePrice: bucks(1) }, + }); + + // Dave escrows his 1 buck with Zoe and forms his proposal + + const daveSecondCoveredCallPayments = { StrikePrice: daveBucksPayment }; + const { + seat: daveSecondCoveredCallSeat, + payout: daveSecondCoveredCallPayoutP, + } = await zoe.redeem( + daveExclOption, + daveProposalCoveredCall, + daveSecondCoveredCallPayments, + ); + const daveSecondCoveredCallOutcome = daveSecondCoveredCallSeat.exercise(); + t.equals( + daveSecondCoveredCallOutcome, + 'The offer has been accepted. Once the contract has been completed, please check your payout', + `dave second offer accepted`, + ); + + const daveSecondCoveredCallPayout = await daveSecondCoveredCallPayoutP; + + const firstCoveredCallInvite = await daveSecondCoveredCallPayout.UnderlyingAsset; + const daveBucksPayout = await daveSecondCoveredCallPayout.StrikePrice; + + // Dave exercises his option by making an offer to the covered + // call. First, he escrows with Zoe. + + const daveFirstCoveredCallProposal = harden({ + want: { UnderlyingAsset: moola(3) }, + give: { StrikePrice: simoleans(7) }, + }); + const daveFirstCoveredCallPayments = harden({ + StrikePrice: daveSimoleanPayment, + }); + const { + seat: daveFirstCoveredCallSeat, + payout: daveFirstCoveredCallPayoutP, + } = await zoe.redeem( + firstCoveredCallInvite, + daveFirstCoveredCallProposal, + daveFirstCoveredCallPayments, + ); + + const daveFirstCoveredCallOutcome = daveFirstCoveredCallSeat.exercise(); + t.equals( + daveFirstCoveredCallOutcome, + 'The offer has been accepted. Once the contract has been completed, please check your payout', + `dave first offer accepted`, + ); + + // Dave should get 3 moola, Bob should get 1 buck, and Alice + // get 7 simoleans + const daveFirstCoveredCallResult = await daveFirstCoveredCallPayoutP; + const aliceResult = await alicePayoutP; + const bobResult = await bobPayoutP; + + const daveMoolaPayout = await daveFirstCoveredCallResult.UnderlyingAsset; + const daveSimoleanPayout = await daveFirstCoveredCallResult.StrikePrice; + + const aliceMoolaPayout = await aliceResult.UnderlyingAsset; + const aliceSimoleanPayout = await aliceResult.StrikePrice; + + const bobInvitePayout = await bobResult.UnderlyingAsset; + const bobBucksPayout = await bobResult.StrikePrice; + + t.deepEquals(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(daveSimoleanPayout), + simoleans(0), + ); + + t.deepEquals(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), simoleans(7), - ), - ); - t.deepEqual(daveOptionExtent.underlyingAsset.extent[0].timerAuthority, timer); - - // Dave's planned offerRules - const daveOfferRulesCoveredCall = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: daveOptionExtent.underlyingAsset, - }, - { - kind: 'offerAtMost', - amount: bucks(1), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - - // Dave escrows his 1 buck with Zoe and forms his offer offerRules - - const daveSecondCoveredCallPayments = [undefined, daveBucksPayment]; - const { - seat: daveSecondCoveredCallSeat, - payout: daveSecondCoveredCallPayoutP, - } = await zoe.redeem( - daveExclOption, - daveOfferRulesCoveredCall, - daveSecondCoveredCallPayments, - ); - const daveSecondCoveredCallOutcome = daveSecondCoveredCallSeat.exercise(); - t.equals( - daveSecondCoveredCallOutcome, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - - const [ - firstCoveredCallInviteP, - daveBucksPayoutP, - ] = await daveSecondCoveredCallPayoutP; - - const firstCoveredCallInvite = await firstCoveredCallInviteP; - const daveBucksPayout = await daveBucksPayoutP; - - // Dave exercises his option by making an offer to the covered - // call. First, he escrows with Zoe. - - const daveFirstCoveredCallOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(3), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, - }); - const daveFirstCoveredCallPayments = [undefined, daveSimoleanPayment]; - const { - seat: daveFirstCoveredCallSeat, - payout: daveFirstCoveredCallPayoutP, - } = await zoe.redeem( - firstCoveredCallInvite, - daveFirstCoveredCallOfferRules, - daveFirstCoveredCallPayments, - ); - - const daveFirstCoveredCallOutcome = daveFirstCoveredCallSeat.exercise(); - t.equals( - daveFirstCoveredCallOutcome, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - - // Dave should get 3 moola, Bob should get 1 buck, and Alice - // get 7 simoleans - const daveFirstCoveredCallResult = await daveFirstCoveredCallPayoutP; - const aliceResult = await alicePayoutP; - const bobResult = await bobPayoutP; - - const [daveMoolaPayout, daveSimoleanPayout] = await Promise.all( - daveFirstCoveredCallResult, - ); - - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - aliceResult, - ); - - const [bobInvitePayout, bobBucksPayout] = await Promise.all(bobResult); - - const daveMoolaAmount = await moolaIssuer.getAmountOf(daveMoolaPayout); - t.deepEquals(daveMoolaAmount, moola(3)); - const daveSimoleanAmount = await simoleanIssuer.getAmountOf( - daveSimoleanPayout, - ); - t.deepEquals(daveSimoleanAmount, simoleans(0)); - - const aliceMoolaAmount = await moolaIssuer.getAmountOf(aliceMoolaPayout); - t.deepEquals(aliceMoolaAmount, moola(0)); - const aliceSimoleanAmount = await simoleanIssuer.getAmountOf( - aliceSimoleanPayout, - ); - t.deepEquals(aliceSimoleanAmount, simoleans(7)); - - inviteIssuer.getAmountOf(bobInvitePayout).then(bobInviteAmount => { - t.deepEquals(bobInviteAmount, inviteAmountMath.getEmpty()); - }); - bucksIssuer.getAmountOf(bobBucksPayout).then(bobBucksAmount => { - t.deepEquals(bobBucksAmount, bucks(1)); - }); - - // Alice deposits her payouts - await aliceMoolaPurse.deposit(aliceMoolaPayout); - await aliceSimoleanPurse.deposit(aliceSimoleanPayout); - - // Bob deposits his payouts - await bobBucksPurse.deposit(bobBucksPayout); - - // Dave deposits his payouts - await daveMoolaPurse.deposit(daveMoolaPayout); - await daveSimoleanPurse.deposit(daveSimoleanPayout); - await daveBucksPurse.deposit(daveBucksPayout); - - t.equals(aliceMoolaPurse.getCurrentAmount().extent, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); - - t.equals(bobMoolaPurse.getCurrentAmount().extent, 0); - t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); - t.equals(bobBucksPurse.getCurrentAmount().extent, 1); - - t.equals(daveMoolaPurse.getCurrentAmount().extent, 3); - t.equals(daveSimoleanPurse.getCurrentAmount().extent, 0); - t.equals(daveBucksPurse.getCurrentAmount().extent, 0); + ); + + t.deepEquals( + await inviteIssuer.getAmountOf(bobInvitePayout), + inviteAmountMath.getEmpty(), + ); + t.deepEquals(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1)); + + // Alice deposits her payouts + await aliceMoolaPurse.deposit(aliceMoolaPayout); + await aliceSimoleanPurse.deposit(aliceSimoleanPayout); + + // Bob deposits his payouts + await bobBucksPurse.deposit(bobBucksPayout); + + // Dave deposits his payouts + await daveMoolaPurse.deposit(daveMoolaPayout); + await daveSimoleanPurse.deposit(daveSimoleanPayout); + await daveBucksPurse.deposit(daveBucksPayout); + + t.equals(aliceMoolaPurse.getCurrentAmount().extent, 0); + t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); + + t.equals(bobMoolaPurse.getCurrentAmount().extent, 0); + t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); + t.equals(bobBucksPurse.getCurrentAmount().extent, 1); + + t.equals(daveMoolaPurse.getCurrentAmount().extent, 3); + t.equals(daveSimoleanPurse.getCurrentAmount().extent, 0); + t.equals(daveBucksPurse.getCurrentAmount().extent, 0); + } catch (e) { + t.isNot(e, e, 'unexpected exception'); + } }); diff --git a/packages/zoe/test/unitTests/contracts/test-myFirstDapp.js b/packages/zoe/test/unitTests/contracts/test-myFirstDapp.js deleted file mode 100644 index 034fdf23456..00000000000 --- a/packages/zoe/test/unitTests/contracts/test-myFirstDapp.js +++ /dev/null @@ -1,308 +0,0 @@ -// 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 harden from '@agoric/harden'; - -import { makeZoe } from '../../../src/zoe'; -import { setup } from '../setupBasicMints'; - -const myFirstDappRoot = `${__dirname}/../../../src/contracts/myFirstDapp`; - -function makeRule(kind, amount) { - return { kind, amount }; -} - -function offerRule(amount) { - return makeRule('offerAtMost', amount); -} - -function wantRule(amount) { - return makeRule('wantAtLeast', amount); -} - -function exitRule(kind) { - return { kind }; -} - -test('myFirstDapp with valid offers', async t => { - t.plan(10); - - const { issuers, mints, amountMaths, moola, simoleans } = setup(); - const [moolaIssuer, simoleanIssuer] = issuers; - const [moolaMint, simoleanMint] = mints; - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(myFirstDappRoot); - - const installationHandle = zoe.install(source, moduleFormat); - - // 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 myFirstDapp instance and spreads the invite far and - // wide with instructions on how to use it. - const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { - issuers: [moolaIssuer, simoleanIssuer], - }); - inviteIssuer.getAmountOf(simonInvite).then(async simonInviteAmount => { - const { instanceHandle } = simonInviteAmount.extent[0]; - const { publicAPI } = zoe.getInstance(instanceHandle); - - const { invite: aliceInvite } = 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 aliceSellOrderOfferRules = harden({ - payoutRules: [offerRule(moola(3)), wantRule(simoleans(4))], - exitRule: exitRule('onDemand'), - }); - const alicePayments = [aliceMoolaPayment, undefined]; - const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( - aliceInvite, - aliceSellOrderOfferRules, - alicePayments, - ); - - // 4: Alice adds her sell order to the exchange - const aliceOfferResult = await aliceSeat.addOrder(); - const { invite: bobInvite } = publicAPI.makeInvite(); - - // 5: Bob decides to join. - const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - - const { - installationHandle: bobInstallationId, - terms: bobTerms, - } = zoe.getInstance(instanceHandle); - - t.equals(bobInstallationId, installationHandle); - t.deepEquals(bobTerms.issuers, [moolaIssuer, simoleanIssuer]); - - // Bob creates a buy order, saying that he wants exactly 3 moola, - // and is willing to pay up to 7 simoleans. - - const bobBuyOrderOfferRules = harden({ - payoutRules: [wantRule(moola(3)), offerRule(simoleans(7))], - exitRule: exitRule('onDemand'), - }); - const bobPayments = [undefined, bobSimoleanPayment]; - - // 6: Bob escrows with zoe - const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( - bobExclusiveInvite, - bobBuyOrderOfferRules, - bobPayments, - ); - - // 8: Bob submits the buy order to the exchange - const bobOfferResult = await bobSeat.addOrder(); - - t.equals( - bobOfferResult, - 'The offer has been accepted. Once the contract has been completed, please check your payout', - ); - t.equals( - aliceOfferResult, - '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, bobSimoleanPayout] = await Promise.all(bobPayout); - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - alicePayout, - ); - - // Alice gets paid at least what she wanted - const aliceSimoleanAmount = await simoleanIssuer.getAmountOf( - aliceSimoleanPayout, - ); - t.ok( - amountMaths[1].isGTE( - aliceSimoleanAmount, - aliceSellOrderOfferRules.payoutRules[1].amount, - ), - ); - - // Alice sold all of her moola - const aliceMoolaAmount = await moolaIssuer.getAmountOf(aliceMoolaPayout); - t.deepEquals(aliceMoolaAmount, 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().extent, 0); - t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); - t.equals(bobMoolaPurse.getCurrentAmount().extent, 3); - t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); - }); -}); - -test('myFirstDapp with multiple sell offers', async t => { - try { - const { issuers, mints, moola, simoleans } = setup(); - const [moolaIssuer, simoleanIssuer] = issuers; - const [moolaMint, simoleanMint] = mints; - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(myFirstDappRoot); - - const installationHandle = zoe.install(source, moduleFormat); - - // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(30)); - const aliceSimoleanPayment = simoleanMint.mintPayment(simoleans(30)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); - await aliceMoolaPurse.deposit(aliceMoolaPayment); - await aliceSimoleanPurse.deposit(aliceSimoleanPayment); - - // 1: Simon creates a myFirstDapp instance and spreads the invite far and - // wide with instructions on how to use it. - const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { - issuers: [moolaIssuer, simoleanIssuer], - }); - const simonInviteAmount = await inviteIssuer.getAmountOf(simonInvite); - const { instanceHandle } = simonInviteAmount.extent[0]; - const { publicAPI } = zoe.getInstance(instanceHandle); - - const { invite: aliceInvite1 } = 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 aliceSale1OrderOfferRules = harden({ - payoutRules: [offerRule(moola(3)), wantRule(simoleans(4))], - exitRule: exitRule('onDemand'), - }); - - const alicePayments = [aliceMoolaPurse.withdraw(moola(3)), undefined]; - const { seat: aliceSeat1 } = await zoe.redeem( - aliceInvite1, - aliceSale1OrderOfferRules, - alicePayments, - ); - - // 4: Alice adds her sell order to the exchange - const aliceOfferResult1 = aliceSeat1.addOrder(); - - // 5: Alice adds another sell order to the exchange - const aliceInvite2 = await inviteIssuer.claim( - publicAPI.makeInvite().invite, - ); - const aliceSale2OrderOfferRules = harden({ - payoutRules: [offerRule(moola(5)), wantRule(simoleans(8))], - exitRule: exitRule('onDemand'), - }); - const { seat: aliceSeat2 } = await zoe.redeem( - aliceInvite2, - aliceSale2OrderOfferRules, - [aliceMoolaPurse.withdraw(moola(5)), undefined], - ); - const aliceOfferResult2 = aliceSeat2.addOrder(); - - // 5: Alice adds a buy order to the exchange - const aliceInvite3 = await inviteIssuer.claim( - publicAPI.makeInvite().invite, - ); - const aliceBuyOrderOfferRules = harden({ - payoutRules: [wantRule(moola(29)), offerRule(simoleans(18))], - exitRule: exitRule('onDemand'), - }); - const { seat: aliceSeat3 } = await zoe.redeem( - aliceInvite3, - aliceBuyOrderOfferRules, - [undefined, aliceSimoleanPurse.withdraw(simoleans(18))], - ); - const aliceOfferResult3 = aliceSeat3.addOrder(); - - Promise.all(aliceOfferResult1, aliceOfferResult2, aliceOfferResult3).then( - () => { - const expectedBook = { - changed: {}, - buys: [[{ want: moola(29) }, { offer: simoleans(18) }]], - sells: [ - [{ offer: moola(3) }, { want: simoleans(4) }], - [{ offer: moola(5) }, { want: simoleans(8) }], - ], - }; - t.deepEquals(publicAPI.getBookOrders(), expectedBook); - }, - ); - } catch (e) { - t.assert(false, e); - console.log(e); - } finally { - t.end(); - } -}); - -test('myFirstDapp showPayoutRules', async t => { - t.plan(1); - - const { issuers, mints, moola, simoleans } = setup(); - const [moolaIssuer, simoleanIssuer] = issuers; - const [moolaMint] = mints; - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(myFirstDappRoot); - - const installationHandle = zoe.install(source, moduleFormat); - - // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); - // 1: Simon creates a myFirstDapp instance and spreads the invite far and - // wide with instructions on how to use it. - const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { - issuers: [moolaIssuer, simoleanIssuer], - }); - inviteIssuer.getAmountOf(simonInvite).then(simonInviteAmout => { - const { instanceHandle } = simonInviteAmout.extent[0]; - const { publicAPI } = zoe.getInstance(instanceHandle); - - const { invite: aliceInvite1, inviteHandle } = 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 aliceSale1OrderOfferRules = harden({ - payoutRules: [offerRule(moola(3)), wantRule(simoleans(4))], - exitRule: exitRule('onDemand'), - }); - - const alicePayments = [aliceMoolaPayment, undefined]; - zoe - .redeem(aliceInvite1, aliceSale1OrderOfferRules, alicePayments) - .then(aliceSeat1P => { - const { seat: aliceSeat1 } = aliceSeat1P; - - // 4: Alice adds her sell order to the exchange - aliceSeat1.addOrder(); - - const expected = [{ offer: moola(3) }, { want: simoleans(4) }]; - - t.deepEquals(publicAPI.getOffer(inviteHandle), expected); - }); - }); -}); diff --git a/packages/zoe/test/unitTests/contracts/test-publicAuction.js b/packages/zoe/test/unitTests/contracts/test-publicAuction.js index ae2ad4e3cef..113f4c4c641 100644 --- a/packages/zoe/test/unitTests/contracts/test-publicAuction.js +++ b/packages/zoe/test/unitTests/contracts/test-publicAuction.js @@ -6,38 +6,36 @@ import bundleSource from '@agoric/bundle-source'; import harden from '@agoric/harden'; import { makeZoe } from '../../../src/zoe'; -import { setup } from '../setupBasicMints'; +import { setup } from '../setupBasicMints2'; const publicAuctionRoot = `${__dirname}/../../../src/contracts/publicAuction`; test('zoe - secondPriceAuction w/ 3 bids', async t => { + t.plan(34); try { - const { issuers: originalIssuers, mints, moola, simoleans } = setup(); - const issuers = originalIssuers.slice(0, 2); - const [moolaMint, simoleanMint] = mints; - const [moolaIssuer, simoleanIssuer] = issuers; + const { moolaR, simoleanR, moola, simoleans } = setup(); const zoe = makeZoe({ require }); const inviteIssuer = zoe.getInviteIssuer(); // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(1)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(1)); + const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); // Setup Bob - const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(11)); - const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); + const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(11)); + const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); // Setup Carol - const carolSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); - const carolMoolaPurse = moolaIssuer.makeEmptyPurse(); - const carolSimoleanPurse = simoleanIssuer.makeEmptyPurse(); + const carolSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7)); + const carolMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const carolSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); // Setup Dave - const daveSimoleanPayment = simoleanMint.mintPayment(simoleans(5)); - const daveMoolaPurse = moolaIssuer.makeEmptyPurse(); - const daveSimoleanPurse = simoleanIssuer.makeEmptyPurse(); + const daveSimoleanPayment = simoleanR.mint.mintPayment(simoleans(5)); + const daveMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const daveSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); // Alice creates a secondPriceAuction instance @@ -46,35 +44,31 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { const installationHandle = zoe.install(source, moduleFormat); const numBidsAllowed = 3; - const aliceInvite = await zoe.makeInstance(installationHandle, { - issuers, - numBidsAllowed, + const issuerKeywordRecord = harden({ + Asset: moolaR.issuer, + Bid: simoleanR.issuer, }); + const terms = harden({ numBidsAllowed }); + const aliceInvite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + terms, + ); - const aliceInviteAmount = await inviteIssuer.getAmountOf(aliceInvite); - const [{ instanceHandle }] = aliceInviteAmount.extent; + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(aliceInvite); const { publicAPI } = zoe.getInstance(instanceHandle); // Alice escrows with zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(1), - }, - { - kind: 'wantAtLeast', - amount: simoleans(3), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const aliceProposal = harden({ + give: { Asset: moola(1) }, + want: { Bid: simoleans(3) }, }); - const alicePayments = [aliceMoolaPayment, undefined]; + const alicePayments = { Asset: aliceMoolaPayment }; const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( aliceInvite, - aliceOfferRules, + aliceProposal, alicePayments, ); @@ -85,45 +79,42 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { t.equals( aliceOfferResult, 'The offer has been accepted. Once the contract has been completed, please check your payout', + 'aliceOfferResult', ); // Alice spreads the invites far and wide and Bob decides he // wants to participate in the auction. const bobExclusiveInvite = await inviteIssuer.claim(bobInvite); - const bobExclInvAmount = await inviteIssuer.getAmountOf(bobExclusiveInvite); - const bobInviteExtent = bobExclInvAmount.extent[0]; + const { + extent: [bobInviteExtent], + } = await inviteIssuer.getAmountOf(bobExclusiveInvite); const { installationHandle: bobInstallationId, terms: bobTerms, + issuerKeywordRecord: bobIssuers, } = zoe.getInstance(bobInviteExtent.instanceHandle); - t.equals(bobInstallationId, installationHandle); - t.deepEquals(bobTerms.issuers, issuers); - t.deepEquals(bobInviteExtent.minimumBid, simoleans(3)); - t.deepEquals(bobInviteExtent.auctionedAssets, moola(1)); - - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(11), - }, - ], - exitRule: { - kind: 'onDemand', - }, + t.equals(bobInstallationId, installationHandle, 'bobInstallationId'); + t.deepEquals( + bobIssuers, + { Asset: moolaR.issuer, Bid: simoleanR.issuer }, + 'bobIssuers', + ); + t.equals(bobTerms.numBidsAllowed, 3, 'bobTerms'); + t.deepEquals(bobInviteExtent.minimumBid, simoleans(3), 'minimumBid'); + t.deepEquals(bobInviteExtent.auctionedAssets, moola(1), 'assets'); + + const bobProposal = harden({ + give: { Bid: simoleans(11) }, + want: { Asset: moola(1) }, }); - const bobPayments = [undefined, bobSimoleanPayment]; + const bobPayments = { Bid: bobSimoleanPayment }; // Bob escrows with zoe const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( bobExclusiveInvite, - bobOfferRules, + bobProposal, bobPayments, ); @@ -133,45 +124,46 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { t.equals( bobOfferResult, 'The offer has been accepted. Once the contract has been completed, please check your payout', + 'bobOfferResult', ); // Carol decides to bid for the one moola const carolExclusiveInvite = await inviteIssuer.claim(carolInvite); - const carolExclAmt = await inviteIssuer.getAmountOf(carolExclusiveInvite); - const carolInviteExtent = carolExclAmt.extent[0]; + const { + extent: [carolInviteExtent], + } = await inviteIssuer.getAmountOf(carolExclusiveInvite); const { installationHandle: carolInstallationId, terms: carolTerms, + issuerKeywordRecord: carolIssuers, } = zoe.getInstance(carolInviteExtent.instanceHandle); - t.equals(carolInstallationId, installationHandle); - t.deepEquals(carolTerms.issuers, issuers); - t.deepEquals(carolInviteExtent.minimumBid, simoleans(3)); - t.deepEquals(carolInviteExtent.auctionedAssets, moola(1)); - - const carolOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(7), - }, - ], - exitRule: { - kind: 'onDemand', - }, + t.equals(carolInstallationId, installationHandle, 'carolInstallationId'); + t.deepEquals( + carolIssuers, + { Asset: moolaR.issuer, Bid: simoleanR.issuer }, + 'carolIssuers', + ); + t.equals(carolTerms.numBidsAllowed, 3, 'carolTerms'); + t.deepEquals(carolInviteExtent.minimumBid, simoleans(3), 'carolMinimumBid'); + t.deepEquals( + carolInviteExtent.auctionedAssets, + moola(1), + 'carolAuctionedAssets', + ); + + const carolProposal = harden({ + give: { Bid: simoleans(7) }, + want: { Asset: moola(1) }, }); - const carolPayments = [undefined, carolSimoleanPayment]; + const carolPayments = { Bid: carolSimoleanPayment }; // Carol escrows with zoe const { seat: carolSeat, payout: carolPayoutP } = await zoe.redeem( carolExclusiveInvite, - carolOfferRules, + carolProposal, carolPayments, ); @@ -181,44 +173,41 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { t.equals( carolOfferResult, 'The offer has been accepted. Once the contract has been completed, please check your payout', + 'carolOfferResult', ); // Dave decides to bid for the one moola const daveExclusiveInvite = await inviteIssuer.claim(daveInvite); - const daveExclAmount = await inviteIssuer.getAmountOf(daveExclusiveInvite); - const daveInviteExtent = daveExclAmount.extent[0]; + const { + extent: [daveInviteExtent], + } = await inviteIssuer.getAmountOf(daveExclusiveInvite); const { installationHandle: daveInstallationId, terms: daveTerms, + issuerKeywordRecord: daveIssuers, } = zoe.getInstance(daveInviteExtent.instanceHandle); - t.equals(daveInstallationId, installationHandle); - t.deepEquals(daveTerms.issuers, issuers); - t.deepEquals(daveInviteExtent.minimumBid, simoleans(3)); - t.deepEquals(daveInviteExtent.auctionedAssets, moola(1)); - - const daveOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(5), - }, - ], - exitRule: { - kind: 'onDemand', - }, + t.equals(daveInstallationId, installationHandle, 'daveInstallationHandle'); + t.deepEquals( + daveIssuers, + { Asset: moolaR.issuer, Bid: simoleanR.issuer }, + 'daveIssuers', + ); + t.equals(daveTerms.numBidsAllowed, 3, 'bobTerms'); + t.deepEquals(daveInviteExtent.minimumBid, simoleans(3), 'daveMinimumBid'); + t.deepEquals(daveInviteExtent.auctionedAssets, moola(1), 'daveAssets'); + + const daveProposal = harden({ + give: { Bid: simoleans(5) }, + want: { Asset: moola(1) }, }); - const davePayments = [undefined, daveSimoleanPayment]; + const davePayments = { Bid: daveSimoleanPayment }; // Dave escrows with zoe const { seat: daveSeat, payout: davePayoutP } = await zoe.redeem( daveExclusiveInvite, - daveOfferRules, + daveProposal, davePayments, ); @@ -228,6 +217,7 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { t.equals( daveOfferResult, 'The offer has been accepted. Once the contract has been completed, please check your payout', + 'daveOfferResult', ); const aliceResult = await alicePayoutP; @@ -235,22 +225,31 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { const carolResult = await carolPayoutP; const daveResult = await davePayoutP; - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - aliceResult, - ); - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobResult); - const [carolMoolaPayout, carolSimoleanPayout] = await Promise.all( - carolResult, - ); - const [daveMoolaPayout, daveSimoleanPayout] = await Promise.all(daveResult); + const aliceMoolaPayout = await aliceResult.Asset; + const aliceSimoleanPayout = await aliceResult.Bid; + + 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 - const aliceSimAmt = await simoleanIssuer.getAmountOf(aliceSimoleanPayout); - t.deepEquals(aliceSimAmt, carolOfferRules.payoutRules[1].amount); + 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 - const aliceMoolaAmount = await moolaIssuer.getAmountOf(aliceMoolaPayout); - t.deepEquals(aliceMoolaAmount, moola(0)); + 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); @@ -258,28 +257,43 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { // Bob (the winner of the auction) gets the one moola and the // difference between his bid and the price back - const bobMoolaAmount = await moolaIssuer.getAmountOf(bobMoolaPayout); - t.deepEquals(bobMoolaAmount, moola(1)); - const bobSimAmount = await simoleanIssuer.getAmountOf(bobSimoleanPayout); - t.deepEquals(bobSimAmount, simoleans(4)); + 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 - const carolMoolaAmount = await moolaIssuer.getAmountOf(carolMoolaPayout); - t.deepEquals(carolMoolaAmount, moola(0)); - const carolSimAmt = await simoleanIssuer.getAmountOf(carolSimoleanPayout); - t.deepEquals(carolSimAmt, carolOfferRules.payoutRules[1].amount); + 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 - const daveSimAmount = await simoleanIssuer.getAmountOf(daveSimoleanPayout); - t.deepEquals(daveSimAmount, daveOfferRules.payoutRules[1].amount); + 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); @@ -307,29 +321,25 @@ test('zoe - secondPriceAuction w/ 3 bids', async t => { } catch (e) { t.assert(false, e); console.log(e); - } finally { - t.end(); } }); test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { + t.plan(10); try { - const { issuers: originalIssuers, mints, moola, simoleans } = setup(); - const issuers = originalIssuers.slice(0, 2); + const { moolaR, simoleanR, moola, simoleans } = setup(); const zoe = makeZoe({ require }); const inviteIssuer = zoe.getInviteIssuer(); - const [moolaMint, simoleanMint] = mints; - const [moolaIssuer, simoleanIssuer] = issuers; // Setup Alice - const aliceMoolaPayment = moolaMint.mintPayment(moola(1)); - const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); - const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); + const aliceMoolaPayment = moolaR.mint.mintPayment(moola(1)); + const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); // Setup Bob - const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(11)); - const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); - const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); + const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(11)); + const bobMoolaPurse = moolaR.issuer.makeEmptyPurse(); + const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse(); // Alice creates a secondPriceAuction instance @@ -338,36 +348,32 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { const installationHandle = zoe.install(source, moduleFormat); const numBidsAllowed = 3; - const aliceInvite = await zoe.makeInstance(installationHandle, { - issuers, - numBidsAllowed, + const issuerKeywordRecord = harden({ + Asset: moolaR.issuer, + Bid: simoleanR.issuer, }); - const aliceInviteAmount = await inviteIssuer.getAmountOf(aliceInvite); - const { instanceHandle } = aliceInviteAmount.extent[0]; + const terms = harden({ numBidsAllowed }); + const aliceInvite = await zoe.makeInstance( + installationHandle, + issuerKeywordRecord, + terms, + ); + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(aliceInvite); const { publicAPI } = zoe.getInstance(instanceHandle); // Alice escrows with zoe - const aliceOfferRules = harden({ - payoutRules: [ - { - kind: 'offerAtMost', - amount: moola(1), - }, - { - kind: 'wantAtLeast', - amount: simoleans(3), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const aliceProposal = harden({ + give: { Asset: moola(1) }, + want: { Bid: simoleans(3) }, }); - const alicePayments = [aliceMoolaPayment, undefined]; + const alicePayments = harden({ Asset: aliceMoolaPayment }); const { seat: aliceSeat, payout: alicePayoutP, cancelObj, - } = await zoe.redeem(aliceInvite, aliceOfferRules, alicePayments); + } = await zoe.redeem(aliceInvite, aliceProposal, alicePayments); // Alice initializes the auction const aliceOfferResult = await aliceSeat.sellAssets(); @@ -384,27 +390,16 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { // Alice gives Bob the invite - const bobOfferRules = harden({ - payoutRules: [ - { - kind: 'wantAtLeast', - amount: moola(1), - }, - { - kind: 'offerAtMost', - amount: simoleans(11), - }, - ], - exitRule: { - kind: 'onDemand', - }, + const bobProposal = harden({ + want: { Asset: moola(1) }, + give: { Bid: simoleans(11) }, }); - const bobPayments = [undefined, bobSimoleanPayment]; + const bobPayments = harden({ Bid: bobSimoleanPayment }); // Bob escrows with zoe const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( bobInvite, - bobOfferRules, + bobProposal, bobPayments, ); @@ -417,28 +412,33 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { const aliceResult = await alicePayoutP; const bobResult = await bobPayoutP; - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - aliceResult, - ); - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobResult); + const aliceMoolaPayout = await aliceResult.Asset; + const aliceSimoleanPayout = await aliceResult.Bid; + const bobMoolaPayout = await bobResult.Asset; + const bobSimoleanPayout = await bobResult.Bid; // Alice (the creator of the auction) gets back what she put in - const aliceMoolaAmount = await moolaIssuer.getAmountOf(aliceMoolaPayout); - t.deepEquals(aliceMoolaAmount, aliceOfferRules.payoutRules[0].amount); + t.deepEquals( + await moolaR.issuer.getAmountOf(aliceMoolaPayout), + aliceProposal.give.Asset, + ); // Alice didn't get any of what she wanted - const aliceSimAmt = await simoleanIssuer.getAmountOf(aliceSimoleanPayout); - t.deepEquals(aliceSimAmt, simoleans(0)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(aliceSimoleanPayout), + simoleans(0), + ); // Alice deposits her payout to ensure she can await aliceMoolaPurse.deposit(aliceMoolaPayout); await aliceSimoleanPurse.deposit(aliceSimoleanPayout); // Bob gets a refund - const bobMoolaAmount = await moolaIssuer.getAmountOf(bobMoolaPayout); - t.deepEquals(bobMoolaAmount, moola(0)); - const bobSimAmount = await simoleanIssuer.getAmountOf(bobSimoleanPayout); - t.deepEquals(bobSimAmount, bobOfferRules.payoutRules[1].amount); + t.deepEquals(await moolaR.issuer.getAmountOf(bobMoolaPayout), moola(0)); + t.deepEquals( + await simoleanR.issuer.getAmountOf(bobSimoleanPayout), + bobProposal.give.Bid, + ); // Bob deposits his payout to ensure he can await bobMoolaPurse.deposit(bobMoolaPayout); @@ -456,7 +456,5 @@ test('zoe - secondPriceAuction w/ 3 bids - alice exits onDemand', async t => { } catch (e) { t.assert(false, e); console.log(e); - } finally { - t.end(); } }); diff --git a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js index e2892a968d0..569f6463821 100644 --- a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js +++ b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js @@ -5,57 +5,44 @@ import bundleSource from '@agoric/bundle-source'; import harden from '@agoric/harden'; +import { assert, details } from '@agoric/assert'; import { makeZoe } from '../../../src/zoe'; import { setup } from '../setupBasicMints'; const simpleExchange = `${__dirname}/../../../src/contracts/simpleExchange`; -function makeRule(kind, amount) { - return { kind, amount }; -} +test('simpleExchange with valid offers', async t => { + t.plan(9); + try { + const { issuers, mints, amountMaths, moola, simoleans } = setup(); + const [moolaIssuer, simoleanIssuer] = issuers; + const [moolaMint, simoleanMint] = mints; + const zoe = makeZoe({ require }); + const inviteIssuer = zoe.getInviteIssuer(); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(simpleExchange); -function offerRule(amount) { - return makeRule('offerAtMost', amount); -} + const installationHandle = zoe.install(source, moduleFormat); -function wantRule(amount) { - return makeRule('wantAtLeast', amount); -} + // Setup Alice + const aliceMoolaPayment = moolaMint.mintPayment(moola(3)); + const aliceMoolaPurse = moolaIssuer.makeEmptyPurse(); + const aliceSimoleanPurse = simoleanIssuer.makeEmptyPurse(); -function exitRule(kind) { - return { kind }; -} + // Setup Bob + const bobSimoleanPayment = simoleanMint.mintPayment(simoleans(7)); + const bobMoolaPurse = moolaIssuer.makeEmptyPurse(); + const bobSimoleanPurse = simoleanIssuer.makeEmptyPurse(); -test('simpleExchange with valid offers', async t => { - t.plan(10); - - const { issuers, mints, amountMaths, moola, simoleans } = setup(); - const [moolaIssuer, simoleanIssuer] = issuers; - const [moolaMint, simoleanMint] = mints; - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(simpleExchange); - - const installationHandle = zoe.install(source, moduleFormat); - - // 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 simpleExchange instance and spreads the invite far and - // wide with instructions on how to use it. - const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { - issuers: [moolaIssuer, simoleanIssuer], - }); - inviteIssuer.getAmountOf(simonInvite).then(async simonInviteAmount => { - const { instanceHandle } = simonInviteAmount.extent[0]; + // 1: Simon creates a simpleExchange instance and spreads the invite far and + // wide with instructions on how to use it. + const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { + Asset: moolaIssuer, + Price: simoleanIssuer, + }); + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(simonInvite); const { publicAPI } = zoe.getInstance(instanceHandle); const { invite: aliceInvite } = publicAPI.makeInvite(); @@ -63,14 +50,15 @@ test('simpleExchange with valid offers', async t => { // 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 aliceSellOrderOfferRules = harden({ - payoutRules: [offerRule(moola(3)), wantRule(simoleans(4))], - exitRule: exitRule('onDemand'), + const aliceSellOrderProposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, }); - const alicePayments = [aliceMoolaPayment, undefined]; + const alicePayments = { Asset: aliceMoolaPayment }; const { seat: aliceSeat, payout: alicePayoutP } = await zoe.redeem( aliceInvite, - aliceSellOrderOfferRules, + aliceSellOrderProposal, alicePayments, ); @@ -83,25 +71,33 @@ test('simpleExchange with valid offers', async t => { const { installationHandle: bobInstallationId, - terms: bobTerms, + issuerKeywordRecord: bobIssuers, } = zoe.getInstance(instanceHandle); t.equals(bobInstallationId, installationHandle); - t.deepEquals(bobTerms.issuers, [moolaIssuer, simoleanIssuer]); + + assert( + bobIssuers.Asset === moolaIssuer, + details`The Asset issuer should be the moola issuer`, + ); + assert( + bobIssuers.Price === simoleanIssuer, + details`The Price issuer should be the simolean issuer`, + ); // Bob creates a buy order, saying that he wants exactly 3 moola, // and is willing to pay up to 7 simoleans. - - const bobBuyOrderOfferRules = harden({ - payoutRules: [wantRule(moola(3)), offerRule(simoleans(7))], - exitRule: exitRule('onDemand'), + const bobBuyOrderProposal = harden({ + give: { Price: simoleans(7) }, + want: { Asset: moola(3) }, + exit: { onDemand: null }, }); - const bobPayments = [undefined, bobSimoleanPayment]; + const bobPayments = { Price: bobSimoleanPayment }; // 6: Bob escrows with zoe const { seat: bobSeat, payout: bobPayoutP } = await zoe.redeem( bobExclusiveInvite, - bobBuyOrderOfferRules, + bobBuyOrderProposal, bobPayments, ); @@ -119,25 +115,21 @@ test('simpleExchange with valid offers', async t => { const bobPayout = await bobPayoutP; const alicePayout = await alicePayoutP; - const [bobMoolaPayout, bobSimoleanPayout] = await Promise.all(bobPayout); - const [aliceMoolaPayout, aliceSimoleanPayout] = await Promise.all( - alicePayout, - ); + const bobMoolaPayout = await bobPayout.Asset; + const bobSimoleanPayout = await bobPayout.Price; + const aliceMoolaPayout = await alicePayout.Asset; + const aliceSimoleanPayout = await alicePayout.Price; // Alice gets paid at least what she wanted - const aliceSimoleanAmount = await simoleanIssuer.getAmountOf( - aliceSimoleanPayout, - ); t.ok( amountMaths[1].isGTE( - aliceSimoleanAmount, - aliceSellOrderOfferRules.payoutRules[1].amount, + await simoleanIssuer.getAmountOf(aliceSimoleanPayout), + aliceSellOrderProposal.want.Price, ), ); // Alice sold all of her moola - const aliceMoolaAmount = await moolaIssuer.getAmountOf(aliceMoolaPayout); - t.deepEquals(aliceMoolaAmount, moola(0)); + t.deepEquals(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); // 13: Alice deposits her payout to ensure she can await aliceMoolaPurse.deposit(aliceMoolaPayout); @@ -154,10 +146,14 @@ test('simpleExchange with valid offers', async t => { t.equals(aliceSimoleanPurse.getCurrentAmount().extent, 7); t.equals(bobMoolaPurse.getCurrentAmount().extent, 3); t.equals(bobSimoleanPurse.getCurrentAmount().extent, 0); - }); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); test('simpleExchange with multiple sell offers', async t => { + t.plan(1); try { const { issuers, mints, moola, simoleans } = setup(); const [moolaIssuer, simoleanIssuer] = issuers; @@ -180,10 +176,12 @@ test('simpleExchange with multiple sell offers', async t => { // 1: Simon creates a simpleExchange instance and spreads the invite far and // wide with instructions on how to use it. const { invite: simonInvite } = await zoe.makeInstance(installationHandle, { - issuers: [moolaIssuer, simoleanIssuer], + Asset: moolaIssuer, + Price: simoleanIssuer, }); - const simonInviteAmount = await inviteIssuer.getAmountOf(simonInvite); - const { instanceHandle } = simonInviteAmount.extent[0]; + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(simonInvite); const { publicAPI } = zoe.getInstance(instanceHandle); const { invite: aliceInvite1 } = publicAPI.makeInvite(); @@ -191,15 +189,16 @@ test('simpleExchange with multiple sell offers', async t => { // 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 aliceSale1OrderOfferRules = harden({ - payoutRules: [offerRule(moola(3)), wantRule(simoleans(4))], - exitRule: exitRule('onDemand'), + const aliceSale1OrderProposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, }); - const alicePayments = [aliceMoolaPurse.withdraw(moola(3)), undefined]; + const alicePayments = { Asset: aliceMoolaPurse.withdraw(moola(3)) }; const { seat: aliceSeat1 } = await zoe.redeem( aliceInvite1, - aliceSale1OrderOfferRules, + aliceSale1OrderProposal, alicePayments, ); @@ -210,14 +209,15 @@ test('simpleExchange with multiple sell offers', async t => { const aliceInvite2 = await inviteIssuer.claim( publicAPI.makeInvite().invite, ); - const aliceSale2OrderOfferRules = harden({ - payoutRules: [offerRule(moola(5)), wantRule(simoleans(8))], - exitRule: exitRule('onDemand'), + const aliceSale2OrderProposal = harden({ + give: { Asset: moola(5) }, + want: { Price: simoleans(8) }, + exit: { onDemand: null }, }); const { seat: aliceSeat2 } = await zoe.redeem( aliceInvite2, - aliceSale2OrderOfferRules, - [aliceMoolaPurse.withdraw(moola(5)), undefined], + aliceSale2OrderProposal, + { Asset: aliceMoolaPurse.withdraw(moola(5)) }, ); const aliceOfferResult2 = aliceSeat2.addOrder(); @@ -225,14 +225,15 @@ test('simpleExchange with multiple sell offers', async t => { const aliceInvite3 = await inviteIssuer.claim( publicAPI.makeInvite().invite, ); - const aliceBuyOrderOfferRules = harden({ - payoutRules: [wantRule(moola(29)), offerRule(simoleans(18))], - exitRule: exitRule('onDemand'), + const aliceBuyOrderProposal = harden({ + give: { Price: simoleans(18) }, + want: { Asset: moola(29) }, + exit: { onDemand: null }, }); const { seat: aliceSeat3 } = await zoe.redeem( aliceInvite3, - aliceBuyOrderOfferRules, - [undefined, aliceSimoleanPurse.withdraw(simoleans(18))], + aliceBuyOrderProposal, + { Price: aliceSimoleanPurse.withdraw(simoleans(18)) }, ); const aliceOfferResult3 = aliceSeat3.addOrder(); @@ -240,10 +241,10 @@ test('simpleExchange with multiple sell offers', async t => { () => { const expectedBook = { changed: {}, - buys: [[{ want: moola(29) }, { offer: simoleans(18) }]], + buys: [[{ Asset: 29 }, { Price: 18 }]], sells: [ - [{ offer: moola(3) }, { want: simoleans(4) }], - [{ offer: moola(5) }, { want: simoleans(8) }], + [{ Price: 4 }, { Asset: 3 }], + [{ Price: 8 }, { Asset: 5 }], ], }; t.deepEquals(publicAPI.getBookOrders(), expectedBook); @@ -252,57 +253,61 @@ test('simpleExchange with multiple sell offers', async t => { } catch (e) { t.assert(false, e); console.log(e); - } finally { - t.end(); } }); test('simpleExchange showPayoutRules', async t => { t.plan(1); + try { + const { issuers, mints, moola, simoleans } = setup(); + const [moolaIssuer, simoleanIssuer] = issuers; + const [moolaMint] = mints; + const zoe = makeZoe({ require }); + const inviteIssuer = zoe.getInviteIssuer(); + // Pack the contract. + const { source, moduleFormat } = await bundleSource(simpleExchange); - const { issuers, mints, moola, simoleans } = setup(); - const [moolaIssuer, simoleanIssuer] = issuers; - const [moolaMint] = mints; - const zoe = makeZoe({ require }); - const inviteIssuer = zoe.getInviteIssuer(); - // Pack the contract. - const { source, moduleFormat } = await bundleSource(simpleExchange); - - const installationHandle = zoe.install(source, moduleFormat); - - // 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 { invite: simonInvite } = await zoe.makeInstance(installationHandle, { - issuers: [moolaIssuer, simoleanIssuer], - }); - inviteIssuer.getAmountOf(simonInvite).then(simonInviteAmout => { - const { instanceHandle } = simonInviteAmout.extent[0]; + const installationHandle = zoe.install(source, moduleFormat); + + // 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 { invite: simonInvite } = await zoe.makeInstance(installationHandle, { + Asset: moolaIssuer, + Price: simoleanIssuer, + }); + const { + extent: [{ instanceHandle }], + } = await inviteIssuer.getAmountOf(simonInvite); const { publicAPI } = zoe.getInstance(instanceHandle); - const { invite: aliceInvite1, inviteHandle } = publicAPI.makeInvite(); + const { invite: aliceInvite, inviteHandle } = 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 aliceSale1OrderOfferRules = harden({ - payoutRules: [offerRule(moola(3)), wantRule(simoleans(4))], - exitRule: exitRule('onDemand'), + const aliceSale1OrderProposal = harden({ + give: { Asset: moola(3) }, + want: { Price: simoleans(4) }, + exit: { onDemand: null }, }); - const alicePayments = [aliceMoolaPayment, undefined]; - zoe - .redeem(aliceInvite1, aliceSale1OrderOfferRules, alicePayments) - .then(aliceSeat1P => { - const { seat: aliceSeat1 } = aliceSeat1P; + const alicePayments = { Asset: aliceMoolaPayment }; + const { seat: aliceSeat1 } = await zoe.redeem( + aliceInvite, + aliceSale1OrderProposal, + alicePayments, + ); - // 4: Alice adds her sell order to the exchange - aliceSeat1.addOrder(); + // 4: Alice adds her sell order to the exchange + aliceSeat1.addOrder(); - const expected = [{ offer: moola(3) }, { want: simoleans(4) }]; + const expected = [{ Price: 4 }, { Asset: 3 }]; - t.deepEquals(publicAPI.getOffer(inviteHandle), expected); - }); - }); + t.deepEquals(publicAPI.getOffer(inviteHandle), expected); + } catch (e) { + t.assert(false, e); + console.log(e); + } }); diff --git a/packages/zoe/test/unitTests/setupBasicMints2.js b/packages/zoe/test/unitTests/setupBasicMints2.js new file mode 100644 index 00000000000..64ef685c65a --- /dev/null +++ b/packages/zoe/test/unitTests/setupBasicMints2.js @@ -0,0 +1,20 @@ +import harden from '@agoric/harden'; + +import produceIssuer from '@agoric/ertp'; + +const setup = () => { + const moolaR = produceIssuer('moola'); + const simoleanR = produceIssuer('simoleans'); + const bucksR = produceIssuer('bucks'); + + return harden({ + moolaR, + simoleanR, + bucksR, + moola: moolaR.amountMath.make, + simoleans: simoleanR.amountMath.make, + bucks: bucksR.amountMath.make, + }); +}; +harden(setup); +export { setup }; diff --git a/packages/zoe/test/unitTests/test-cleanProposal.js b/packages/zoe/test/unitTests/test-cleanProposal.js new file mode 100644 index 00000000000..d37ae18b41d --- /dev/null +++ b/packages/zoe/test/unitTests/test-cleanProposal.js @@ -0,0 +1,127 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from 'tape-promise/tape'; + +import harden from '@agoric/harden'; + +import { cleanProposal } from '../../src/cleanProposal'; +import { setup } from './setupBasicMints'; +import buildManualTimer from '../../tools/manualTimer'; + +test('cleanProposal', t => { + t.plan(1); + try { + const { amountMaths, moola, simoleans, bucks } = setup(); + + const keywords = ['Asset', 'Price', 'AlternativePrice']; + + const proposal = harden({ + give: { Asset: simoleans(1) }, + want: { Price: moola(3) }, + }); + + const amountMathKeywordRecord = harden({ + Asset: amountMaths[1], + Price: amountMaths[0], + AlternativePrice: amountMaths[2], + }); + + const expected = harden({ + give: { Asset: simoleans(1) }, + want: { Price: moola(3), AlternativePrice: bucks(0) }, + exit: { onDemand: null }, + }); + + const actual = cleanProposal(keywords, amountMathKeywordRecord, proposal); + + t.deepEquals(actual, expected); + } catch (e) { + t.assert(false, e); + } +}); + +test('cleanProposal - all empty', t => { + t.plan(1); + try { + const { amountMaths, moola, simoleans, bucks } = setup(); + + const keywords = ['Asset', 'Price', 'AlternativePrice']; + const amountMathKeywordRecord = harden({ + Asset: amountMaths[1], + Price: amountMaths[0], + AlternativePrice: amountMaths[2], + }); + + const proposal = harden({ + give: {}, + want: {}, + exit: { waived: null }, + }); + + const expected = harden({ + give: {}, + want: { + Asset: simoleans(0), + Price: moola(0), + AlternativePrice: bucks(0), + }, + exit: { waived: null }, + }); + + t.deepEquals( + cleanProposal(keywords, amountMathKeywordRecord, proposal), + expected, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('cleanProposal - repeated brands', t => { + t.plan(3); + try { + const { amountMaths, moola, simoleans, bucks } = setup(); + const timer = buildManualTimer(console.log); + + const keywords = [ + 'Asset1', + 'Price1', + 'AlternativePrice1', + 'Asset2', + 'Price2', + 'AlternativePrice2', + ]; + const amountMathsObj = harden({ + Asset1: amountMaths[1], + Price1: amountMaths[0], + AlternativePrice1: amountMaths[2], + Asset2: amountMaths[1], + Price2: amountMaths[0], + AlternativePrice2: amountMaths[2], + }); + + const proposal = harden({ + want: { Asset2: simoleans(1) }, + give: { Price2: moola(3) }, + exit: { afterDeadline: { timer, deadline: 100 } }, + }); + + const expected = harden({ + want: { + Asset2: simoleans(1), + Asset1: simoleans(0), + Price1: moola(0), + AlternativePrice1: bucks(0), + AlternativePrice2: bucks(0), + }, + give: { Price2: moola(3) }, + exit: { afterDeadline: { timer, deadline: 100 } }, + }); + + const actual = cleanProposal(keywords, amountMathsObj, proposal); + t.deepEquals(actual.want, expected.want); + t.deepEquals(actual.give, expected.give); + t.deepEquals(actual.exit, expected.exit); + } catch (e) { + t.assert(false, e); + } +}); diff --git a/packages/zoe/test/unitTests/test-isOfferSafe.js b/packages/zoe/test/unitTests/test-isOfferSafe.js deleted file mode 100644 index 0b0f6b0ee96..00000000000 --- a/packages/zoe/test/unitTests/test-isOfferSafe.js +++ /dev/null @@ -1,386 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import { test } from 'tape-promise/tape'; - -import { isOfferSafeForOffer, isOfferSafeForAll } from '../../src/isOfferSafe'; -import { setup } from './setupBasicMints'; - -// The player must have payoutRules for each issuer -test('isOfferSafeForOffer - empty payoutRules', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = []; - const amounts = [moola(8), simoleans(6), bucks(7)]; - - t.throws( - _ => isOfferSafeForOffer(amountMaths, payoutRules, amounts), - /amountMaths, payoutRules, and amounts must be arrays of the same length/, - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The amount array must have an item for each issuer/rule -test('isOfferSafeForOffer - empty amount', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'wantAtLeast', amount: moola(8) }, - { kind: 'wantAtLeast', amount: simoleans(6) }, - { kind: 'wantAtLeast', amount: bucks(7) }, - ]; - const amount = []; - - t.throws( - _ => isOfferSafeForOffer(amountMaths, payoutRules, amount), - /amountMaths, payoutRules, and amounts must be arrays of the same length/, - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The player puts in something and gets exactly what they wanted, -// with no refund -test('isOfferSafeForOffer - gets want exactly', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(8) }, - { kind: 'wantAtLeast', amount: simoleans(6) }, - { kind: 'wantAtLeast', amount: bucks(7) }, - ]; - const amount = [moola(0), simoleans(6), bucks(7)]; - - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The player gets exactly what they wanted, with no 'offer' -test('isOfferSafeForOffer - gets wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'wantAtLeast', amount: moola(8) }, - { kind: 'wantAtLeast', amount: simoleans(6) }, - { kind: 'wantAtLeast', amount: bucks(7) }, - ]; - const amount = [moola(8), simoleans(6), bucks(7)]; - - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The player gets more than what they wanted, with no 'offer' rule -// kind. Note: This returns 'true' counterintuitively because no -// 'offer' rule kind was specified and none were given back, so the -// refund condition was fulfilled trivially. -test('isOfferSafeForOffer - gets wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'wantAtLeast', amount: moola(8) }, - { kind: 'wantAtLeast', amount: simoleans(6) }, - { kind: 'wantAtLeast', amount: bucks(7) }, - ]; - const amount = [moola(9), simoleans(6), bucks(7)]; - - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets refunded exactly what they put in, with a 'wantAtLeast' -test(`isOfferSafeForOffer - gets offerAtMost, doesn't get wantAtLeast`, t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(1) }, - { kind: 'wantAtLeast', amount: simoleans(2) }, - { kind: 'offerAtMost', amount: bucks(3) }, - ]; - const amount = [moola(1), simoleans(0), bucks(3)]; - - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets refunded exactly what they put in, with no 'wantAtLeast' -test('isOfferSafeForOffer - gets offerAtMost, no wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(1) }, - { kind: 'offerAtMost', amount: simoleans(2) }, - { kind: 'offerAtMost', amount: bucks(3) }, - ]; - const amount = [moola(1), simoleans(2), bucks(3)]; - - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets a refund *and* winnings. This is 'offer safe'. -test('isOfferSafeForOffer - refund and winnings', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(3) }, - ]; - const amount = [moola(2), simoleans(3), bucks(3)]; - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets more than they wanted -test('isOfferSafeForOffer - more than wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(4) }, - ]; - const amount = [moola(0), simoleans(3), bucks(5)]; - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets more than they wanted - wantAtLeast -test('isOfferSafeForOffer - more than wantAtLeast (no offerAtMost)', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'wantAtLeast', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(4) }, - ]; - const amount = [moola(2), simoleans(6), bucks(7)]; - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets refunded more than what they put in -test('isOfferSafeForOffer - more than offerAtMost', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'offerAtMost', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(4) }, - ]; - const amount = [moola(5), simoleans(6), bucks(8)]; - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets refunded more than what they put in, with no -// wantAtLeast. Note: This returns 'true' counterintuitively -// because no winnings were specified and none were given back. -test('isOfferSafeForOffer - more than offerAtMost, no wants', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'offerAtMost', amount: simoleans(3) }, - { kind: 'offerAtMost', amount: bucks(4) }, - ]; - const amount = [moola(5), simoleans(6), bucks(8)]; - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets refunded more than what they put in, with 'offer' -test('isOfferSafeForOffer - more than offer', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'offerAtMost', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(4) }, - ]; - const amount = [moola(5), simoleans(3), bucks(0)]; - t.ok(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets less than what they wanted - wantAtLeast -test('isOfferSafeForOffer - less than wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(5) }, - ]; - const amount = [moola(0), simoleans(2), bucks(1)]; - t.notOk(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets less than what they wanted - wantAtLeast -test('isOfferSafeForOffer - less than wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(9) }, - ]; - const amount = [moola(0), simoleans(2), bucks(1)]; - t.notOk(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// The user gets refunded less than they put in -test('isOfferSafeForOffer - less than wantAtLeast', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(3) }, - ]; - const amount = [moola(1), simoleans(0), bucks(0)]; - t.notOk(isOfferSafeForOffer(amountMaths, payoutRules, amount)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -test('isOfferSafeForOffer - empty arrays', t => { - try { - const { amountMaths } = setup(); - const payoutRules = []; - const amount = []; - t.throws( - () => isOfferSafeForOffer(amountMaths, payoutRules, amount), - /amountMaths, payoutRules, and amounts must be arrays of the same length/, - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -test('isOfferSafeForOffer - null for some issuers', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - null, - { kind: 'offerAtMost', amount: bucks(4) }, - ]; - const amount = [moola(5), simoleans(6), bucks(8)]; - t.throws( - () => isOfferSafeForOffer(amountMaths, payoutRules, amount), - /payoutRule must be specified/, - ); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -// All users get exactly what they wanted -test('isOfferSafeForAll - All users get what they wanted', t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(3) }, - ]; - - const offerMatrix = [payoutRules, payoutRules, payoutRules]; - const amount = [moola(0), simoleans(3), bucks(3)]; - const amountMatrix = [amount, amount, amount]; - t.ok(isOfferSafeForAll(amountMaths, offerMatrix, amountMatrix)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); - -test(`isOfferSafeForAll - One user doesn't get what they wanted`, t => { - try { - const { amountMaths, moola, simoleans, bucks } = setup(); - const payoutRules = [ - { kind: 'offerAtMost', amount: moola(2) }, - { kind: 'wantAtLeast', amount: simoleans(3) }, - { kind: 'wantAtLeast', amount: bucks(3) }, - ]; - - const offerMatrix = [payoutRules, payoutRules, payoutRules]; - const amount = [moola(0), simoleans(3), bucks(3)]; - const unsatisfiedUserAmounts = [moola(0), simoleans(3), bucks(2)]; - const amountMatrix = [amount, amount, unsatisfiedUserAmounts]; - t.notOk(isOfferSafeForAll(amountMaths, offerMatrix, amountMatrix)); - } catch (e) { - t.assert(false, e); - } finally { - t.end(); - } -}); diff --git a/packages/zoe/test/unitTests/test-objArrayConversion.js b/packages/zoe/test/unitTests/test-objArrayConversion.js new file mode 100644 index 00000000000..9c33a5e63cc --- /dev/null +++ b/packages/zoe/test/unitTests/test-objArrayConversion.js @@ -0,0 +1,54 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from 'tape-promise/tape'; + +import { arrayToObj, objToArray } from '../../src/objArrayConversion'; + +test('arrayToObj', t => { + t.plan(3); + try { + const keywords = ['X', 'Y']; + const array = [1, 2]; + t.deepEquals(arrayToObj(array, keywords), { X: 1, Y: 2 }); + + const keywords2 = ['X', 'Y', 'Z']; + t.throws( + () => arrayToObj(array, keywords2), + /Error: array and keywords must be of equal length/, + `unequal length should throw`, + ); + + const array2 = [4, 5, 2]; + t.throws( + () => arrayToObj(array2, keywords), + /Error: array and keywords must be of equal length/, + `unequal length should throw`, + ); + } catch (e) { + t.assert(false, e); + } +}); + +test('objToArray', t => { + t.plan(3); + try { + const keywords = ['X', 'Y']; + const obj = { X: 1, Y: 2 }; + t.deepEquals(objToArray(obj, keywords), [1, 2]); + + const keywords2 = ['X', 'Y', 'Z']; + t.throws( + () => objToArray(obj, keywords2), + /Error: Assertion failed: object keys and keywords must be of equal length/, + `unequal length should throw`, + ); + + const obj2 = { X: 1, Y: 2, Z: 5 }; + t.throws( + () => objToArray(obj2, keywords), + /Error: Assertion failed: object keys and keywords must be of equal length/, + `unequal length should throw`, + ); + } catch (e) { + t.assert(false, e); + } +}); diff --git a/packages/zoe/test/unitTests/test-offerSafety.js b/packages/zoe/test/unitTests/test-offerSafety.js new file mode 100644 index 00000000000..064134d1fde --- /dev/null +++ b/packages/zoe/test/unitTests/test-offerSafety.js @@ -0,0 +1,283 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from 'tape-promise/tape'; +import harden from '@agoric/harden'; + +import { isOfferSafeForOffer, isOfferSafeForAll } from '../../src/offerSafety'; +import { setup } from './setupBasicMints2'; + +// Potential outcomes: +// 1. Users can get what they wanted, get back what they gave, both, or +// neither +// 2. Users can either get more than, less than, or equal to what they +// wanted or gave + +// possible combinations to test: +// more than want, more than give -> isOfferSafe() = true +// more than want, less than give -> true +// more than want, equal to give -> true +// less than want, more than give -> true +// less than want, less than give -> false +// less than want, equal to give -> true +// equal to want, more than give -> true +// equal to want, less than give -> true +// equal to want, equal to give -> true + +// more than want, more than give -> isOfferSafe() = true +test('isOfferSafeForOffer - more than want, more than give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + give: { A: moola(8) }, + want: { B: simoleans(6), C: bucks(7) }, + }); + const amounts = harden({ A: moola(10), B: simoleans(7), C: bucks(8) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// more than want, less than give -> true +test('isOfferSafeForOffer - more than want, less than give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + give: { A: moola(8) }, + want: { B: simoleans(6), C: bucks(7) }, + }); + const amounts = harden({ A: moola(1), B: simoleans(7), C: bucks(8) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// more than want, equal to give -> true +test('isOfferSafeForOffer - more than want, equal to give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { A: moola(8) }, + give: { B: simoleans(6), C: bucks(7) }, + }); + const amounts = harden({ A: moola(9), B: simoleans(6), C: bucks(7) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// less than want, more than give -> true +test('isOfferSafeForOffer - less than want, more than give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { A: moola(8) }, + give: { B: simoleans(6), C: bucks(7) }, + }); + const amounts = harden({ A: moola(7), B: simoleans(9), C: bucks(19) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// less than want, less than give -> false +test('isOfferSafeForOffer - less than want, less than give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { A: moola(8) }, + give: { B: simoleans(6), C: bucks(7) }, + }); + const amounts = harden({ A: moola(7), B: simoleans(5), C: bucks(6) }); + + t.notOk(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// less than want, equal to give -> true +test('isOfferSafeForOffer - less than want, equal to give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { B: simoleans(6) }, + give: { A: moola(1), C: bucks(7) }, + }); + const amounts = harden({ A: moola(1), B: simoleans(5), C: bucks(7) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// equal to want, more than give -> true +test('isOfferSafeForOffer - equal to want, more than give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { B: simoleans(6) }, + give: { A: moola(1), C: bucks(7) }, + }); + const amounts = harden({ A: moola(2), B: simoleans(6), C: bucks(8) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// equal to want, less than give -> true +test('isOfferSafeForOffer - equal to want, less than give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { B: simoleans(6) }, + give: { A: moola(1), C: bucks(7) }, + }); + const amounts = harden({ A: moola(0), B: simoleans(6), C: bucks(0) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// equal to want, equal to give -> true +test('isOfferSafeForOffer - equal to want, equal to give', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { B: simoleans(6) }, + give: { A: moola(1), C: bucks(7) }, + }); + const amounts = harden({ A: moola(1), B: simoleans(6), C: bucks(7) }); + + t.ok(isOfferSafeForOffer(amountMathKeywordRecord, proposal, amounts)); + } catch (e) { + t.assert(false, e); + } +}); + +// All users get exactly what they wanted +test('isOfferSafeForAll - All users get what they wanted', t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { B: simoleans(6) }, + give: { A: moola(1), C: bucks(7) }, + }); + const proposals = [proposal, proposal, proposal]; + const amounts = harden({ A: moola(0), B: simoleans(6), C: bucks(0) }); + const amountKeywordRecords = [amounts, amounts, amounts]; + t.ok( + isOfferSafeForAll( + amountMathKeywordRecord, + proposals, + amountKeywordRecords, + ), + ); + } catch (e) { + t.assert(false, e); + } +}); + +test(`isOfferSafeForAll - One user doesn't get what they wanted`, t => { + t.plan(1); + try { + const { moolaR, simoleanR, bucksR, moola, simoleans, bucks } = setup(); + const amountMathKeywordRecord = harden({ + A: moolaR.amountMath, + B: simoleanR.amountMath, + C: bucksR.amountMath, + }); + const proposal = harden({ + want: { B: simoleans(6) }, + give: { A: moola(1), C: bucks(7) }, + }); + const proposals = [proposal, proposal, proposal]; + const amounts = harden({ A: moola(0), B: simoleans(6), C: bucks(0) }); + const unsatisfiedAmounts = harden({ + A: moola(0), + B: simoleans(4), + C: bucks(0), + }); + const amountKeywordRecords = [amounts, amounts, unsatisfiedAmounts]; + t.notOk( + isOfferSafeForAll( + amountMathKeywordRecord, + proposals, + amountKeywordRecords, + ), + ); + } catch (e) { + t.assert(false, e); + } +}); diff --git a/packages/zoe/test/unitTests/test-areRightsConserved.js b/packages/zoe/test/unitTests/test-rightsConservation.js similarity index 85% rename from packages/zoe/test/unitTests/test-areRightsConserved.js rename to packages/zoe/test/unitTests/test-rightsConservation.js index 6917a392232..dc770f988b2 100644 --- a/packages/zoe/test/unitTests/test-areRightsConserved.js +++ b/packages/zoe/test/unitTests/test-rightsConservation.js @@ -1,13 +1,16 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; -import { areRightsConserved, transpose } from '../../src/areRightsConserved'; +import { areRightsConserved, transpose } from '../../src/rightsConservation'; import { setup } from './setupBasicMints'; -const makeAmountMatrix = (amountMaths, extentMatrix) => - extentMatrix.map(row => row.map((extent, i) => amountMaths[i].make(extent))); +const makeAmountMatrix = (amountMathArray, extentMatrix) => + extentMatrix.map(row => + row.map((extent, i) => amountMathArray[i].make(extent)), + ); test('transpose', t => { + t.plan(1); try { t.deepEquals( transpose([ @@ -22,13 +25,12 @@ test('transpose', t => { ); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); // rights are conserved for amount with Nat extents test(`areRightsConserved - true for amount with nat extents`, t => { + t.plan(1); try { const { amountMaths } = setup(); const oldExtents = [ @@ -48,13 +50,12 @@ test(`areRightsConserved - true for amount with nat extents`, t => { t.ok(areRightsConserved(amountMaths, oldAmounts, newAmounts)); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); // rights are *not* conserved for amount with Nat extents test(`areRightsConserved - false for amount with Nat extents`, t => { + t.plan(1); try { const { amountMaths } = setup(); const oldExtents = [ @@ -74,12 +75,11 @@ test(`areRightsConserved - false for amount with Nat extents`, t => { t.notOk(areRightsConserved(amountMaths, oldAmounts, newAmounts)); } catch (e) { t.assert(false, e); - } finally { - t.end(); } }); test(`areRightsConserved - empty arrays`, t => { + t.plan(1); try { const { amountMaths } = setup(); const oldAmounts = [[], [], []]; @@ -88,8 +88,6 @@ test(`areRightsConserved - empty arrays`, t => { t.ok(areRightsConserved(amountMaths, oldAmounts, newAmounts)); } catch (e) { t.assert(false, e); - } finally { - t.end(); } });