@agoric/zoe
Version:
Zoe: the Smart Contract Framework for Offer Enforcement
272 lines (245 loc) • 8.6 kB
JavaScript
import { Fail, assert } from '@endo/errors';
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';
import { AmountMath } from '@agoric/ertp';
import { makeNotifier } from '@agoric/notifier';
/**
* @import {EOnly} from '@endo/eventual-send';
* @import {MutableQuote, PriceAuthority, PriceQuote, PriceDescription,} from '@agoric/zoe/tools/types.js';
*/
/**
* @param {Brand<'set'>} quoteBrand
* @param {Amount<'nat'>} amountIn
* @param {Amount<'nat'>} amountOut
* @param {import('@agoric/time').TimerService} timer
* @param {import('@agoric/time').TimestampRecord} timestamp
* @param {ERef<Mint<'set', PriceDescription>>} quoteMint
* @returns {Promise<PriceQuote>}
*/
export const mintQuote = async (
quoteBrand,
amountIn,
amountOut,
timer,
timestamp,
quoteMint,
) => {
const quoteAmount = {
brand: quoteBrand,
value: [{ amountIn, amountOut, timer, timestamp }],
};
const quotePayment = await E(quoteMint).mintPayment({
brand: quoteBrand,
value: [quoteAmount],
});
return harden({ quoteAmount, quotePayment });
};
/**
* @param {object} opts
* @param {ERef<Mint<'set', PriceDescription>>} opts.quoteMint
* @param {ERef<PriceAuthority>} opts.sourcePriceAuthority
* @param {Brand<'nat'>} opts.sourceBrandIn
* @param {Brand<'nat'>} opts.sourceBrandOut
* @param {Brand<'nat'>} [opts.actualBrandIn]
* @param {Brand<'nat'>} [opts.actualBrandOut]
* @param {(amountIn: Amount<'nat'>) => Amount<'nat'>} [opts.makeSourceAmountIn]
* @param {(amountOut: Amount<'nat'>) => Amount<'nat'>} [opts.makeSourceAmountOut]
* @param {(sourceAmountIn: Amount<'nat'>) => Amount<'nat'>} [opts.transformSourceAmountIn]
* @param {(sourceAmountOut: Amount<'nat'>) => Amount<'nat'>} [opts.transformSourceAmountOut]
*/
export const makePriceAuthorityTransform = async ({
quoteMint,
sourcePriceAuthority,
sourceBrandIn,
sourceBrandOut,
actualBrandIn = sourceBrandIn,
actualBrandOut = sourceBrandOut,
makeSourceAmountIn = x => x,
makeSourceAmountOut = x => x,
transformSourceAmountIn = x => x,
transformSourceAmountOut = x => x,
}) => {
const quoteIssuer = E(quoteMint).getIssuer();
const quoteBrand = await E(quoteIssuer).getBrand();
/**
* Ensure that the brandIn/brandOut pair is supported.
*
* @param {Brand} brandIn
* @param {Brand} brandOut
*/
const assertBrands = (brandIn, brandOut) => {
brandIn === actualBrandIn ||
Fail`Desired brandIn ${brandIn} must match ${actualBrandIn}`;
brandOut === actualBrandOut ||
Fail`Desired brandOut ${brandOut} must match ${actualBrandOut}`;
};
/**
* @param {PriceQuote} sourceQuote
* @returns {Promise<PriceQuote>}
*/
const scaleQuote = async sourceQuote => {
const { quotePayment: sourceQuotePayment } = sourceQuote;
const sourceQuoteIssuer = E(sourcePriceAuthority).getQuoteIssuer(
sourceBrandIn,
sourceBrandOut,
);
const { value: sourceQuoteValue } =
await E(sourceQuoteIssuer).getAmountOf(sourceQuotePayment);
sourceQuoteValue.length === 1 ||
Fail`sourceQuoteValue.length ${sourceQuoteValue.length} is not 1`;
const {
amountIn: sourceAmountIn,
amountOut: sourceAmountOut,
timer,
timestamp,
} = sourceQuoteValue[0];
const amountIn = transformSourceAmountIn(sourceAmountIn);
const amountOut = transformSourceAmountOut(sourceAmountOut);
return mintQuote(
quoteBrand,
amountIn,
amountOut,
timer,
timestamp,
quoteMint,
);
};
/**
* Create a quoteWhen* function.
*
* @param {string} sourceMethod
*/
const makeQuoteWhenOut = sourceMethod => {
/**
* Return a quote when sourceMethod fires.
*
* @param {Amount<'nat'>} amountIn the input value to the calcAmountTrigger
* @param {Amount<'nat'>} amountOutLimit the value to compare with the output
* of calcAmountTrigger
*/
const quoteWhenOut = async (amountIn, amountOutLimit) => {
AmountMath.coerce(actualBrandIn, amountIn);
AmountMath.coerce(actualBrandOut, amountOutLimit);
const sourceQuote = await E(sourcePriceAuthority)[sourceMethod](
makeSourceAmountIn(amountIn),
makeSourceAmountOut(amountOutLimit),
);
return scaleQuote(sourceQuote);
};
return quoteWhenOut;
};
/**
* Create a mutableQuoteWhen* function.
*
* @param {string} sourceMethod
*/
const makeMutableQuote = sourceMethod => {
/**
* @param {Amount<'nat'>} amountIn
* @param {Amount<'nat'>} amountOutLimit
*/
const mutableQuoteWhenOut = (amountIn, amountOutLimit) => {
AmountMath.coerce(actualBrandIn, amountIn);
AmountMath.coerce(actualBrandOut, amountOutLimit);
/** @type {ERef<MutableQuote>} */
const sourceMutableQuote = E(sourcePriceAuthority)[sourceMethod](
makeSourceAmountIn(amountIn),
makeSourceAmountOut(amountOutLimit),
);
/** @type {EOnly<MutableQuote>} */
const mutableQuote = Far('MutableQuote', {
cancel: e => E(sourceMutableQuote).cancel(e),
updateLevel: (newAmountIn, newAmountOutLimit) => {
AmountMath.coerce(actualBrandIn, newAmountIn);
AmountMath.coerce(actualBrandOut, newAmountOutLimit);
return E(sourceMutableQuote).updateLevel(
makeSourceAmountIn(newAmountIn),
makeSourceAmountOut(newAmountOutLimit),
);
},
getPromise: () => E(sourceMutableQuote).getPromise().then(scaleQuote),
});
return mutableQuote;
};
return mutableQuoteWhenOut;
};
/** @type {PriceAuthority} */
const priceAuthority = Far('ScaledPriceAuthority', {
getQuoteIssuer(brandIn, brandOut) {
assertBrands(brandIn, brandOut);
return quoteIssuer;
},
getTimerService(brandIn, brandOut) {
assertBrands(brandIn, brandOut);
return E(sourcePriceAuthority).getTimerService(
sourceBrandIn,
sourceBrandOut,
);
},
makeQuoteNotifier(amountIn, brandOut) {
AmountMath.coerce(actualBrandIn, amountIn);
assertBrands(amountIn.brand, brandOut);
const notifier = E(sourcePriceAuthority).makeQuoteNotifier(
makeSourceAmountIn(amountIn),
sourceBrandOut,
);
// Wrap our underlying notifier with scaled quotes.
const scaledBaseNotifier = harden({
async getUpdateSince(updateCount = undefined) {
// We use the same updateCount as our underlying notifier.
const record = await E(notifier).getUpdateSince(updateCount);
const quote = await scaleQuote(record.value);
return harden({
value: quote,
updateCount: record.updateCount,
});
},
});
/** @type {Notifier<PriceQuote>} */
const scaledNotifier = Far('QuoteNotifier', {
...makeNotifier(scaledBaseNotifier),
// TODO stop exposing baseNotifier methods directly.
...scaledBaseNotifier,
});
return scaledNotifier;
},
async quoteGiven(amountIn, brandOut) {
AmountMath.coerce(actualBrandIn, amountIn);
assertBrands(amountIn.brand, brandOut);
const sourceQuote = await E(sourcePriceAuthority).quoteGiven(
makeSourceAmountIn(amountIn),
sourceBrandOut,
);
return scaleQuote(sourceQuote);
},
async quoteWanted(brandIn, amountOut) {
AmountMath.coerce(actualBrandOut, amountOut);
assertBrands(brandIn, amountOut.brand);
const sourceQuote = await E(sourcePriceAuthority).quoteWanted(
sourceBrandIn,
makeSourceAmountOut(amountOut),
);
return scaleQuote(sourceQuote);
},
async quoteAtTime(deadline, amountIn, brandOut) {
assert.typeof(deadline, 'bigint');
AmountMath.coerce(actualBrandIn, amountIn);
assertBrands(amountIn.brand, brandOut);
const sourceQuote = await E(sourcePriceAuthority).quoteAtTime(
deadline,
makeSourceAmountIn(amountIn),
sourceBrandOut,
);
return scaleQuote(sourceQuote);
},
quoteWhenLT: makeQuoteWhenOut('quoteWhenLT'),
quoteWhenLTE: makeQuoteWhenOut('quoteWhenLTE'),
quoteWhenGTE: makeQuoteWhenOut('quoteWhenGTE'),
quoteWhenGT: makeQuoteWhenOut('quoteWhenGT'),
mutableQuoteWhenLT: makeMutableQuote('mutableQuoteWhenLT'),
mutableQuoteWhenLTE: makeMutableQuote('mutableQuoteWhenLTE'),
mutableQuoteWhenGT: makeMutableQuote('mutableQuoteWhenGT'),
mutableQuoteWhenGTE: makeMutableQuote('mutableQuoteWhenGTE'),
});
return priceAuthority;
};