@agoric/zoe
Version:
Zoe: the Smart Contract Framework for Offer Enforcement
94 lines (85 loc) • 3.06 kB
JavaScript
import { Fail, q } from '@endo/errors';
import { AmountMath } from '@agoric/ertp';
/**
* @callback Operation
*
* An operation such as add or subtract.
*
* @param {Amount | undefined} amount
* @param {Amount | undefined} diff
* @param {Keyword} keyword
* @returns {Amount}
*/
/**
* Add amounts to an allocation, or subtract amounts from an
* allocation, by keyword. The operationFn performs the actual adding
* or subtracting.
*
* @param {Allocation} allocation
* @param {AmountKeywordRecord} amountKeywordRecord
* @param {Operation} operationFn
* @returns {AmountKeywordRecord}
*/
const doOperation = (allocation, amountKeywordRecord, operationFn) => {
const allKeywords = Object.keys({ ...allocation, ...amountKeywordRecord });
const entries = allKeywords
.map(keyword => {
const result = operationFn(
allocation[keyword],
amountKeywordRecord[keyword],
keyword,
);
return [keyword, result];
})
.filter(([_keyword, result]) => result !== undefined);
return harden(Object.fromEntries(entries));
};
/** @type {Operation} */
const add = (amount, amountToAdd, _keyword) => {
// Add if both are defined.
if (amount && amountToAdd) {
return AmountMath.add(amount, amountToAdd);
}
// If one is undefined (at least one will be defined), return the
// other, defined amount
return /** @type {Amount} */ (amount || amountToAdd);
};
/** @type {Operation} */
const subtract = (amount, amountToSubtract, keyword) => {
// if amountToSubtract is undefined OR is defined, but empty
if (amountToSubtract === undefined || AmountMath.isEmpty(amountToSubtract)) {
// Subtracting undefined is equivalent to subtracting empty, so in
// both cases we can return the original amount. If the original
// amount is undefined, it is filtered out in doOperation.
return /** @type {Amount} */ (amount);
} else {
if (amount === undefined) {
// TypeScript confused about `||` control flow so use `if` instead
// https://github.com/microsoft/TypeScript/issues/50739
throw Fail`The amount could not be subtracted from the allocation because the allocation did not have an amount under the keyword ${q(
keyword,
)}.`;
}
AmountMath.isGTE(amount, amountToSubtract) ||
Fail`The amount to be subtracted ${amountToSubtract} was greater than the allocation's amount ${amount} for the keyword ${q(
keyword,
)}`;
return AmountMath.subtract(amount, amountToSubtract);
}
};
/**
* @param {Allocation} allocation
* @param {AmountKeywordRecord} amountKeywordRecord
* @returns {AmountKeywordRecord}
*/
export const addToAllocation = (allocation, amountKeywordRecord) => {
return doOperation(allocation, amountKeywordRecord, add);
};
/**
* @param {Allocation} allocation
* @param {AmountKeywordRecord} amountKeywordRecord
* @returns {AmountKeywordRecord}
*/
export const subtractFromAllocation = (allocation, amountKeywordRecord) => {
return doOperation(allocation, amountKeywordRecord, subtract);
};