Skip to content

Commit 1509c5b

Browse files
committed
take2
1 parent d2b661f commit 1509c5b

File tree

5 files changed

+116
-66
lines changed

5 files changed

+116
-66
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: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
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 { kindOf } 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';
99

1010
/**
11+
* @import {Passable} from '@endo/pass-style'
1112
* @import {CopyBag, CopySet} from '@endo/patterns';
12-
* @import {Amount, AssetValueForKind, Brand, CopyBagAmount, CopySetAmount, MathHelpers, NatAmount, NatValue, SetAmount, SetValue} from './types.js';
13+
* @import {Amount, AmountBound, AmountValueBound, AssetValueForKind, Brand, CopyBagAmount, CopySetAmount, MathHelpers, NatAmount, NatValue, SetAmount, SetValue} from './types.js';
1314
*/
1415

1516
// NB: AssetKind is both a constant for enumerated values and a type for those values.
@@ -75,43 +76,50 @@ const helpers = {
7576
copyBag: copyBagMathHelpers,
7677
};
7778

78-
/** @type {(value: unknown) => 'nat' | 'set' | 'copySet' | 'copyBag'} } */
79-
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';
89-
}
90-
if (matches(value, M.bag())) {
91-
return 'copyBag';
79+
/**
80+
* @template {AmountValueBound} V=AmountValueBound
81+
* @param {V} value
82+
* @param {AssetKind} [defaultKind]
83+
* @returns {AssetKind}
84+
*/
85+
const assertValueBoundGetAssetKind = (value, defaultKind = undefined) => {
86+
const kind = kindOf(value);
87+
switch (kind) {
88+
case 'bigint': {
89+
return 'nat';
90+
}
91+
case 'copyArray': {
92+
return 'set';
93+
}
94+
case 'copySet':
95+
case 'copyBag': {
96+
return kind;
97+
}
98+
case 'match:has': {
99+
if (defaultKind === undefined) {
100+
throw Fail`defaultKind expected for right has-bound ${value}`;
101+
}
102+
return defaultKind;
103+
}
104+
default: {
105+
throw Fail`value ${value} must be an AmountValueBound, not ${q(kind)}`;
106+
}
92107
}
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 {AmountValueBound} V=AmountValueBound
109116
* @param {V} value
117+
* @param {AssetKind} [defaultKind]
110118
* @returns {MathHelpers<V>}
111119
*/
112-
export const assertValueGetHelpers = value =>
120+
export const assertValueBoundGetHelpers = (value, defaultKind = undefined) =>
113121
// @ts-expect-error cast
114-
helpers[assertValueGetAssetKind(value)];
122+
helpers[assertValueBoundGetAssetKind(value, defaultKind)];
115123

116124
/** @type {(allegedBrand: Brand, brand?: Brand) => void} */
117125
const optionalBrandCheck = (allegedBrand, brand) => {
@@ -127,15 +135,19 @@ const optionalBrandCheck = (allegedBrand, brand) => {
127135
/**
128136
* @template {AssetKind} K
129137
* @param {Amount<K>} leftAmount
130-
* @param {Amount<K>} rightAmount
138+
* @param {AmountBound<K>} rightAmountBound
131139
* @param {Brand<K> | undefined} brand
132140
* @returns {MathHelpers<any>}
133141
*/
134-
const checkLRAndGetHelpers = (leftAmount, rightAmount, brand = undefined) => {
142+
const checkLRAndGetHelpers = (
143+
leftAmount,
144+
rightAmountBound,
145+
brand = undefined,
146+
) => {
135147
assertRecord(leftAmount, 'leftAmount');
136-
assertRecord(rightAmount, 'rightAmount');
137-
const { value: leftValue, brand: leftBrand } = leftAmount;
138-
const { value: rightValue, brand: rightBrand } = rightAmount;
148+
assertRecord(rightAmountBound, 'rightAmountBound');
149+
const { brand: leftBrand, value: leftValue } = leftAmount;
150+
const { brand: rightBrand, value: rightValueBound } = rightAmountBound;
139151
assertRemotable(leftBrand, 'leftBrand');
140152
assertRemotable(rightBrand, 'rightBrand');
141153
optionalBrandCheck(leftBrand, brand);
@@ -144,41 +156,41 @@ const checkLRAndGetHelpers = (leftAmount, rightAmount, brand = undefined) => {
144156
Fail`Brands in left ${q(leftBrand)} and right ${q(
145157
rightBrand,
146158
)} 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;
159+
const leftKind = assertValueBoundGetAssetKind(leftValue);
160+
const rightKind = assertValueBoundGetAssetKind(rightValueBound, leftKind);
161+
leftKind === rightKind ||
162+
Fail`The left ${leftAmount} and right amount ${rightAmountBound} had different assetKinds: ${q(leftKind)} vs ${q(rightKind)}`;
163+
return helpers[leftKind];
152164
};
153165

154166
/**
155167
* @template {AssetKind} K
156168
* @param {MathHelpers<AssetValueForKind<K>>} h
157169
* @param {Amount<K>} leftAmount
158-
* @param {Amount<K>} rightAmount
170+
* @param {AmountBound<K>} rightAmountBound
159171
* @returns {[K, K]}
160172
*/
161-
const coerceLR = (h, leftAmount, rightAmount) => {
173+
const coerceLR = (h, leftAmount, rightAmountBound) => {
162174
// @ts-expect-error could be arbitrary subtype
163-
return [h.doCoerce(leftAmount.value), h.doCoerce(rightAmount.value)];
175+
return [h.doCoerce(leftAmount.value), h.doCoerce(rightAmountBound.value)];
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+
const h = checkLRAndGetHelpers(leftAmount, rightAmountBound, brand);
193+
return h.doIsGTE(...coerceLR(h, leftAmount, rightAmountBound));
182194
};
183195

184196
/**
@@ -215,7 +227,7 @@ export const AmountMath = {
215227
*/
216228
make: (brand, allegedValue) => {
217229
assertRemotable(brand, 'brand');
218-
const h = assertValueGetHelpers(allegedValue);
230+
const h = assertValueBoundGetHelpers(allegedValue);
219231
const value = h.doCoerce(allegedValue);
220232
// @ts-expect-error cast
221233
return harden({ brand, value });
@@ -275,7 +287,7 @@ export const AmountMath = {
275287
makeEmptyFromAmount: amount => {
276288
assertRecord(amount, 'amount');
277289
const { brand, value } = amount;
278-
const assetKind = assertValueGetAssetKind(value);
290+
const assetKind = assertValueBoundGetAssetKind(value);
279291
// @ts-expect-error different subtype
280292
return AmountMath.makeEmpty(brand, assetKind);
281293
},
@@ -291,7 +303,7 @@ export const AmountMath = {
291303
const { brand: allegedBrand, value } = amount;
292304
assertRemotable(allegedBrand, 'brand');
293305
optionalBrandCheck(allegedBrand, brand);
294-
const h = assertValueGetHelpers(value);
306+
const h = assertValueBoundGetHelpers(value);
295307
return h.doIsEmpty(h.doCoerce(value));
296308
},
297309
isGTE,
@@ -387,6 +399,6 @@ harden(AmountMath);
387399
export const getAssetKind = amount => {
388400
assertRecord(amount, 'amount');
389401
const { value } = amount;
390-
return assertValueGetAssetKind(value);
402+
return assertValueBoundGetAssetKind(value);
391403
};
392404
harden(getAssetKind);

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+
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);

packages/ERTP/src/types.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { LatestTopic } from '@agoric/notifier';
22
import type { ERef } from '@endo/far';
3-
import type { RemotableObject } from '@endo/pass-style';
4-
import type { CopyBag, CopySet, Key, Pattern } from '@endo/patterns';
3+
import type { RemotableObject, CopyTagged } from '@endo/pass-style';
4+
import type { CopyBag, CopySet, Key, Pattern, Limits } from '@endo/patterns';
55
import type { TypeTag } from '@agoric/internal/src/tagged.js';
66
import type { AssetKind } from './amountMath.js';
77

@@ -69,11 +69,8 @@ export type Amount<
6969
* once or more times, i.e., some positive bigint number of times,
7070
* representing that quantity of the asset represented by that key.
7171
*/
72-
export type AmountValue =
73-
| NatValue
74-
| SetValue
75-
| CopySet
76-
| import('@endo/patterns').CopyBag;
72+
export type AmountValue = NatValue | SetValue | CopySet | CopyBag;
73+
7774
/**
7875
* See doc-comment
7976
* for `AmountValue`.
@@ -90,16 +87,35 @@ export type AssetValueForKind<
9087
: K extends 'copyBag'
9188
? CopyBag<M>
9289
: never;
90+
9391
export type AssetKindForValue<V extends AmountValue> = V extends NatValue
9492
? 'nat'
9593
: V extends SetValue
9694
? 'set'
9795
: V extends CopySet
9896
? 'copySet'
99-
: V extends import('@endo/patterns').CopyBag
97+
: V extends CopyBag
10098
? 'copyBag'
10199
: never;
102100

101+
export type AmountValueHasBound = CopyTagged<
102+
'match:has',
103+
[elementPatt: Pattern, countPatt: bigint, limits?: Limits]
104+
>;
105+
106+
export type AmountValueBound<
107+
K extends AssetKind = AssetKind,
108+
M extends Key = Key,
109+
> = AssetValueForKind<K, M> | AmountValueHasBound;
110+
111+
export type AmountBound<
112+
K extends AssetKind = AssetKind,
113+
M extends Key = Key,
114+
> = {
115+
brand: Brand<K>;
116+
value: AmountValueBound<K, M>;
117+
};
118+
103119
export type Ratio = { numerator: Amount<'nat'>; denominator: Amount<'nat'> };
104120

105121
/** @deprecated */

packages/ERTP/test/unitTests/mintObj.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ test('non-fungible tokens example', async t => {
124124
mint: balletTicketMint,
125125
issuer: balletTicketIssuer,
126126
brand,
127-
} = /**
128-
* @type {IssuerKit<'set', { seat: number; show: string; start: string }>}
129-
*/ (makeIssuerKit('Agoric Ballet Opera tickets', AssetKind.SET));
127+
} = /** @type {IssuerKit<'set', { seat: number; show: string; start: string }>} */ (
128+
makeIssuerKit('Agoric Ballet Opera tickets', AssetKind.SET)
129+
);
130130

131131
const startDateString = new Date(2020, 1, 17, 20, 30).toISOString();
132132

0 commit comments

Comments
 (0)