Skip to content

Commit c4e16da

Browse files
committed
feat(ertp,zoe): minimal want patterns using M.has(el,n)
1 parent fb6a3e0 commit c4e16da

File tree

17 files changed

+187
-115
lines changed

17 files changed

+187
-115
lines changed

packages/ERTP/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@endo/far": "^1.1.10",
4949
"@endo/marshal": "^1.6.3",
5050
"@endo/nat": "^5.0.14",
51+
"@endo/pass-style": "^1.4.8",
5152
"@endo/patterns": "^1.4.8",
5253
"@endo/promise-kit": "^1.1.9"
5354
},

packages/ERTP/src/amountMath.js

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import { q, Fail } from '@endo/errors';
2-
import { passStyleOf, assertRemotable, assertRecord } from '@endo/marshal';
2+
import { assertRemotable, assertRecord } from '@endo/pass-style';
3+
import { isKey, kindOf, mustMatch } from '@endo/patterns';
34

4-
import { M, matches } from '@agoric/store';
55
import { natMathHelpers } from './mathHelpers/natMathHelpers.js';
66
import { setMathHelpers } from './mathHelpers/setMathHelpers.js';
77
import { copySetMathHelpers } from './mathHelpers/copySetMathHelpers.js';
88
import { copyBagMathHelpers } from './mathHelpers/copyBagMathHelpers.js';
9+
import {
10+
AmountBoundShape,
11+
AmountShape,
12+
AmountValueHasBoundShape,
13+
} from './typeGuards.js';
914

1015
/**
11-
* @import {CopyBag, CopySet} from '@endo/patterns';
12-
* @import {Amount, AssetValueForKind, Brand, CopyBagAmount, CopySetAmount, MathHelpers, NatAmount, NatValue, SetAmount, SetValue} from './types.js';
16+
* @import {Passable} from '@endo/pass-style'
17+
* @import {Key, CopyBag, CopySet} from '@endo/patterns';
18+
* @import {Amount, AmountBound, AmountValue, AssetValueForKind, Brand, CopyBagAmount, CopySetAmount, MathHelpers, NatAmount, NatValue, SetAmount, SetValue} from './types.js';
1319
*/
1420

1521
// NB: AssetKind is both a constant for enumerated values and a type for those values.
@@ -75,39 +81,42 @@ const helpers = {
7581
copyBag: copyBagMathHelpers,
7682
};
7783

