Skip to content

Commit

Permalink
fix: zoe start using patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Dec 28, 2021
1 parent b894b95 commit ef116ca
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 29 deletions.
2 changes: 1 addition & 1 deletion packages/wallet/api/src/findOrMakeInvitation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const assertFirstCapASCII = str => {
const firstCapASCII = /^[A-Z][a-zA-Z0-9_$]*$/;
assert(
firstCapASCII.test(str),
X`The string ${q(str)} must be ascii and must start with a capital letter.`,
X`The string ${q(str)} must be an ascii identifier starting with upper case.`,
);
assert(
str !== 'NaN' && str !== 'Infinity',
Expand Down
11 changes: 6 additions & 5 deletions packages/zoe/src/cleanProposal.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
// @ts-check

import { assert, details as X, q } from '@agoric/assert';
import { mustBeComparable } from '@agoric/same-structure';
import { isNat } from '@agoric/nat';
import { AmountMath, getAssetKind } from '@agoric/ertp';
import { assertRecord } from '@agoric/marshal';
import { assertPattern } from '@agoric/store';
import {
isOnDemandExitRule,
isWaivedExitRule,
isAfterDeadlineExitRule,
} from './typeGuards.js';
import { arrayToObj, assertSubset } from './objArrayConversion.js';

import '../exported.js';
import './internal-types.js';

import { arrayToObj, assertSubset } from './objArrayConversion.js';

const firstCapASCII = /^[A-Z][a-zA-Z0-9_$]*$/;

// We adopt simple requirements on keywords so that they do not accidentally
Expand All @@ -33,7 +32,7 @@ export const assertKeywordName = keyword => {
firstCapASCII.test(keyword),
X`keyword ${q(
keyword,
)} must be ascii and must start with a capital letter.`,
)} must be an ascii identifier starting with upper case.`,
);
assert(
keyword !== 'NaN' && keyword !== 'Infinity',
Expand All @@ -48,6 +47,7 @@ const assertKeysAllowed = (allowedKeys, record) => {
const keys = Object.getOwnPropertyNames(record);
assertSubset(allowedKeys, keys);
// assert that there are no symbol properties.
// TODO unreachable: already rejected as unpassable
assert(
Object.getOwnPropertySymbols(record).length === 0,
X`no symbol properties allowed`,
Expand Down Expand Up @@ -95,6 +95,7 @@ export const cleanKeywords = keywordRecord => {
const keywords = Object.getOwnPropertyNames(keywordRecord);

// Insist that there are no symbol properties.
// TODO unreachable: already rejected as unpassable
assert(
Object.getOwnPropertySymbols(keywordRecord).length === 0,
X`no symbol properties allowed`,
Expand Down Expand Up @@ -177,7 +178,7 @@ const rootKeysAllowed = harden(['want', 'give', 'exit']);
* @returns {ProposalRecord}
*/
export const cleanProposal = (proposal, getAssetKindByBrand) => {
mustBeComparable(proposal);
assertPattern(proposal);
assertKeysAllowed(rootKeysAllowed, proposal);

// We fill in the default values if the keys are undefined.
Expand Down
1 change: 1 addition & 0 deletions packages/zoe/src/contractSupport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from './statistics.js';
export {
defaultAcceptanceMsg,
swap,
fitProposalPattern,
assertProposalShape,
assertIssuerKeywords,
satisfies,
Expand Down
14 changes: 13 additions & 1 deletion packages/zoe/src/contractSupport/zoeHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { assert, details as X } from '@agoric/assert';
import { sameStructure } from '@agoric/same-structure';
import { E } from '@agoric/eventual-send';
import { makePromiseKit } from '@agoric/promise-kit';

import { fit } from '@agoric/store';
import { AssetKind } from '@agoric/ertp';
import { satisfiesWant } from '../contractFacet/offerSafety.js';

Expand Down Expand Up @@ -100,6 +100,18 @@ export const swapExact = (zcf, leftSeat, rightSeat) => {
* @property {Partial<Record<keyof ProposalRecord['exit'], null>>} [exit]
*/

/**
* Check the seat's proposal against `proposalPattern`.
* If the client submits an offer which does not match
* these expectations, the seat will be exited (and payments refunded).
*
* @param {ZCFSeat} seat
* @param {Pattern} proposalPattern
*/
export const fitProposalPattern = (seat, proposalPattern) =>
// TODO remove this harden, obligating our caller to harden.
fit(seat.getProposal(), harden(proposalPattern));

/**
* Check the seat's proposal against an `expected` record that says
* what shape of proposal is acceptable.
Expand Down
11 changes: 7 additions & 4 deletions packages/zoe/src/contracts/coveredCall.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @ts-check

import { assert } from '@agoric/assert';
import { M, fit } from '@agoric/store';
import '../../exported.js';

// Eventually will be importable from '@agoric/zoe-contract-support'
import { assertProposalShape, swapExact } from '../contractSupport/index.js';
import { swapExact } from '../contractSupport/index.js';
import { isAfterDeadlineExitRule } from '../typeGuards.js';

/**
Expand All @@ -27,8 +28,10 @@ import { isAfterDeadlineExitRule } from '../typeGuards.js';
* different brands can be escrowed under different keywords. The
* proposal must have an exit record with the key "afterDeadline":
* {
* give: { ... }, want: { ... }, exit: {afterDeadline: { deadline:
* time, timer: myTimer }
* give: { ... },
* want: { ... },
* exit: {
* afterDeadline: { deadline: time, timer: myTimer }
* },
* }
*
Expand Down Expand Up @@ -70,7 +73,7 @@ const start = zcf => {

/** @type {OfferHandler} */
const makeOption = sellSeat => {
assertProposalShape(sellSeat, { exit: { afterDeadline: null } });
fit(sellSeat.getProposal(), M.split({ exit: { afterDeadline: M.any() } }));
const sellSeatExitRule = sellSeat.getProposal().exit;
assert(
isAfterDeadlineExitRule(sellSeatExitRule),
Expand Down
37 changes: 33 additions & 4 deletions packages/zoe/src/typeGuards.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
// @ts-check

/**
* @param {ExitRule} exit
* @returns {exit is OnDemandExitRule}
*/
import { AmountPattern } from '@agoric/ertp';
import { M } from '@agoric/store';

export const ExitOnDemandPattern = harden({ onDemand: null });

export const ExitWaivedPattern = harden({ waived: null });

export const ExitAfterDeadlinePattern = harden({
afterDeadline: { timer: M.remotable(), deadline: M.nat() },
});

export const ExitRulePattern = M.or(
ExitOnDemandPattern,
ExitWaivedPattern,
ExitAfterDeadlinePattern,
);

export const KeywordRecordPatternOf = valuePatt =>
M.recordOf(M.string(), valuePatt);

export const AmountKeywordRecordPattern = KeywordRecordPatternOf(AmountPattern);

export const PatternKeywordRecordPattern = KeywordRecordPatternOf(M.pattern());

export const ProposalPattern = M.partial(
{
want: M.or(undefined, PatternKeywordRecordPattern),
give: M.or(undefined, AmountKeywordRecordPattern),
exit: M.or(undefined, ExitRulePattern),
},
{},
);

export const isOnDemandExitRule = exit => {
const [exitKey] = Object.getOwnPropertyNames(exit);
return exitKey === 'onDemand';
Expand Down
Loading

0 comments on commit ef116ca

Please sign in to comment.