diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js index c76d4010b6a..e9a20ae30dd 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js @@ -1,3 +1,4 @@ +// @ts-nocheck import { allComparable } from '@agoric/same-structure'; import { makeLoopbackProtocolHandler, @@ -12,6 +13,8 @@ import { GCI } from './gci'; import { makeBridgeManager } from './bridge'; const NUM_IBC_PORTS = 3; +const CENTRAL_ISSUER_NAME = 'Testnet.$USD'; +const QUOTE_INTERVAL = 30; console.debug(`loading bootstrap.js`); @@ -60,17 +63,19 @@ export function buildRootObject(vatPowers, vatParameters) { * @typedef {Object} IssuerRecord * @property {Array} [issuerArgs] * @property {string} pursePetname - * @property {any} mintValue + * @property {number} mintValue + * @property {Array<[number, number]>} [fakeTradesGivenCentral] */ /** @type {Map} */ const issuerNameToRecord = new Map( harden([ [ - 'Testnet.$USD', + CENTRAL_ISSUER_NAME, { issuerArgs: [undefined, { decimalPlaces: 3 }], mintValue: 20000, pursePetname: 'Local currency', + fakeTradesGivenCentral: [[1, 1]], }, ], [ @@ -79,6 +84,11 @@ export function buildRootObject(vatPowers, vatParameters) { issuerArgs: [undefined, { decimalPlaces: 6 }], mintValue: 7 * 10 ** 6, pursePetname: 'Oracle fee', + fakeTradesGivenCentral: [ + [1000, 3000000], + [1000, 2500000], + [1000, 2750000], + ], }, ], [ @@ -86,6 +96,13 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Fun budget', + fakeTradesGivenCentral: [ + [10, 1], + [13, 1], + [12, 1], + [18, 1], + [15, 1], + ], }, ], [ @@ -93,11 +110,22 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Nest egg', + fakeTradesGivenCentral: [ + [2135, 50], + [2172, 50], + [2124, 50], + ], }, ], ]), ); const issuerNames = [...issuerNameToRecord.keys()]; + const centralIssuerIndex = issuerNames.findIndex( + issuerName => issuerName === CENTRAL_ISSUER_NAME, + ); + if (centralIssuerIndex < 0) { + throw Error(`Cannot find issuer ${CENTRAL_ISSUER_NAME}`); + } const issuers = await Promise.all( issuerNames.map(issuerName => E(vats.mints).makeMintAndIssuer( @@ -107,8 +135,55 @@ export function buildRootObject(vatPowers, vatParameters) { ), ); - // TODO: Create priceAuthority pairs for moola-simolean based on the - // FakePriceAuthority. + const centralIssuer = issuers[centralIssuerIndex]; + + /** + * @param {ERef} issuerIn + * @param {ERef} issuerOut + * @param {Array<[number, number]>} tradeList + */ + const makeFakePriceAuthority = async (issuerIn, issuerOut, tradeList) => { + const [brandIn, brandOut, pa] = await Promise.all([ + E(issuerIn).getBrand(), + E(issuerOut).getBrand(), + E(vats.priceAuthority).makeFakePriceAuthority({ + issuerIn, + issuerOut, + tradeList, + timer: chainTimerService, + quoteInterval: QUOTE_INTERVAL, + }), + ]); + return E(priceAuthorityAdmin).registerPriceAuthority( + pa, + brandIn, + brandOut, + ); + }; + await Promise.all( + issuers.map(async (issuer, i) => { + // Create priceAuthority pairs for centralIssuerIndex based on the + // FakePriceAuthority. + console.debug(`Creating ${issuerNames[i]}-${CENTRAL_ISSUER_NAME}`); + const { fakeTradesGivenCentral } = issuerNameToRecord.get( + issuerNames[i], + ); + if (!fakeTradesGivenCentral) { + return; + } + const fakeTradesGivenOther = + centralIssuer !== issuer && + fakeTradesGivenCentral.map(([valueCentral, valueOther]) => [ + valueOther, + valueCentral, + ]); + await Promise.all([ + makeFakePriceAuthority(centralIssuer, issuer, fakeTradesGivenCentral), + fakeTradesGivenOther && + makeFakePriceAuthority(issuer, centralIssuer, fakeTradesGivenOther), + ]); + }), + ); return harden({ async createUserBundle(_nickname, powerFlags = []) { diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/vat-priceAuthority.js b/packages/cosmic-swingset/lib/ag-solo/vats/vat-priceAuthority.js index 1e8198c1ac8..a4e1a3d7ba6 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/vat-priceAuthority.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/vat-priceAuthority.js @@ -5,24 +5,13 @@ import { makeLocalAmountMath } from '@agoric/ertp'; export function buildRootObject(_vatPowers) { return harden({ makePriceAuthority: makePriceAuthorityRegistry, - async makeFakePriceAuthority( - issuerIn, - issuerOut, - priceList, - timer, - quoteInterval = undefined, - ) { + async makeFakePriceAuthority(options) { + const { issuerIn, issuerOut } = options; const [mathIn, mathOut] = await Promise.all([ makeLocalAmountMath(issuerIn), makeLocalAmountMath(issuerOut), ]); - return makeFakePriceAuthority( - mathIn, - mathOut, - priceList, - timer, - quoteInterval, - ); + return makeFakePriceAuthority({ ...options, mathIn, mathOut }); }, }); } diff --git a/packages/zoe/tools/fakePriceAuthority.js b/packages/zoe/tools/fakePriceAuthority.js index 92f293a2385..0d2c49832b3 100644 --- a/packages/zoe/tools/fakePriceAuthority.js +++ b/packages/zoe/tools/fakePriceAuthority.js @@ -14,7 +14,8 @@ import '../exported'; * @typedef {Object} FakePriceAuthorityOptions * @property {AmountMath} mathIn * @property {AmountMath} mathOut - * @property {Array} priceList + * @property {Array} [priceList] + * @property {Array<[number, number]>} [tradeList] * @property {ERef} timer * @property {RelativeTime} [quoteInterval] * @property {ERef} [quoteMint] @@ -33,20 +34,29 @@ export async function makeFakePriceAuthority(options) { mathIn, mathOut, priceList, + tradeList, timer, unitAmountIn = mathIn.make(1), quoteInterval = 1, quoteMint = makeIssuerKit('quote', MathKind.SET).mint, } = options; + assert( + tradeList || priceList, + details`One of priceList or tradeList must be specified`, + ); + const unitValueIn = mathIn.getValue(unitAmountIn); const comparisonQueue = []; let currentPriceIndex = 0; - function currentPrice() { - return priceList[currentPriceIndex % priceList.length]; + function currentTrade() { + if (tradeList) { + return tradeList[currentPriceIndex % tradeList.length]; + } + return [unitValueIn, priceList[currentPriceIndex % priceList.length]]; } /** @@ -81,16 +91,17 @@ export async function makeFakePriceAuthority(options) { */ function priceInQuote(amountIn, brandOut, quoteTime) { assertBrands(amountIn.brand, brandOut); + mathIn.coerce(amountIn); + const [tradeValueIn, tradeValueOut] = currentTrade(); + const valueOut = natSafeMath.floorDivide( + natSafeMath.multiply(amountIn.value, tradeValueOut), + tradeValueIn, + ); const quoteAmount = quoteMath.make( harden([ { amountIn, - amountOut: mathOut.make( - natSafeMath.floorDivide( - currentPrice() * amountIn.value, - unitValueIn, - ), - ), + amountOut: mathOut.make(valueOut), timer, timestamp: quoteTime, }, @@ -112,10 +123,12 @@ export async function makeFakePriceAuthority(options) { */ function priceOutQuote(brandIn, amountOut, quoteTime) { assertBrands(brandIn, amountOut.brand); - const desiredValue = mathOut.getValue(amountOut); - const price = currentPrice(); - // FIXME: Use natSafeMath.ceilDivide to calculate valueIn. - const valueIn = Math.ceil((desiredValue * unitValueIn) / price); + const valueOut = mathOut.getValue(amountOut); + const [tradeValueIn, tradeValueOut] = currentTrade(); + const valueIn = natSafeMath.ceilDivide( + natSafeMath.multiply(valueOut, tradeValueIn), + tradeValueOut, + ); return priceInQuote(mathIn.make(valueIn), amountOut.brand, quoteTime); } diff --git a/packages/zoe/tools/priceAuthorityRegistry.js b/packages/zoe/tools/priceAuthorityRegistry.js index 4cbffec339c..b4c46fc0715 100644 --- a/packages/zoe/tools/priceAuthorityRegistry.js +++ b/packages/zoe/tools/priceAuthorityRegistry.js @@ -13,7 +13,8 @@ import '../exported'; /** * @typedef {Object} PriceAuthorityRegistryAdmin - * @property {(pa: ERef, brandIn: Brand, brandOut: Brand) + * @property {(pa: ERef, brandIn: Brand, brandOut: Brand, force: + * boolean | undefined) * => Deleter} registerPriceAuthority Add a unique price authority for a given * pair */ @@ -109,7 +110,7 @@ export const makePriceAuthorityRegistry = () => { /** @type {PriceAuthorityRegistryAdmin} */ const adminFacet = { - registerPriceAuthority(pa, brandIn, brandOut) { + registerPriceAuthority(pa, brandIn, brandOut, force = false) { /** @type {Store} */ let priceStore; if (assetToPriceStore.has(brandIn)) { @@ -126,7 +127,11 @@ export const makePriceAuthorityRegistry = () => { }; // Set up the record. - priceStore.init(brandOut, harden(record)); + if (force && priceStore.has(brandOut)) { + priceStore.set(brandOut, harden(record)); + } else { + priceStore.init(brandOut, harden(record)); + } return harden({ delete() {