78-
/** @type {(value: unknown) => 'nat' | 'set' | 'copySet' | 'copyBag'} } */
84+
/**
85+
* @template {AssetKind} K=AssetKind
86+
* @template {Key} M=Key
87+
* @template {AssetValueForKind<K, M>} V=AssetValueForKind<K, M>
88+
* @param {V} value
89+
* @returns {AssetKind}
90+
*/
7991
const assertValueGetAssetKind = value => {
80-
const passStyle = passStyleOf(value);
81-
if (passStyle === 'bigint') {
82-
return 'nat';
83-
}
84-
if (passStyle === 'copyArray') {
85-
return 'set';
86-
}
87-
if (matches(value, M.set())) {
88-
return 'copySet';
92+
const kind = kindOf(value);
93+
switch (kind) {
94+
case 'bigint': {
95+
return 'nat';
96+
}
97+
case 'copyArray': {
98+
return 'set';
99+
}
100+
case 'copySet':
101+
case 'copyBag': {
102+
return kind;
103+
}
104+
default: {
105+
throw Fail`value ${value} must be an AmountValue, not ${q(kind)}`;
106+
}
89107
}
90-
if (matches(value, M.bag())) {
91-
return 'copyBag';
92-
}
93-
// TODO This isn't quite the right error message, in case valuePassStyle
94-
// is 'tagged'. We would need to distinguish what kind of tagged
95-
// object it is.
96-
// Also, this kind of manual listing is a maintenance hazard we
97-
// (TODO) will encounter when we extend the math helpers further.
98-
throw Fail`value ${value} must be a bigint, copySet, copyBag, or an array, not ${q(
99-
passStyle,
100-
)}`;
101108
};
102109

103110
/**
104111
* Asserts that value is a valid AmountMath and returns the appropriate helpers.
105112
*
106113
* Made available only for testing, but it is harmless for other uses.
107114
*
108-
* @template V
115+
* @template {AssetKind} K=AssetKind
116+
* @template {Key} M=Key
117+
* @template {AssetValueForKind<K, M>} V=AssetValueForKind<K, M>
109118
* @param {V} value
110-
* @returns {MathHelpers<V>}
119+
* @returns {MathHelpers<K, M, V>}
111120
*/
112121
export const assertValueGetHelpers = value =>
113122
// @ts-expect-error cast
@@ -125,35 +134,38 @@ const optionalBrandCheck = (allegedBrand, brand) => {
125134
};
126135

127136
/**
128-
* @template {AssetKind} K
137+
* @template {AssetKind} K=AssetKind
138+
* @template {Key} M=Key
139+
* @template {AssetValueForKind<K, M>} V=AssetValueForKind<K, M>
129140
* @param {Amount<K>} leftAmount
130141
* @param {Amount<K>} rightAmount
131142
* @param {Brand<K> | undefined} brand
132-
* @returns {MathHelpers<any>}
143+
* @returns {MathHelpers<K, M, V>}
133144
*/
134145
const checkLRAndGetHelpers = (leftAmount, rightAmount, brand = undefined) => {
135-
assertRecord(leftAmount, 'leftAmount');
136-
assertRecord(rightAmount, 'rightAmount');
137-
const { value: leftValue, brand: leftBrand } = leftAmount;
138-
const { value: rightValue, brand: rightBrand } = rightAmount;
139-
assertRemotable(leftBrand, 'leftBrand');
140-
assertRemotable(rightBrand, 'rightBrand');
146+
mustMatch(leftAmount, AmountShape, 'left amount');
147+
mustMatch(rightAmount, AmountShape, 'right amount');
148+
const { brand: leftBrand, value: leftValue } = leftAmount;
149+
const { brand: rightBrand, value: rightValue } = rightAmount;
141150
optionalBrandCheck(leftBrand, brand);
142151
optionalBrandCheck(rightBrand, brand);
143152
leftBrand === rightBrand ||
144153
Fail`Brands in left ${q(leftBrand)} and right ${q(
145154
rightBrand,
146155
)} should match but do not`;
147-
const leftHelpers = assertValueGetHelpers(leftValue);
148-
const rightHelpers = assertValueGetHelpers(rightValue);
149-
leftHelpers === rightHelpers ||
150-
Fail`The left ${leftAmount} and right amount ${rightAmount} had different assetKinds`;
151-
return leftHelpers;
156+
const leftKind = assertValueGetAssetKind(leftValue);
157+
const rightKind = assertValueGetAssetKind(rightValue);
158+
leftKind === rightKind ||
159+
Fail`The left ${leftAmount} and right amounts ${rightAmount} had different assetKinds: ${q(leftKind)} vs ${q(rightKind)}`;
160+
// @ts-expect-error cast
161+
return helpers[leftKind];
152162
};
153163

154164
/**
155-
* @template {AssetKind} K
156-
* @param {MathHelpers<AssetValueForKind<K>>} h
165+
* @template {AssetKind} K=AssetKind
166+
* @template {Key} M=Key
167+
* @template {AssetValueForKind<K, M>} V=AssetValueForKind<K, M>
168+
* @param {MathHelpers<K, M, V>} h
157169
* @param {Amount<K>} leftAmount
158170
* @param {Amount<K>} rightAmount
159171
* @returns {[K, K]}
@@ -164,21 +176,42 @@ const coerceLR = (h, leftAmount, rightAmount) => {
164176
};
165177

166178
/**
167-
* Returns true if the leftAmount is greater than or equal to the rightAmount.
168-
* The notion of "greater than or equal to" depends on the kind of amount, as
169-
* defined by the MathHelpers. For example, whether rectangle A is greater than
170-
* rectangle B depends on whether rectangle A includes rectangle B as defined by
171-
* the logic in MathHelpers.
179+
* Returns true if the leftAmount is greater than or equal to the
180+
* rightAmountBound. The notion of "greater than or equal to" depends on the
181+
* kind of amount, as defined by the MathHelpers. For example, whether rectangle
182+
* A is greater than rectangle B depends on whether rectangle A includes
183+
* rectangle B as defined by the logic in MathHelpers.
172184
*
173185
* @template {AssetKind} K
174186
* @param {Amount<K>} leftAmount
175-
* @param {Amount<K>} rightAmount
187+
* @param {AmountBound<K>} rightAmountBound
176188
* @param {Brand<K>} [brand]
177189
* @returns {boolean}
178190
*/
179-
const isGTE = (leftAmount, rightAmount, brand = undefined) => {
180-
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
181-
return h.doIsGTE(...coerceLR(h, leftAmount, rightAmount));
191+
const isGTE = (leftAmount, rightAmountBound, brand = undefined) => {
192+
if (isKey(rightAmountBound)) {
193+
const rightAmount = /** @type {Amount<K>} */ (rightAmountBound);
194+
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
195+
// @ts-expect-error cast?
196+
return h.doIsGTE(...coerceLR(h, leftAmount, rightAmount));
197+
}
198+
mustMatch(leftAmount, AmountShape, 'left amount');
199+
mustMatch(rightAmountBound, AmountBoundShape, 'right amount bound');
200+
const { brand: leftBrand, value: leftValue } = leftAmount;
201+
const { brand: rightBrand, value: rightValueHasBound } = rightAmountBound;
202+
optionalBrandCheck(leftBrand, brand);
203+
optionalBrandCheck(rightBrand, brand);
204+
leftBrand === rightBrand ||
205+
Fail`Brands in left ${q(leftBrand)} and right ${q(
206+
rightBrand,
207+
)} should match but do not`;
208+
const leftKind = assertValueGetAssetKind(leftValue);
209+
// If it were anything else, it would have been a Key and so taken care of
210+
// in the first case above.
211+
mustMatch(rightValueHasBound, AmountValueHasBoundShape, 'right value bound');
212+
const h = helpers[leftKind];
213+
// @ts-expect-error cast?
214+
return h.doIsGTE(h.doCoerce(leftValue), rightValueHasBound);
182215
};
183216

184217
/**
@@ -307,6 +340,7 @@ export const AmountMath = {
307340
*/
308341
isEqual: (leftAmount, rightAmount, brand = undefined) => {
309342
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
343+
// @ts-expect-error cast?
310344
return h.doIsEqual(...coerceLR(h, leftAmount, rightAmount));
311345
},
312346
/**
@@ -324,6 +358,7 @@ export const AmountMath = {
324358
*/
325359
add: (leftAmount, rightAmount, brand = undefined) => {
326360
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
361+
// @ts-expect-error cast?
327362
const value = h.doAdd(...coerceLR(h, leftAmount, rightAmount));
328363
// @ts-expect-error different subtype
329364
return harden({ brand: leftAmount.brand, value });
@@ -344,6 +379,7 @@ export const AmountMath = {
344379
*/
345380
subtract: (leftAmount, rightAmount, brand = undefined) => {
346381
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
382+
// @ts-expect-error cast?
347383
const value = h.doSubtract(...coerceLR(h, leftAmount, rightAmount));
348384
// @ts-expect-error different subtype
349385
return harden({ brand: leftAmount.brand, value });

packages/ERTP/src/mathHelpers/copyBagMathHelpers.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import {
1111
bagDisjointSubtract,
1212
} from '@agoric/store';
1313

14-
/** @import {MathHelpers} from '../types.js' */
14+
/**
15+
* @import {Key, CopyBag} from '@endo/patterns'
16+
* @import {MathHelpers} from '../types.js'
17+
*/
1518

16-
/** @type {import('@endo/patterns').CopyBag} */
19+
/** @type {CopyBag} */
1720
const empty = makeCopyBag([]);
1821

19-
/** @type {MathHelpers<import('@endo/patterns').CopyBag>} */
22+
/** @type {MathHelpers<'copyBag', Key, CopyBag<Key>>} */
2023
export const copyBagMathHelpers = harden({
2124
doCoerce: bag => {
2225
mustMatch(bag, M.bag(), 'bag of amount');

packages/ERTP/src/mathHelpers/copySetMathHelpers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import {
1212
} from '@agoric/store';
1313

1414
/**
15+
* @import {Key, CopySet} from '@endo/patterns'
1516
* @import {MathHelpers} from '../types.js'
16-
* @import {CopySet} from '@endo/patterns'
1717
*/
1818

1919
/** @type {CopySet} */
2020
const empty = makeCopySet([]);
2121

22-
/** @type {MathHelpers<CopySet>} */
22+
/** @type {MathHelpers<'copySet', Key, CopySet<Key>>} */
2323
export const copySetMathHelpers = harden({
2424
doCoerce: set => {
2525
mustMatch(set, M.set(), 'set of amount');

packages/ERTP/src/mathHelpers/natMathHelpers.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import { Fail } from '@endo/errors';
44
import { Nat, isNat } from '@endo/nat';
55

6-
/** @import {MathHelpers, NatValue} from '../types.js' */
6+
/**
7+
* @import {Key} from '@endo/patterns'
8+
* @import {MathHelpers, NatValue} from '../types.js'
9+
*/
710

811
const empty = 0n;
912

@@ -16,7 +19,7 @@ const empty = 0n;
1619
* smallest whole unit such that the `natMathHelpers` never deals with
1720
* fractional parts.
1821
*
19-
* @type {MathHelpers<NatValue>}
22+
* @type {MathHelpers<'nat', Key, NatValue>}
2023
*/
2124
export const natMathHelpers = harden({
2225
doCoerce: nat => {

packages/ERTP/src/mathHelpers/setMathHelpers.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import {
1010
elementsCompare,
1111
} from '@agoric/store';
1212

13-
/** @import {MathHelpers, SetValue} from '../types.js' */
13+
/**
14+
* @import {Key} from '@endo/patterns'
15+
* @import {MathHelpers, SetValue} from '../types.js'
16+
*/
1417

1518
// Operations for arrays with unique objects identifying and providing
1619
// information about digital assets. Used for Zoe invites.
@@ -19,7 +22,7 @@ const empty = harden([]);
1922

2023
/**
2124
* @deprecated Replace array-based SetMath with CopySet-based CopySetMath
22-
* @type {MathHelpers<SetValue>}
25+
* @type {MathHelpers<'set', Key, SetValue<Key>>}
2326
*/
2427
export const setMathHelpers = harden({
2528
doCoerce: list => {

packages/ERTP/src/typeGuards.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { M, matches, getInterfaceGuardPayload } from '@endo/patterns';
44
/**
5-
* @import {AmountValue, Ratio} from './types.js'
5+
* @import {AmountValue, Amount, AmountValueHasBound, AmountValueBound, AmountBound, Ratio} from './types.js'
66
* @import {TypedPattern} from '@agoric/internal'
77
*/
88

@@ -68,13 +68,18 @@ const SetValueShape = M.arrayOf(M.key());
6868
*/
6969
const CopyBagValueShape = M.bag();
7070

71+
/**
72+
* @see {AmountValue}
73+
* @see {AssetValueForKind}
74+
*/
7175
const AmountValueShape = M.or(
7276
NatValueShape,
7377
CopySetValueShape,
7478
SetValueShape,
7579
CopyBagValueShape,
7680
);
7781

82+
/** @see {Amount} */
7883
export const AmountShape = { brand: BrandShape, value: AmountValueShape };
7984
harden(AmountShape);
8085

@@ -91,6 +96,22 @@ harden(AmountShape);
9196
*/
9297
export const AmountPatternShape = M.pattern();
9398

99+
/** @see {AmountValueHasBound} */
100+
export const AmountValueHasBoundShape = M.tagged(
101+
'match:has',
102+
M.splitArray([M.pattern(), M.bigint()], [M.record()]),
103+
);
104+
105+
/** @see {AmountValueBound} */
106+
const AmountValueBoundShape = M.or(AmountValueShape, AmountValueHasBoundShape);
107+
108+
/** @see {AmountBound} */
109+
export const AmountBoundShape = {
110+
brand: BrandShape,
111+
value: AmountValueBoundShape,
112+
};
113+
harden(AmountBoundShape);
114+
94115
/** @type {TypedPattern<Ratio>} */
95116
export const RatioShape = { numerator: AmountShape, denominator: AmountShape };
96117
harden(RatioShape);

0 commit comments

Comments
 (0)