Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Support fungible invites directly #1870

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/ERTP/src/amountMath.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import { mustBeComparable } from '@agoric/same-structure';

import './types';
import natMathHelpers from './mathHelpers/natMathHelpers';
import strSetMathHelpers from './mathHelpers/strSetMathHelpers';
import setMathHelpers from './mathHelpers/setMathHelpers';
import strSetMathHelpers from './mathHelpers/strSetMathHelpers';
import strBagMathHelpers from './mathHelpers/strBagMathHelpers';

// We want an enum, but narrowed to the AmountMathKind type.
/**
* Constants for the kinds of amountMath we support.
*
* @type {{ NAT: 'nat', SET: 'set', STRING_SET: 'strSet' }}
* @type {{ NAT: 'nat', SET: 'set', STRING_SET: 'strSet' , STRING_BAG: 'strBag' }}
*/
const MathKind = {
NAT: 'nat',
SET: 'set',
STRING_SET: 'strSet',
STRING_BAG: 'strBag',
};
harden(MathKind);
export { MathKind };
Expand Down Expand Up @@ -64,7 +66,7 @@ export { MathKind };
* makeLocalAmountMath(issuer).
*
* AmountMath exports MathKind, which contains constants for the kinds:
* NAT, SET, and STRING_SET.
* NAT, SET, STRING_SET, and STRING_BAG.
*
* Each issuer of digital assets has an associated brand in a one-to-one
* mapping. In untrusted contexts, such as in analyzing payments and
Expand All @@ -81,8 +83,9 @@ function makeAmountMath(brand, amountMathKind) {

const mathHelpers = {
nat: natMathHelpers,
strSet: strSetMathHelpers,
set: setMathHelpers,
strSet: strSetMathHelpers,
strBag: strBagMathHelpers,
};
const helpers = mathHelpers[amountMathKind];
assert(
Expand Down
169 changes: 169 additions & 0 deletions packages/ERTP/src/mathHelpers/strBagMathHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// @ts-check

import Nat from '@agoric/nat';
import { passStyleOf } from '@agoric/marshal';
import { assert, details } from '@agoric/assert';

const identity = harden({ strings: [], counts: [] });

const assertUniqueSorted = strings => {
const len = strings.length;
for (let i = 1; i < len; i += 1) {
const leftStr = strings[i - 1];
const rightStr = strings[i];
assert(leftStr !== rightStr, details`value has duplicates: ${strings}`);
assert(leftStr < rightStr, details`value not sorted ${strings}`);
}
};

/**
* Operations for arrays with unique string elements. More information
* about these assets might be provided by some other mechanism, such as
* an off-chain API or physical records. strBagMathHelpers are highly
* efficient, but if the users rely on an external resource to learn
* more about the digital assets (for example, looking up a string ID
* in a database), the administrator of the external resource could
* potentially change the external definition at any time.
*
* @type {MathHelpers}
*/
const strBagMathHelpers = harden({
doCoerce: bag => {
const { strings, counts } = bag;
assert(passStyleOf(strings) === 'copyArray', 'value must be an array');
assert(passStyleOf(counts) === 'copyArray', 'value must be an array');
assert.equal(strings.length, counts.length);
strings.forEach(str => assert.typeof(str, 'string'));
counts.forEach(count => Nat(count) && assert(count >= 1));
assertUniqueSorted(strings);
return harden(bag);
},
doGetEmpty: _ => identity,
doIsEmpty: ({ strings }) =>
passStyleOf(strings) === 'copyArray' && strings.length === 0,
doIsGTE: (leftBag, rightBag) => {
const { strings: leftStrings, counts: leftCounts } = leftBag;
const { strings: rightStrings, counts: rightCounts } = rightBag;
let leftI = 0;
let rightI = 0;
const leftLen = leftStrings.length;
const rightLen = rightStrings.length;
while (leftI < leftLen && rightI < rightLen) {
const leftStr = leftStrings[leftI];
const rightStr = rightStrings[rightI];
if (leftStr < rightStr) {
// elements of left not in right. Fine
leftI += 1;
} else if (leftStr > rightStr) {
// elements of right not in left.
return false;
} else if (leftCounts[leftI] < rightCounts[rightI]) {
// more of this element on right than in left.
return false;
} else {
leftI += 1;
rightI += 1;
}
}
// Are there no elements of right remaining?
return rightI >= rightLen;
},
doIsEqual: (leftBag, rightBag) => {
const { strings: leftStrings, counts: leftCounts } = leftBag;
const { strings: rightStrings, counts: rightCounts } = rightBag;
if (leftStrings.length !== rightStrings.length) {
return false;
}
return leftStrings.every(
(leftStr, i) =>
leftStr === rightStrings[i] && leftCounts[i] === rightCounts[i],
);
},
doAdd: (leftBag, rightBag) => {
const { strings: leftStrings, counts: leftCounts } = leftBag;
const { strings: rightStrings, counts: rightCounts } = rightBag;
const resultStrings = [];
const resultCounts = [];
let leftI = 0;
let rightI = 0;
const leftLen = leftStrings.length;
const rightLen = rightStrings.length;
while (leftI < leftLen && rightI < rightLen) {
const leftStr = leftStrings[leftI];
const rightStr = rightStrings[rightI];
if (leftStr < rightStr) {
resultStrings.push(leftStr);
resultCounts.push(leftCounts[leftI]);
leftI += 1;
} else if (leftStr > rightStr) {
resultStrings.push(rightStr);
resultCounts.push(rightCounts[rightI]);
rightI += 1;
} else {
resultStrings.push(leftStr);
resultCounts.push(Nat(leftCounts[leftI] + rightCounts[rightI]));
leftI += 1;
rightI += 1;
}
}
if (leftI < leftLen) {
resultStrings.push(leftStrings[leftI]);
resultCounts.push(leftCounts[leftI]);
} else if (rightI < rightLen) {
resultStrings.push(rightStrings[rightI]);
resultCounts.push(rightCounts[rightI]);
}
return harden({ strings: resultStrings, counts: resultCounts });
},
doSubtract: (leftBag, rightBag) => {
const { strings: leftStrings, counts: leftCounts } = leftBag;
const { strings: rightStrings, counts: rightCounts } = rightBag;
const resultStrings = [];
const resultCounts = [];
let leftI = 0;
let rightI = 0;
const leftLen = leftStrings.length;
const rightLen = rightStrings.length;
while (leftI < leftLen && rightI < rightLen) {
const leftStr = leftStrings[leftI];
const rightStr = rightStrings[rightI];
assert(
leftStr <= rightStr,
details`element of right not present in left ${rightStr}`,
);
if (leftStr < rightStr) {
// an element of left not in right. Keep.
resultStrings.push(leftStr);
resultCounts.push(leftCounts[leftI]);
leftI += 1;
} else {
// element in both.
const leftCount = leftCounts[leftI];
const rightCount = rightCounts[rightI];
assert(
leftCount >= rightCount,
details`more of element on right than left ${rightStr},${rightCount}`,
);
const resultCount = Nat(leftCount - rightCount);
if (resultCount >= 1) {
resultStrings.push(leftStr);
resultCounts.push(resultCount);
}
leftI += 1;
rightI += 1;
}
}
assert(
rightI >= rightLen,
details`some of the elements in right (${rightStrings}) were not present in left (${leftStrings})`,
);
if (leftI < leftLen) {
resultStrings.push(leftStrings[leftI]);
resultCounts.push(leftCounts[leftI]);
}
return harden({ strings: resultStrings, counts: resultCounts });
},
});

harden(strBagMathHelpers);
export default strBagMathHelpers;
109 changes: 85 additions & 24 deletions packages/ERTP/src/mathHelpers/strSetMathHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import { assert, details } from '@agoric/assert';

const identity = harden([]);

const checkForDupes = list => {
const set = new Set(list);
assert(set.size === list.length, details`value has duplicates: ${list}`);
const assertUniqueSorted = list => {
const len = list.length;
for (let i = 1; i < len; i += 1) {
const leftStr = list[i - 1];
const rightStr = list[i];
assert(leftStr !== rightStr, details`value has duplicates: ${list}`);
assert(leftStr < rightStr, details`value not sorted ${list}`);
}
};

/**
Expand All @@ -25,42 +30,98 @@ const strSetMathHelpers = harden({
doCoerce: list => {
assert(passStyleOf(list) === 'copyArray', 'value must be an array');
list.forEach(elem => assert.typeof(elem, 'string'));
checkForDupes(list);
return list;
assertUniqueSorted(list);
return harden(list);
},
doGetEmpty: _ => identity,
doIsEmpty: list => passStyleOf(list) === 'copyArray' && list.length === 0,
doIsGTE: (left, right) => {
const leftSet = new Set(left);
const leftHas = elem => leftSet.has(elem);
return right.every(leftHas);
let leftI = 0;
let rightI = 0;
const leftLen = left.length;
const rightLen = right.length;
while (leftI < leftLen && rightI < rightLen) {
const leftStr = left[leftI];
const rightStr = right[rightI];
if (leftStr < rightStr) {
// an element of left not in right. Fine
leftI += 1;
} else if (leftStr > rightStr) {
// an element of right not in left.
return false;
} else {
leftI += 1;
rightI += 1;
}
}
// Are there no elements of right remaining?
return rightI >= rightLen;
},
doIsEqual: (left, right) => {
const leftSet = new Set(left);
const leftHas = elem => leftSet.has(elem);
return left.length === right.length && right.every(leftHas);
if (left.length !== right.length) {
return false;
}
return left.every((leftStr, i) => leftStr === right[i]);
},
doAdd: (left, right) => {
const union = new Set(left);
const addToUnion = elem => {
const result = [];
let leftI = 0;
let rightI = 0;
const leftLen = left.length;
const rightLen = right.length;
while (leftI < leftLen && rightI < rightLen) {
const leftStr = left[leftI];
const rightStr = right[rightI];
assert(
!union.has(elem),
details`left and right have same element ${elem}`,
leftStr !== rightStr,
details`left and right have same element ${leftStr}`,
);
union.add(elem);
};
right.forEach(addToUnion);
return harden(Array.from(union));
if (leftStr < rightStr) {
result.push(leftStr);
leftI += 1;
} else {
result.push(rightStr);
rightI += 1;
}
}
if (leftI < leftLen) {
result.push(left[leftI]);
} else if (rightI < rightLen) {
result.push(right[rightI]);
}
Comment on lines +87 to +91
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these leftover processing loops should be while loops. Also the two in doSubtract below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. not sure. need closer examination than I'm about to give it.

return harden(result);
},
doSubtract: (left, right) => {
const leftSet = new Set(left);
const remove = elem => leftSet.delete(elem);
const allRemovedCorrectly = right.every(remove);
const result = [];
let leftI = 0;
let rightI = 0;
const leftLen = left.length;
const rightLen = right.length;
while (leftI < leftLen && rightI < rightLen) {
const leftStr = left[leftI];
const rightStr = right[rightI];
assert(
leftStr <= rightStr,
details`element of right not present in left ${rightStr}`,
);
if (leftStr < rightStr) {
// an element of left not in right. Keep.
result.push(leftStr);
leftI += 1;
} else {
// element in both. Skip.
leftI += 1;
rightI += 1;
}
}
assert(
allRemovedCorrectly,
rightI >= rightLen,
details`some of the elements in right (${right}) were not present in left (${left})`,
);
return harden(Array.from(leftSet));
if (leftI < leftLen) {
result.push(left[leftI]);
}
return harden(result);
},
});

Expand Down
Loading