Skip to content

Commit ffd5cdd

Browse files
committed
fixup! first helpers
1 parent c4e16da commit ffd5cdd

File tree

5 files changed

+112
-24
lines changed

5 files changed

+112
-24
lines changed

packages/ERTP/src/amountMath.js

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ const coerceLR = (h, leftAmount, rightAmount) => {
182182
* A is greater than rectangle B depends on whether rectangle A includes
183183
* rectangle B as defined by the logic in MathHelpers.
184184
*
185+
* For non-fungible or sem-fungible amounts, the right operand can also be an
186+
* `AmountBound` which can a normal concrete `Amount` or a specialized pattern:
187+
* A `RecordPattern` of a normal concrete `brand: Brand` and a `value:
188+
* AmountValueHasBound`, as made by `M.has(elementPattern)` or
189+
* `M.has(elementPattern, bigint)`. This represents those elements of the value
190+
* collection that match the elementPattern, if that number is exactly the same
191+
* as the bigint argument. If the second argument of `M.has` is omitted, it
192+
* defaults to `1n`. IOW, the left operand is `>=` such a bound if the total
193+
* number of elements in the left operand that match the element pattern is `>=`
194+
* the bigint argument in the `M.has` pattern.
195+
*
185196
* @template {AssetKind} K
186197
* @param {Amount<K>} leftAmount
187198
* @param {AmountBound<K>} rightAmountBound
@@ -364,25 +375,52 @@ export const AmountMath = {
364375
return harden({ brand: leftAmount.brand, value });
365376
},
366377
/**
367-
* Returns a new amount that is the leftAmount minus the rightAmount (i.e.
368-
* everything in the leftAmount that is not in the rightAmount). If leftAmount
369-
* doesn't include rightAmount (subtraction results in a negative), throw an
370-
* error. Because the left amount must include the right amount, this is NOT
371-
* equivalent to set subtraction.
378+
* Returns a new amount that is the leftAmount minus the rightAmountBound
379+
* (i.e. everything in the leftAmount that is not in the rightAmountBound). If
380+
* leftAmount doesn't include rightAmountBound (subtraction results in a
381+
* negative), throw an error. Because the left amount must include the right
382+
* amount bound, this is NOT equivalent to set subtraction.
372383
*
373-
* @template {Amount} L
374-
* @template {Amount} R
384+
* @template {AssetKind} K
385+
* @template {Amount<K>} L
386+
* @template {AmountBound<K>} R
375387
* @param {L} leftAmount
376-
* @param {R} rightAmount
388+
* @param {R} rightAmountBound
377389
* @param {Brand} [brand]
378-
* @returns {L extends R ? L : never}
390+
* @returns {L}
379391
*/
380-
subtract: (leftAmount, rightAmount, brand = undefined) => {
381-
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
392+
subtract: (leftAmount, rightAmountBound, brand = undefined) => {
393+
if (isKey(rightAmountBound)) {
394+
const rightAmount = /** @type {Amount<K>} */ (rightAmountBound);
395+
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
396+
// @ts-expect-error cast?
397+
const value = h.doSubtract(...coerceLR(h, leftAmount, rightAmount));
398+
// @ts-expect-error different subtype
399+
return harden({ brand: leftAmount.brand, value });
400+
}
401+
mustMatch(leftAmount, AmountShape, 'left amount');
402+
mustMatch(rightAmountBound, AmountBoundShape, 'right amount bound');
403+
const { brand: leftBrand, value: leftValue } = leftAmount;
404+
const { brand: rightBrand, value: rightValueHasBound } = rightAmountBound;
405+
optionalBrandCheck(leftBrand, brand);
406+
optionalBrandCheck(rightBrand, brand);
407+
leftBrand === rightBrand ||
408+
Fail`Brands in left ${q(leftBrand)} and right ${q(
409+
rightBrand,
410+
)} should match but do not`;
411+
const leftKind = assertValueGetAssetKind(leftValue);
412+
// If it were anything else, it would have been a Key and so taken care of
413+
// in the first case above.
414+
mustMatch(
415+
rightValueHasBound,
416+
AmountValueHasBoundShape,
417+
'right value bound',
418+
);
419+
const h = helpers[leftKind];
382420
// @ts-expect-error cast?
383-
const value = h.doSubtract(...coerceLR(h, leftAmount, rightAmount));
384-
// @ts-expect-error different subtype
385-
return harden({ brand: leftAmount.brand, value });
421+
const value = h.doSubtract(h.doCoerce(leftValue), rightValueHasBound);
422+
// @ts-expect-error cast?
423+
return harden({ brand: leftBrand, value });
386424
},
387425
/**
388426
* Returns the min value between x and y using isGTE

packages/ERTP/src/mathHelpers/natMathHelpers.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const empty = 0n;
1919
* smallest whole unit such that the `natMathHelpers` never deals with
2020
* fractional parts.
2121
*
22+
* For this 'nat' asset kind, the rightBound is always a bigint, since a a
23+
* fungible number has no "elements" to match against an elementPattern.
24+
*
2225
* @type {MathHelpers<'nat', Key, NatValue>}
2326
*/
2427
export const natMathHelpers = harden({
@@ -30,9 +33,11 @@ export const natMathHelpers = harden({
3033
},
3134
doMakeEmpty: () => empty,
3235
doIsEmpty: nat => nat === empty,
33-
doIsGTE: (left, right) => left >= right,
36+
doIsGTE: (left, rightBound) =>
37+
left >= /** @type {bigint} */ (/** @type {unknown} */ (rightBound)),
3438
doIsEqual: (left, right) => left === right,
3539
// BigInts don't observably overflow
3640
doAdd: (left, right) => left + right,
37-
doSubtract: (left, right) => Nat(left - right),
41+
doSubtract: (left, rightBound) =>
42+
Nat(left - /** @type {bigint} */ (/** @type {unknown} */ (rightBound))),
3843
});

packages/ERTP/src/mathHelpers/setMathHelpers.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
// @jessie-check
22

3-
import { passStyleOf } from '@endo/marshal';
3+
import { passStyleOf } from '@endo/pass-style';
4+
import { isKey, assertKey, mustMatch, matches } from '@endo/patterns';
45
import {
5-
assertKey,
66
elementsIsSuperset,
77
elementsDisjointUnion,
88
elementsDisjointSubtract,
99
coerceToElements,
1010
elementsCompare,
1111
} from '@agoric/store';
12+
import { AmountValueHasBoundShape } from '../typeGuards.js';
1213

1314
/**
1415
* @import {Key} from '@endo/patterns'
@@ -36,8 +37,51 @@ export const setMathHelpers = harden({
3637
},
3738
doMakeEmpty: () => empty,
3839
doIsEmpty: list => passStyleOf(list) === 'copyArray' && list.length === 0,
39-
doIsGTE: elementsIsSuperset,
40+
doIsGTE: (left, rightBound) => {
41+
if (isKey(rightBound)) {
42+
return elementsIsSuperset(left, rightBound);
43+
}
44+
mustMatch(rightBound, AmountValueHasBoundShape, 'right value bound');
45+
const {
46+
payload: [elementPatt, bound],
47+
} = rightBound;
48+
if (bound === 0n) {
49+
return true;
50+
}
51+
let count = 0n;
52+
for (const element of left) {
53+
if (matches(element, elementPatt)) {
54+
count += 1n;
55+
}
56+
if (count >= bound) {
57+
return true;
58+
}
59+
}
60+
},
4061
doIsEqual: (x, y) => elementsCompare(x, y) === 0,
4162
doAdd: elementsDisjointUnion,
42-
doSubtract: elementsDisjointSubtract,
63+
doSubtract: (left, rightBound) => {
64+
if (isKey(rightBound)) {
65+
return elementsDisjointSubtract(left, rightBound);
66+
}
67+
mustMatch(rightBound, AmountValueHasBoundShape, 'right value bound');
68+
const {
69+
payload: [elementPatt, bound],
70+
} = rightBound;
71+
if (bound === 0n) {
72+
return [];
73+
}
74+
let count = 0n;
75+
const result = [];
76+
for (const element of left) {
77+
if (matches(element, elementPatt)) {
78+
count += 1n;
79+
} else {
80+
result.push(element);
81+
}
82+
if (count >= bound) {
83+
return result;
84+
}
85+
}
86+
},
4387
});

packages/ERTP/src/typeGuards.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const AmountPatternShape = M.pattern();
9999
/** @see {AmountValueHasBound} */
100100
export const AmountValueHasBoundShape = M.tagged(
101101
'match:has',
102-
M.splitArray([M.pattern(), M.bigint()], [M.record()]),
102+
M.splitArray([M.pattern(), M.nat()], [M.record()]),
103103
);
104104

105105
/** @see {AmountValueBound} */

packages/ERTP/src/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ export type MathHelpers<
453453
K extends AssetKind = AssetKind,
454454
M extends Key = Key,
455455
V extends AssetValueForKind<K, M> = AssetValueForKind<K, M>,
456+
VBound extends AmountValueHasBound = AmountValueHasBound,
456457
> = {
457458
/**
458459
* Check the kind of this value and
@@ -471,9 +472,9 @@ export type MathHelpers<
471472
doIsEmpty: (value: V) => boolean;
472473
/**
473474
* Is the left greater than
474-
* or equal to the right?
475+
* or equal to the right bound?
475476
*/
476-
doIsGTE: (left: V, right: V) => boolean;
477+
doIsGTE: (left: V, rightBound: VBound) => boolean;
477478
/**
478479
* Does left equal right?
479480
*/
@@ -488,7 +489,7 @@ export type MathHelpers<
488489
* removing the right from the left. If something in the right was not in the
489490
* left, we throw an error.
490491
*/
491-
doSubtract: (left: V, right: V) => V;
492+
doSubtract: (left: V, rightBound: VBound) => V;
492493
};
493494
export type NatValue = bigint;
494495
export type SetValue<K extends Key = Key> = K[];

0 commit comments

Comments
 (0)