1
1
import { 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' ;
4
4
5
5
import { natMathHelpers } from './mathHelpers/natMathHelpers.js' ;
6
6
import { setMathHelpers } from './mathHelpers/setMathHelpers.js' ;
7
7
import { copySetMathHelpers } from './mathHelpers/copySetMathHelpers.js' ;
8
8
import { copyBagMathHelpers } from './mathHelpers/copyBagMathHelpers.js' ;
9
- import { AmountBoundShape , AmountShape } from './typeGuards.js' ;
9
+ import { AmountShape } from './typeGuards.js' ;
10
10
11
11
/**
12
12
* @import {Passable} from '@endo/pass-style'
13
13
* @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';
15
15
*/
16
16
17
17
// NB: AssetKind is both a constant for enumerated values and a type for those values.
@@ -183,13 +183,13 @@ const coerceLR = (h, leftAmount, rightAmount) => {
183
183
* For non-fungible or sem-fungible amounts, the right operand can also be an
184
184
* `AmountBound` which can a normal concrete `Amount` or a specialized pattern:
185
185
* 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.
193
193
*
194
194
* @template {AssetKind} K
195
195
* @param {Amount<K> } leftAmount
@@ -198,26 +198,33 @@ const coerceLR = (h, leftAmount, rightAmount) => {
198
198
* @returns {boolean }
199
199
*/
200
200
const 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 ) ;
206
223
}
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 ) ;
219
226
// @ts -expect-error cast?
220
- return h . doIsGTE ( h . doCoerce ( leftValue ) , rightValueHasBound ) ;
227
+ return h . doIsGTE ( ... coerceLR ( h , leftAmount , rightAmount ) ) ;
221
228
} ;
222
229
223
230
/**
@@ -385,30 +392,48 @@ export const AmountMath = {
385
392
* @returns {L }
386
393
*/
387
394
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
+ ) ;
391
426
// @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 } ) ;
395
428
}
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 ) ;
410
433
// @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 } ) ;
412
437
} ,
413
438
/**
414
439
* Returns the min value between x and y using isGTE
0 commit comments