@@ -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
0 commit comments