Skip to content

Commit

Permalink
fixup! first helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Feb 11, 2025
1 parent c4e16da commit ffd5cdd
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 24 deletions.
66 changes: 52 additions & 14 deletions packages/ERTP/src/amountMath.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ const coerceLR = (h, leftAmount, rightAmount) => {
* A is greater than rectangle B depends on whether rectangle A includes
* rectangle B as defined by the logic in MathHelpers.
*
* For non-fungible or sem-fungible amounts, the right operand can also be an
* `AmountBound` which can a normal concrete `Amount` or a specialized pattern:
* A `RecordPattern` of a normal concrete `brand: Brand` and a `value:
* AmountValueHasBound`, as made by `M.has(elementPattern)` or
* `M.has(elementPattern, bigint)`. This represents those elements of the value
* collection that match the elementPattern, if that number is exactly the same
* as the bigint argument. If the second argument of `M.has` is omitted, it
* defaults to `1n`. IOW, the left operand is `>=` such a bound if the total
* number of elements in the left operand that match the element pattern is `>=`
* the bigint argument in the `M.has` pattern.
*
* @template {AssetKind} K
* @param {Amount<K>} leftAmount
* @param {AmountBound<K>} rightAmountBound
Expand Down Expand Up @@ -364,25 +375,52 @@ export const AmountMath = {
return harden({ brand: leftAmount.brand, value });
},
/**
* Returns a new amount that is the leftAmount minus the rightAmount (i.e.
* everything in the leftAmount that is not in the rightAmount). If leftAmount
* doesn't include rightAmount (subtraction results in a negative), throw an
* error. Because the left amount must include the right amount, this is NOT
* equivalent to set subtraction.
* Returns a new amount that is the leftAmount minus the rightAmountBound
* (i.e. everything in the leftAmount that is not in the rightAmountBound). If
* leftAmount doesn't include rightAmountBound (subtraction results in a
* negative), throw an error. Because the left amount must include the right
* amount bound, this is NOT equivalent to set subtraction.
*
* @template {Amount} L
* @template {Amount} R
* @template {AssetKind} K
* @template {Amount<K>} L
* @template {AmountBound<K>} R
* @param {L} leftAmount
* @param {R} rightAmount
* @param {R} rightAmountBound
* @param {Brand} [brand]
* @returns {L extends R ? L : never}
* @returns {L}
*/
subtract: (leftAmount, rightAmount, brand = undefined) => {
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
subtract: (leftAmount, rightAmountBound, brand = undefined) => {
if (isKey(rightAmountBound)) {
const rightAmount = /** @type {Amount<K>} */ (rightAmountBound);
const h = checkLRAndGetHelpers(leftAmount, rightAmount, brand);
// @ts-expect-error cast?
const value = h.doSubtract(...coerceLR(h, leftAmount, rightAmount));
// @ts-expect-error different subtype
return harden({ brand: leftAmount.brand, value });
}
mustMatch(leftAmount, AmountShape, 'left amount');
mustMatch(rightAmountBound, AmountBoundShape, 'right amount bound');
const { brand: leftBrand, value: leftValue } = leftAmount;
const { brand: rightBrand, value: rightValueHasBound } = rightAmountBound;
optionalBrandCheck(leftBrand, brand);
optionalBrandCheck(rightBrand, brand);
leftBrand === rightBrand ||
Fail`Brands in left ${q(leftBrand)} and right ${q(
rightBrand,
)} should match but do not`;
const leftKind = assertValueGetAssetKind(leftValue);
// If it were anything else, it would have been a Key and so taken care of
// in the first case above.
mustMatch(
rightValueHasBound,
AmountValueHasBoundShape,
'right value bound',
);
const h = helpers[leftKind];
// @ts-expect-error cast?
const value = h.doSubtract(...coerceLR(h, leftAmount, rightAmount));
// @ts-expect-error different subtype
return harden({ brand: leftAmount.brand, value });
const value = h.doSubtract(h.doCoerce(leftValue), rightValueHasBound);
// @ts-expect-error cast?
return harden({ brand: leftBrand, value });
},
/**
* Returns the min value between x and y using isGTE
Expand Down
9 changes: 7 additions & 2 deletions packages/ERTP/src/mathHelpers/natMathHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const empty = 0n;
* smallest whole unit such that the `natMathHelpers` never deals with
* fractional parts.
*
* For this 'nat' asset kind, the rightBound is always a bigint, since a a
* fungible number has no "elements" to match against an elementPattern.
*
* @type {MathHelpers<'nat', Key, NatValue>}
*/
export const natMathHelpers = harden({
Expand All @@ -30,9 +33,11 @@ export const natMathHelpers = harden({
},
doMakeEmpty: () => empty,
doIsEmpty: nat => nat === empty,
doIsGTE: (left, right) => left >= right,
doIsGTE: (left, rightBound) =>
left >= /** @type {bigint} */ (/** @type {unknown} */ (rightBound)),
doIsEqual: (left, right) => left === right,
// BigInts don't observably overflow
doAdd: (left, right) => left + right,
doSubtract: (left, right) => Nat(left - right),
doSubtract: (left, rightBound) =>
Nat(left - /** @type {bigint} */ (/** @type {unknown} */ (rightBound))),
});
52 changes: 48 additions & 4 deletions packages/ERTP/src/mathHelpers/setMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// @jessie-check

import { passStyleOf } from '@endo/marshal';
import { passStyleOf } from '@endo/pass-style';
import { isKey, assertKey, mustMatch, matches } from '@endo/patterns';
import {
assertKey,
elementsIsSuperset,
elementsDisjointUnion,
elementsDisjointSubtract,
coerceToElements,
elementsCompare,
} from '@agoric/store';
import { AmountValueHasBoundShape } from '../typeGuards.js';

/**
* @import {Key} from '@endo/patterns'
Expand Down Expand Up @@ -36,8 +37,51 @@ export const setMathHelpers = harden({
},
doMakeEmpty: () => empty,
doIsEmpty: list => passStyleOf(list) === 'copyArray' && list.length === 0,
doIsGTE: elementsIsSuperset,
doIsGTE: (left, rightBound) => {
if (isKey(rightBound)) {
return elementsIsSuperset(left, rightBound);
}
mustMatch(rightBound, AmountValueHasBoundShape, 'right value bound');
const {
payload: [elementPatt, bound],
} = rightBound;
if (bound === 0n) {
return true;
}
let count = 0n;
for (const element of left) {
if (matches(element, elementPatt)) {
count += 1n;
}
if (count >= bound) {
return true;
}
}
},
doIsEqual: (x, y) => elementsCompare(x, y) === 0,
doAdd: elementsDisjointUnion,
doSubtract: elementsDisjointSubtract,
doSubtract: (left, rightBound) => {
if (isKey(rightBound)) {
return elementsDisjointSubtract(left, rightBound);
}
mustMatch(rightBound, AmountValueHasBoundShape, 'right value bound');
const {
payload: [elementPatt, bound],
} = rightBound;
if (bound === 0n) {
return [];
}
let count = 0n;
const result = [];
for (const element of left) {
if (matches(element, elementPatt)) {
count += 1n;
} else {
result.push(element);
}
if (count >= bound) {
return result;
}
}
},
});
2 changes: 1 addition & 1 deletion packages/ERTP/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const AmountPatternShape = M.pattern();
/** @see {AmountValueHasBound} */
export const AmountValueHasBoundShape = M.tagged(
'match:has',
M.splitArray([M.pattern(), M.bigint()], [M.record()]),
M.splitArray([M.pattern(), M.nat()], [M.record()]),
);

/** @see {AmountValueBound} */
Expand Down
7 changes: 4 additions & 3 deletions packages/ERTP/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ export type MathHelpers<
K extends AssetKind = AssetKind,
M extends Key = Key,
V extends AssetValueForKind<K, M> = AssetValueForKind<K, M>,
VBound extends AmountValueHasBound = AmountValueHasBound,
> = {
/**
* Check the kind of this value and
Expand All @@ -471,9 +472,9 @@ export type MathHelpers<
doIsEmpty: (value: V) => boolean;
/**
* Is the left greater than
* or equal to the right?
* or equal to the right bound?
*/
doIsGTE: (left: V, right: V) => boolean;
doIsGTE: (left: V, rightBound: VBound) => boolean;
/**
* Does left equal right?
*/
Expand All @@ -488,7 +489,7 @@ export type MathHelpers<
* removing the right from the left. If something in the right was not in the
* left, we throw an error.
*/
doSubtract: (left: V, right: V) => V;
doSubtract: (left: V, rightBound: VBound) => V;
};
export type NatValue = bigint;
export type SetValue<K extends Key = Key> = K[];

0 comments on commit ffd5cdd

Please sign in to comment.