Skip to content

Commit a04ee47

Browse files
committed
fixup! refactor for containerHasSplit
1 parent 12de3f0 commit a04ee47

File tree

11 files changed

+175
-52
lines changed

11 files changed

+175
-52
lines changed

packages/ERTP/src/amountMath.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
* AmountValueHasBound`, 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

packages/ERTP/src/legacy-payment-helpers.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const claim = async (
4040
) => {
4141
const srcPayment = await srcPaymentP;
4242
return E.when(E(recoveryPurse).deposit(srcPayment, optAmountShape), amount =>
43+
// @ts-expect-error Why doesn't TS know that an Amount is a kind
44+
// of AmountBound?
4345
E(recoveryPurse).withdraw(amount),
4446
);
4547
};
@@ -87,6 +89,8 @@ export const combine = async (
8789
if (optTotalAmount !== undefined) {
8890
mustMatch(total, optTotalAmount, 'amount');
8991
}
92+
// @ts-expect-error Why doesn't TS know that an Amount is a kind
93+
// of AmountBound?
9094
return E(recoveryPurse).withdraw(total);
9195
};
9296
harden(combine);
@@ -111,7 +115,11 @@ export const split = async (recoveryPurse, srcPaymentP, paymentAmountA) => {
111115
const srcAmount = await E(recoveryPurse).deposit(srcPayment);
112116
const paymentAmountB = AmountMath.subtract(srcAmount, paymentAmountA);
113117
return Promise.all([
118+
// @ts-expect-error Why doesn't TS know that an Amount is a kind
119+
// of AmountBound?
114120
E(recoveryPurse).withdraw(paymentAmountA),
121+
// @ts-expect-error Why doesn't TS know that an Amount is a kind
122+
// of AmountBound?
115123
E(recoveryPurse).withdraw(paymentAmountB),
116124
]);
117125
};
@@ -147,6 +155,12 @@ export const splitMany = async (recoveryPurse, srcPaymentP, amounts) => {
147155
AmountMath.isEqual(srcAmount, total) ||
148156
Fail`rights were not conserved: ${total} vs ${srcAmount}`;
149157

150-
return Promise.all(amounts.map(amount => E(recoveryPurse).withdraw(amount)));
158+
return Promise.all(
159+
amounts.map(amount =>
160+
// @ts-expect-error Why doesn't TS know that an Amount is a kind
161+
// of AmountBound?
162+
E(recoveryPurse).withdraw(amount),
163+
),
164+
);
151165
};
152166
harden(splitMany);

packages/ERTP/src/mathHelpers/copySetMathHelpers.js

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// @jessie-check
22

3+
import { Fail } from '@endo/errors';
4+
import { assertChecker } from '@endo/pass-style';
35
import {
6+
isKey,
47
keyEQ,
58
makeCopySet,
69
mustMatch,
@@ -9,7 +12,9 @@ import {
912
setIsSuperset,
1013
setDisjointUnion,
1114
setDisjointSubtract,
12-
} from '@agoric/store';
15+
containerHasSplit,
16+
kindOf,
17+
} from '@endo/patterns';
1318

1419
/**
1520
* @import {Key, CopySet} from '@endo/patterns'
@@ -27,8 +32,38 @@ export const copySetMathHelpers = harden({
2732
},
2833
doMakeEmpty: () => empty,
2934
doIsEmpty: set => getCopySetKeys(set).length === 0,
30-
doIsGTE: setIsSuperset,
35+
doIsGTE: (left, rightBound) => {
36+
if (isKey(rightBound)) {
37+
return setIsSuperset(left, rightBound);
38+
}
39+
kindOf(rightBound) === 'match:containerHas' ||
40+
Fail`rightBound must either be a key or an M.containerHas pattern ${rightBound}`;
41+
const {
42+
payload: [elementPatt, bound],
43+
} = rightBound;
44+
return !!containerHasSplit(left, elementPatt, bound);
45+
},
3146
doIsEqual: keyEQ,
3247
doAdd: setDisjointUnion,
33-
doSubtract: setDisjointSubtract,
48+
doSubtract: (left, rightBound) => {
49+
if (isKey(rightBound)) {
50+
return setDisjointSubtract(left, rightBound);
51+
}
52+
kindOf(rightBound) === 'match:containerHas' ||
53+
Fail`rightBound must either be a key or an M.containerHas pattern ${rightBound}`;
54+
const {
55+
payload: [elementPatt, bound],
56+
} = rightBound;
57+
// Passing in `assertChecker` as the `check` argument should guarantee
58+
// that the result is not `undefined`. It would have thrown first.
59+
const [_, result] = containerHasSplit(
60+
left,
61+
elementPatt,
62+
bound,
63+
false,
64+
true,
65+
assertChecker,
66+
);
67+
return harden(result);
68+
},
3469
});

packages/ERTP/src/mathHelpers/setMathHelpers.js

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
import { Fail } from '@endo/errors';
44
import { passStyleOf, assertChecker } from '@endo/pass-style';
5-
import { isKey, assertKey, elementsHasSplit, kindOf } from '@endo/patterns';
65
import {
6+
isKey,
7+
assertKey,
78
elementsIsSuperset,
89
elementsDisjointUnion,
910
elementsDisjointSubtract,
1011
coerceToElements,
1112
elementsCompare,
12-
} from '@agoric/store';
13+
containerHasSplit,
14+
kindOf,
15+
} from '@endo/patterns';
1316

1417
/**
1518
* @import {Key} from '@endo/patterns'
@@ -41,31 +44,32 @@ export const setMathHelpers = harden({
4144
if (isKey(rightBound)) {
4245
return elementsIsSuperset(left, rightBound);
4346
}
44-
kindOf(rightBound) === 'match:has' ||
45-
Fail`rightBound must either be a key or an M.has pattern ${rightBound}`;
47+
kindOf(rightBound) === 'match:containerHas' ||
48+
Fail`rightBound must either be a key or an M.containerHas pattern ${rightBound}`;
4649
const {
4750
payload: [elementPatt, bound],
4851
} = rightBound;
49-
return elementsHasSplit(left, elementPatt, bound);
52+
return !!containerHasSplit(left, elementPatt, bound);
5053
},
5154
doIsEqual: (x, y) => elementsCompare(x, y) === 0,
5255
doAdd: elementsDisjointUnion,
5356
doSubtract: (left, rightBound) => {
5457
if (isKey(rightBound)) {
5558
return elementsDisjointSubtract(left, rightBound);
5659
}
57-
kindOf(rightBound) === 'match:has' ||
58-
Fail`rightBound must either be a key or an M.has pattern ${rightBound}`;
60+
kindOf(rightBound) === 'match:containerHas' ||
61+
Fail`rightBound must either be a key or an M.containerHas pattern ${rightBound}`;
5962
const {
6063
payload: [elementPatt, bound],
6164
} = rightBound;
62-
const result = [];
63-
elementsHasSplit(
65+
// Passing in `assertChecker` as the `check` argument should guarantee
66+
// that the result is not `undefined`. It would have thrown first.
67+
const [_, result] = containerHasSplit(
6468
left,
6569
elementPatt,
6670
bound,
67-
undefined,
68-
result,
71+
false,
72+
true,
6973
assertChecker,
7074
);
7175
return harden(result);

packages/ERTP/src/paymentLedger.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
// @jessie-check
22

3-
/// <reference types="@agoric/store/exported.js" />
4-
53
import { X, q, Fail, annotateError } from '@endo/errors';
64
import { isPromise } from '@endo/promise-kit';
7-
import { mustMatch, M, keyEQ } from '@agoric/store';
5+
import { mustMatch, M, keyEQ } from '@endo/patterns';
86
import { AmountMath } from './amountMath.js';
97
import { preparePaymentKind } from './payment.js';
108
import { preparePurseKind } from './purse.js';
@@ -13,7 +11,7 @@ import { BrandI, makeIssuerInterfaces } from './typeGuards.js';
1311

1412
/**
1513
* @import {Key, Pattern} from '@endo/patterns';
16-
* @import {Amount, AssetKind, DisplayInfo, PaymentLedger, Payment, Brand, RecoverySetsOption, Purse, Issuer, Mint} from './types.js'
14+
* @import {Amount, AssetKind, DisplayInfo, PaymentLedger, Payment, Brand, RecoverySetsOption, Purse, Issuer, Mint, AmountBound} from './types.js'
1715
* @import {ShutdownWithFailure} from '@agoric/swingset-vat'
1816
* @import {TypedPattern} from '@agoric/internal';
1917
*/

packages/ERTP/src/purse.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,16 @@ export const preparePurseKind = (
108108
updateBalance(purse, balanceStore.getAmount());
109109
return srcPaymentBalance;
110110
},
111-
withdraw(amount) {
111+
withdraw(amountBound) {
112112
const { state } = this;
113113
const { purse } = this.facets;
114114

115115
const optRecoverySet = maybeRecoverySet(state);
116116
const balanceStore = makeAmountStore(state, 'currentBalance');
117-
// Note COMMIT POINT within withdraw.
117+
// Note COMMIT POINT within withdrawInternal.
118118
const payment = withdrawInternal(
119119
balanceStore,
120-
amount,
120+
amountBound,
121121
optRecoverySet,
122122
);
123123
updateBalance(purse, balanceStore.getAmount());

packages/ERTP/src/typeGuards.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ harden(AmountShape);
9898
export const AmountPatternShape = M.pattern();
9999

100100
/** @see {HasBound} */
101-
export const HasBoundShape = M.tagged('match:has');
101+
export const HasBoundShape = M.tagged('match:containerHas');
102102

103103
/** @see {AmountValueBound} */
104104
const AmountValueBoundShape = M.or(AmountValueShape, HasBoundShape);
@@ -242,7 +242,7 @@ export const makeIssuerInterfaces = (
242242
.optional(AmountPatternShape)
243243
.returns(amountShape),
244244
getDepositFacet: M.call().returns(DepositFacetShape),
245-
withdraw: M.call(amountShape).returns(PaymentShape),
245+
withdraw: M.call(AmountBoundShape).returns(PaymentShape),
246246
getRecoverySet: M.call().returns(M.setOf(PaymentShape)),
247247
recoverAll: M.call().returns(amountShape),
248248
});

packages/ERTP/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export type AssetKindForValue<V extends AmountValue> = V extends NatValue
9999
: never;
100100

101101
export type HasBound = CopyTagged<
102-
'match:has',
102+
'match:containerHas',
103103
[elementPatt: Pattern, countPatt: bigint, limits?: Limits]
104104
>;
105105

@@ -379,7 +379,7 @@ export type PurseMethods<
379379
* Withdraw amount
380380
* from this purse into a new Payment.
381381
*/
382-
withdraw: (amount: Amount<K, M>) => Payment<K, M>;
382+
withdraw: (amount: AmountBound<K, M>) => Payment<K, M>;
383383
/**
384384
* The set of payments
385385
* withdrawn from this purse that are still live. These are the payments that
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
2+
import { M, makeCopySet } from '@endo/patterns';
3+
4+
import { AmountMath as m } from '../../../src/index.js';
5+
import { mockCopySetBrand as mockBrand } from './mockBrand.js';
6+
7+
const mock = value => harden({ brand: mockBrand, value });
8+
const mockCopySet = elements => mock(makeCopySet(elements));
9+
10+
// The "unit tests" for MathHelpers actually make the calls through
11+
// AmountMath so that we can test that any duplication is handled
12+
// correctly.
13+
14+
test('set minus setBound', t => {
15+
const specimen = mockCopySet(['a', 'b', 'c']);
16+
t.deepEqual(
17+
m.subtract(specimen, mock(M.containerHas(M.any(), 1n))),
18+
mockCopySet(['b', 'c']),
19+
);
20+
t.deepEqual(
21+
m.subtract(specimen, mock(M.containerHas('a', 1n))),
22+
mockCopySet(['b', 'c']),
23+
);
24+
t.deepEqual(
25+
m.subtract(specimen, mock(M.containerHas(M.any(), 1n))),
26+
mockCopySet(['b', 'c']),
27+
);
28+
t.deepEqual(
29+
m.subtract(specimen, mock(M.containerHas(M.any(), 3n))),
30+
mockCopySet([]),
31+
);
32+
t.deepEqual(
33+
m.subtract(specimen, mock(M.containerHas(M.any()))),
34+
mockCopySet(['b', 'c']),
35+
);
36+
37+
t.throws(() => m.subtract(specimen, mock(M.containerHas(M.any(), 4n))), {
38+
message: 'Has only "[3n]" matches, but needs "[4n]"',
39+
});
40+
41+
t.throws(() => m.subtract(specimen, mock(M.containerHas('d', 1n))), {
42+
message: 'Has only "[0n]" matches, but needs "[1n]"',
43+
});
44+
45+
t.throws(() => m.subtract(specimen, mock(M.containerHas('d'))), {
46+
message: 'Has only "[0n]" matches, but needs "[1n]"',
47+
});
48+
});
49+
50+
test('set isGTE setBound', t => {
51+
const specimen = mockCopySet(['a', 'b', 'c']);
52+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any(), 1n))));
53+
t.true(m.isGTE(specimen, mock(M.containerHas('a', 1n))));
54+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any(), 1n))));
55+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any(), 3n))));
56+
t.false(m.isGTE(specimen, mock(M.containerHas(M.any(), 4n))));
57+
t.false(m.isGTE(specimen, mock(M.containerHas('d', 1n))));
58+
59+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any()))));
60+
});

packages/ERTP/test/unitTests/mathHelpers/setBoundMathHelpers.test.js

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,49 @@ const mock = value => harden({ brand: mockBrand, value });
1111
// correctly.
1212

1313
test('set minus setBound', t => {
14+
const specimen = mock(['a', 'b', 'c']);
1415
t.deepEqual(
15-
m.subtract(mock(['a', 'b']), mock(M.has(M.any(), 1n))),
16-
mock(['b']),
16+
m.subtract(specimen, mock(M.containerHas(M.any(), 1n))),
17+
mock(['b', 'c']),
1718
);
18-
t.deepEqual(m.subtract(mock(['a', 'b']), mock(M.has('a', 1n))), mock(['b']));
1919
t.deepEqual(
20-
m.subtract(mock(['a', 'b']), mock(M.has(M.any(), 1n))),
21-
mock(['b']),
20+
m.subtract(specimen, mock(M.containerHas('a', 1n))),
21+
mock(['b', 'c']),
22+
);
23+
t.deepEqual(
24+
m.subtract(specimen, mock(M.containerHas(M.any(), 1n))),
25+
mock(['b', 'c']),
26+
);
27+
t.deepEqual(
28+
m.subtract(specimen, mock(M.containerHas(M.any(), 3n))),
29+
mock([]),
30+
);
31+
t.deepEqual(
32+
m.subtract(specimen, mock(M.containerHas(M.any()))),
33+
mock(['b', 'c']),
2234
);
23-
t.deepEqual(m.subtract(mock(['a', 'b']), mock(M.has(M.any(), 2n))), mock([]));
24-
t.deepEqual(m.subtract(mock(['a', 'b']), mock(M.has(M.any()))), mock(['b']));
2535

26-
t.throws(() => m.subtract(mock(['a', 'b']), mock(M.has(M.any(), 3n))), {
27-
message: 'Has only "[2n]" matches, but needs "[3n]"',
36+
t.throws(() => m.subtract(specimen, mock(M.containerHas(M.any(), 4n))), {
37+
message: 'Has only "[3n]" matches, but needs "[4n]"',
2838
});
2939

30-
t.throws(() => m.subtract(mock(['a', 'b']), mock(M.has('c', 1n))), {
40+
t.throws(() => m.subtract(specimen, mock(M.containerHas('d', 1n))), {
3141
message: 'Has only "[0n]" matches, but needs "[1n]"',
3242
});
3343

34-
t.throws(() => m.subtract(mock(['a', 'b']), mock(M.has('c'))), {
44+
t.throws(() => m.subtract(specimen, mock(M.containerHas('d'))), {
3545
message: 'Has only "[0n]" matches, but needs "[1n]"',
3646
});
3747
});
3848

3949
test('set isGTE setBound', t => {
40-
t.true(m.isGTE(mock(['a', 'b']), mock(M.has(M.any(), 1n))));
41-
t.true(m.isGTE(mock(['a', 'b']), mock(M.has('a', 1n))));
42-
t.true(m.isGTE(mock(['a', 'b']), mock(M.has(M.any(), 1n))));
43-
t.true(m.isGTE(mock(['a', 'b']), mock(M.has(M.any(), 2n))));
44-
t.false(m.isGTE(mock(['a', 'b']), mock(M.has(M.any(), 3n))));
45-
t.false(m.isGTE(mock(['a', 'b']), mock(M.has('c', 1n))));
46-
47-
t.true(m.isGTE(mock(['a', 'b']), mock(M.has(M.any()))));
50+
const specimen = mock(['a', 'b', 'c']);
51+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any(), 1n))));
52+
t.true(m.isGTE(specimen, mock(M.containerHas('a', 1n))));
53+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any(), 1n))));
54+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any(), 3n))));
55+
t.false(m.isGTE(specimen, mock(M.containerHas(M.any(), 4n))));
56+
t.false(m.isGTE(specimen, mock(M.containerHas('d', 1n))));
57+
58+
t.true(m.isGTE(specimen, mock(M.containerHas(M.any()))));
4859
});

0 commit comments

Comments
 (0)