11import { q , Fail } from '@endo/errors' ;
2- import { assertRemotable , assertRecord } from '@endo/pass-style' ;
3- import { isKey , kindOf , mustMatch } from '@endo/patterns' ;
2+ import { assertRemotable , assertRecord , assertChecker } from '@endo/pass-style' ;
3+ import { containerHasSplit , kindOf , mustMatch } from '@endo/patterns' ;
44
55import { natMathHelpers } from './mathHelpers/natMathHelpers.js' ;
66import { setMathHelpers } from './mathHelpers/setMathHelpers.js' ;
77import { copySetMathHelpers } from './mathHelpers/copySetMathHelpers.js' ;
88import { copyBagMathHelpers } from './mathHelpers/copyBagMathHelpers.js' ;
9- import { AmountBoundShape , AmountShape } from './typeGuards.js' ;
9+ import { AmountShape } from './typeGuards.js' ;
1010
1111/**
1212 * @import {Passable} from '@endo/pass-style'
1313 * @import {Key, CopyBag, CopySet} from '@endo/patterns';
14- * @import {Amount, AmountBound, AmountValue, AssetValueForKind, Brand, CopyBagAmount, CopySetAmount, MathHelpers, NatAmount, NatValue, SetAmount, SetValue} from './types.js';
14+ * @import {Amount, AmountBound, AmountHasBound, AmountValue, AssetValueForKind, Brand, CopyBagAmount, CopySetAmount, MathHelpers, NatAmount, NatValue, SetAmount, SetValue} from './types.js';
1515 */
1616
1717// NB: AssetKind is both a constant for enumerated values and a type for those values.
@@ -183,13 +183,13 @@ const coerceLR = (h, leftAmount, rightAmount) => {
183183 * For non-fungible or sem-fungible amounts, the right operand can also be an
184184 * `AmountBound` which can a normal concrete `Amount` or a specialized pattern:
185185 * A `RecordPattern` of a normal concrete `brand: Brand` and a `value:
186- * AmountValueHasBound `, as made by `M.has (elementPattern)` or
187- * `M.has (elementPattern, bigint)`. This represents those elements of the value
188- * collection that match the elementPattern, if that number is exactly the same
189- * as the bigint argument. If the second argument of `M.has` is omitted, it
190- * defaults to `1n`. IOW, the left operand is `>=` such a bound if the total
191- * number of elements in the left operand that match the element pattern is `>=`
192- * the bigint argument in the `M.has ` pattern.
186+ * HasBound `, as made by `M.containerHas (elementPattern)` or
187+ * `M.containerHas (elementPattern, bigint)`. This represents those elements of
188+ * the value collection that match the elementPattern, if that number is exactly
189+ * the same as the bigint argument. If the second argument of `M.containerHas`
190+ * is omitted, it defaults to `1n`. IOW, the left operand is `>=` such a bound
191+ * if the total number of elements in the left operand that match the element
192+ * pattern is `>=` the bigint argument in the `M.containerHas ` pattern.
193193 *
194194 * @template {AssetKind} K
195195 * @param {Amount<K> } leftAmount
@@ -198,26 +198,33 @@ const coerceLR = (h, leftAmount, rightAmount) => {
198198 * @returns {boolean }
199199 */
200200const isGTE = ( leftAmount , rightAmountBound , brand = undefined ) => {
201- if ( isKey ( rightAmountBound ) ) {
202- const rightAmount = /** @type {Amount<K> } */ ( rightAmountBound ) ;
203- const h = checkLRAndGetHelpers ( leftAmount , rightAmount , brand ) ;
204- // @ts -expect-error cast?
205- return h . doIsGTE ( ...coerceLR ( h , leftAmount , rightAmount ) ) ;
201+ if ( kindOf ( rightAmountBound ) === 'match:containerHas' ) {
202+ mustMatch ( leftAmount , AmountShape , 'left amount' ) ;
203+ const { brand : leftBrand , value : leftValue } = leftAmount ;
204+ const {
205+ brand : rightBrand ,
206+ value : {
207+ payload : [ elementPatt , bound ] ,
208+ } ,
209+ } = /** @type {AmountHasBound } */ ( rightAmountBound ) ;
210+ optionalBrandCheck ( leftBrand , brand ) ;
211+ optionalBrandCheck ( rightBrand , brand ) ;
212+ leftBrand === rightBrand ||
213+ Fail `Brands in left ${ q ( leftBrand ) } and right ${ q (
214+ rightBrand ,
215+ ) } should match but do not`;
216+ const leftKind = assertValueGetAssetKind ( leftValue ) ;
217+ leftKind !== 'nat' ||
218+ Fail `can only use M.containerHas on container assets, not nat: ${ leftValue } ` ;
219+ const h = helpers [ leftKind ] ;
220+ // @ts -expect-error param type of doCoerce should not be never
221+ const lv = h . doCoerce ( leftValue ) ;
222+ return ! ! containerHasSplit ( lv , elementPatt , bound ) ;
206223 }
207- mustMatch ( leftAmount , AmountShape , 'left amount' ) ;
208- mustMatch ( rightAmountBound , AmountBoundShape , 'right amount bound' ) ;
209- const { brand : leftBrand , value : leftValue } = leftAmount ;
210- const { brand : rightBrand , value : rightValueHasBound } = rightAmountBound ;
211- optionalBrandCheck ( leftBrand , brand ) ;
212- optionalBrandCheck ( rightBrand , brand ) ;
213- leftBrand === rightBrand ||
214- Fail `Brands in left ${ q ( leftBrand ) } and right ${ q (
215- rightBrand ,
216- ) } should match but do not`;
217- const leftKind = assertValueGetAssetKind ( leftValue ) ;
218- const h = helpers [ leftKind ] ;
224+ const rightAmount = /** @type {Amount<K> } */ ( rightAmountBound ) ;
225+ const h = checkLRAndGetHelpers ( leftAmount , rightAmount , brand ) ;
219226 // @ts -expect-error cast?
220- return h . doIsGTE ( h . doCoerce ( leftValue ) , rightValueHasBound ) ;
227+ return h . doIsGTE ( ... coerceLR ( h , leftAmount , rightAmount ) ) ;
221228} ;
222229
223230/**
@@ -385,30 +392,48 @@ export const AmountMath = {
385392 * @returns {L }
386393 */
387394 subtract : ( leftAmount , rightAmountBound , brand = undefined ) => {
388- if ( isKey ( rightAmountBound ) ) {
389- const rightAmount = /** @type {Amount<K> } */ ( rightAmountBound ) ;
390- const h = checkLRAndGetHelpers ( leftAmount , rightAmount , brand ) ;
395+ if ( kindOf ( rightAmountBound ) === 'match:containerHas' ) {
396+ mustMatch ( leftAmount , AmountShape , 'left amount' ) ;
397+ const { brand : leftBrand , value : leftValue } = leftAmount ;
398+ const {
399+ brand : rightBrand ,
400+ value : {
401+ payload : [ elementPatt , bound ] ,
402+ } ,
403+ } = /** @type {AmountHasBound } */ ( rightAmountBound ) ;
404+ optionalBrandCheck ( leftBrand , brand ) ;
405+ optionalBrandCheck ( rightBrand , brand ) ;
406+ leftBrand === rightBrand ||
407+ Fail `Brands in left ${ q ( leftBrand ) } and right ${ q (
408+ rightBrand ,
409+ ) } should match but do not`;
410+ const leftKind = assertValueGetAssetKind ( leftValue ) ;
411+ leftKind !== 'nat' ||
412+ Fail `can only use M.containerHas on container assets, not nat: ${ leftValue } ` ;
413+ const h = helpers [ leftKind ] ;
414+ // @ts -expect-error param type of doCoerce should not be never
415+ const lv = h . doCoerce ( leftValue ) ;
416+ // Passing in `assertChecker` as the `check` argument should guarantee
417+ // that `value` is not `undefined`. It would have thrown first.
418+ const [ _ , value ] = containerHasSplit (
419+ lv ,
420+ elementPatt ,
421+ bound ,
422+ false ,
423+ true ,
424+ assertChecker ,
425+ ) ;
391426 // @ts -expect-error cast?
392- const value = h . doSubtract ( ...coerceLR ( h , leftAmount , rightAmount ) ) ;
393- // @ts -expect-error different subtype
394- return harden ( { brand : leftAmount . brand , value } ) ;
427+ return harden ( { brand : leftBrand , value } ) ;
395428 }
396- mustMatch ( leftAmount , AmountShape , 'left amount' ) ;
397- mustMatch ( rightAmountBound , AmountBoundShape , 'right amount bound' ) ;
398- const { brand : leftBrand , value : leftValue } = leftAmount ;
399- const { brand : rightBrand , value : rightValueHasBound } = rightAmountBound ;
400- optionalBrandCheck ( leftBrand , brand ) ;
401- optionalBrandCheck ( rightBrand , brand ) ;
402- leftBrand === rightBrand ||
403- Fail `Brands in left ${ q ( leftBrand ) } and right ${ q (
404- rightBrand ,
405- ) } should match but do not`;
406- const leftKind = assertValueGetAssetKind ( leftValue ) ;
407- const h = helpers [ leftKind ] ;
408- // @ts -expect-error cast?
409- const value = h . doSubtract ( h . doCoerce ( leftValue ) , rightValueHasBound ) ;
429+ // @ts -expect-error I don't know why TS complains here but not in
430+ // the identical case in isGTE
431+ const rightAmount = /** @type {Amount<K> } */ ( rightAmountBound ) ;
432+ const h = checkLRAndGetHelpers ( leftAmount , rightAmount , brand ) ;
410433 // @ts -expect-error cast?
411- return harden ( { brand : leftBrand , value } ) ;
434+ const value = h . doSubtract ( ...coerceLR ( h , leftAmount , rightAmount ) ) ;
435+ // @ts -expect-error different subtype
436+ return harden ( { brand : leftAmount . brand , value } ) ;
412437 } ,
413438 /**
414439 * Returns the min value between x and y using isGTE
0 commit comments