From af7658576f00b6ebaae3bd91aebc6d9fc983fa71 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 6 Nov 2020 08:34:05 -0600 Subject: [PATCH 1/5] feat: simple volatile priceAuthority --- .../lib/ag-solo/vats/bootstrap.js | 56 +++++++++++++++++-- .../lib/ag-solo/vats/vat-priceAuthority.js | 17 +----- packages/zoe/tools/fakePriceAuthority.js | 32 ++++++----- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js index c76d4010b6a..bbc91bd8f20 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]>} tradesGivenCentral */ /** @type {Map} */ const issuerNameToRecord = new Map( harden([ [ - 'Testnet.$USD', + CENTRAL_ISSUER_NAME, { issuerArgs: [undefined, { decimalPlaces: 3 }], mintValue: 20000, pursePetname: 'Local currency', + tradesGivenCentral: [[1, 1]], }, ], [ @@ -79,6 +84,7 @@ export function buildRootObject(vatPowers, vatParameters) { issuerArgs: [undefined, { decimalPlaces: 6 }], mintValue: 7 * 10 ** 6, pursePetname: 'Oracle fee', + tradesGivenCentral: [[1000, 3000000], [1000, 2500000], [1000, 2750000]], }, ], [ @@ -86,6 +92,7 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Fun budget', + tradesGivenCentral: [[10, 1], [13, 1], [12, 1], [18, 1], [15, 1]], }, ], [ @@ -93,11 +100,16 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Nest egg', + tradesGivenCentral: [[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 +119,44 @@ 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. + if (issuer === centralIssuer) { + return; + } + console.error(`Creating ${issuerNames[i]}-${issuerNames[centralIssuerIndex]}`); + const { tradesGivenCentral } = issuerNameToRecord.get(issuerNames[i]); + const tradesGivenThis = tradesGivenCentral.map(([a, b]) => [b, a]); + return Promise.all([ + makeFakePriceAuthority(centralIssuer, issuer, tradesGivenCentral), + makeFakePriceAuthority(issuer, centralIssuer, tradesGivenThis), + ]); + })); + + for (const issuerpetname of issuerNameToRecord) + 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..696a2232f8d 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,26 @@ 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 +88,16 @@ 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 +119,9 @@ 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); } From dceafd6073b8ba6d43acbefafec68797eb365729 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 6 Nov 2020 08:40:38 -0600 Subject: [PATCH 2/5] fix: allow priceRegistry to force-override an entry --- .../lib/ag-solo/vats/bootstrap.js | 66 ++++++++++++------- packages/zoe/tools/priceAuthorityRegistry.js | 10 ++- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js index bbc91bd8f20..4c91e143b5f 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js @@ -84,7 +84,11 @@ export function buildRootObject(vatPowers, vatParameters) { issuerArgs: [undefined, { decimalPlaces: 6 }], mintValue: 7 * 10 ** 6, pursePetname: 'Oracle fee', - tradesGivenCentral: [[1000, 3000000], [1000, 2500000], [1000, 2750000]], + tradesGivenCentral: [ + [1000, 3000000], + [1000, 2500000], + [1000, 2750000], + ], }, ], [ @@ -92,7 +96,13 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Fun budget', - tradesGivenCentral: [[10, 1], [13, 1], [12, 1], [18, 1], [15, 1]], + tradesGivenCentral: [ + [10, 1], + [13, 1], + [12, 1], + [18, 1], + [15, 1], + ], }, ], [ @@ -100,13 +110,19 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Nest egg', - tradesGivenCentral: [[2135, 50], [2172, 50], [2124, 50]], + tradesGivenCentral: [ + [2135, 50], + [2172, 50], + [2124, 50], + ], }, ], ]), ); const issuerNames = [...issuerNameToRecord.keys()]; - const centralIssuerIndex = issuerNames.findIndex(issuerName => issuerName === CENTRAL_ISSUER_NAME); + const centralIssuerIndex = issuerNames.findIndex( + issuerName => issuerName === CENTRAL_ISSUER_NAME, + ); if (centralIssuerIndex < 0) { throw Error(`Cannot find issuer ${CENTRAL_ISSUER_NAME}`); } @@ -120,10 +136,10 @@ export function buildRootObject(vatPowers, vatParameters) { ); const centralIssuer = issuers[centralIssuerIndex]; - + /** - * @param {ERef} issuerIn - * @param {ERef} issuerOut + * @param {ERef} issuerIn + * @param {ERef} issuerOut * @param {Array<[number, number]>} tradeList */ const makeFakePriceAuthority = async (issuerIn, issuerOut, tradeList) => { @@ -138,25 +154,25 @@ export function buildRootObject(vatPowers, vatParameters) { quoteInterval: QUOTE_INTERVAL, }), ]); - return E(priceAuthorityAdmin).registerPriceAuthority(pa, brandIn, brandOut); + return E(priceAuthorityAdmin).registerPriceAuthority( + pa, + brandIn, + brandOut, + ); }; - await Promise.all(issuers.map(async (issuer, i) => { - // Create priceAuthority pairs for centralIssuerIndex based on the - // FakePriceAuthority. - if (issuer === centralIssuer) { - return; - } - console.error(`Creating ${issuerNames[i]}-${issuerNames[centralIssuerIndex]}`); - const { tradesGivenCentral } = issuerNameToRecord.get(issuerNames[i]); - const tradesGivenThis = tradesGivenCentral.map(([a, b]) => [b, a]); - return Promise.all([ - makeFakePriceAuthority(centralIssuer, issuer, tradesGivenCentral), - makeFakePriceAuthority(issuer, centralIssuer, tradesGivenThis), - ]); - })); - - for (const issuerpetname of issuerNameToRecord) - + await Promise.all( + issuers.map(async (issuer, i) => { + // Create priceAuthority pairs for centralIssuerIndex based on the + // FakePriceAuthority. + console.error(`Creating ${issuerNames[i]}-${CENTRAL_ISSUER_NAME}`); + const { tradesGivenCentral } = issuerNameToRecord.get(issuerNames[i]); + const tradesGivenThis = tradesGivenCentral.map(([a, b]) => [b, a]); + return Promise.all([ + makeFakePriceAuthority(centralIssuer, issuer, tradesGivenCentral), + makeFakePriceAuthority(issuer, centralIssuer, tradesGivenThis), + ]); + }), + ); return harden({ async createUserBundle(_nickname, powerFlags = []) { diff --git a/packages/zoe/tools/priceAuthorityRegistry.js b/packages/zoe/tools/priceAuthorityRegistry.js index 4cbffec339c..113f3a019eb 100644 --- a/packages/zoe/tools/priceAuthorityRegistry.js +++ b/packages/zoe/tools/priceAuthorityRegistry.js @@ -13,7 +13,7 @@ import '../exported'; /** * @typedef {Object} PriceAuthorityRegistryAdmin - * @property {(pa: ERef, brandIn: Brand, brandOut: Brand) + * @property {(pa: ERef, brandIn: Brand, brandOut: Brand, force?: boolean) * => Deleter} registerPriceAuthority Add a unique price authority for a given * pair */ @@ -109,7 +109,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 +126,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() { From a7ec3d1f775c764fe09cf45fb67130f24d3a35cf Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 6 Nov 2020 10:34:53 -0600 Subject: [PATCH 3/5] fix: don't create duplicate central price authority --- .../cosmic-swingset/lib/ag-solo/vats/bootstrap.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js index 4c91e143b5f..99d96d709d1 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js @@ -164,12 +164,18 @@ export function buildRootObject(vatPowers, vatParameters) { issuers.map(async (issuer, i) => { // Create priceAuthority pairs for centralIssuerIndex based on the // FakePriceAuthority. - console.error(`Creating ${issuerNames[i]}-${CENTRAL_ISSUER_NAME}`); + console.debug(`Creating ${issuerNames[i]}-${CENTRAL_ISSUER_NAME}`); const { tradesGivenCentral } = issuerNameToRecord.get(issuerNames[i]); - const tradesGivenThis = tradesGivenCentral.map(([a, b]) => [b, a]); - return Promise.all([ + const tradesGivenOther = + centralIssuer !== issuer && + tradesGivenCentral.map(([valueCentral, valueOther]) => [ + valueOther, + valueCentral, + ]); + await Promise.all([ makeFakePriceAuthority(centralIssuer, issuer, tradesGivenCentral), - makeFakePriceAuthority(issuer, centralIssuer, tradesGivenThis), + tradesGivenOther && + makeFakePriceAuthority(issuer, centralIssuer, tradesGivenOther), ]); }), ); From 4dea2f14531990d9b6fd173165a3e722408715cb Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 6 Nov 2020 12:19:45 -0600 Subject: [PATCH 4/5] chore: fix the lint --- packages/zoe/tools/fakePriceAuthority.js | 13 ++++++++++--- packages/zoe/tools/priceAuthorityRegistry.js | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/zoe/tools/fakePriceAuthority.js b/packages/zoe/tools/fakePriceAuthority.js index 696a2232f8d..0d2c49832b3 100644 --- a/packages/zoe/tools/fakePriceAuthority.js +++ b/packages/zoe/tools/fakePriceAuthority.js @@ -41,7 +41,10 @@ export async function makeFakePriceAuthority(options) { quoteMint = makeIssuerKit('quote', MathKind.SET).mint, } = options; - assert(tradeList || priceList, details`One of priceList or tradeList must be specified`); + assert( + tradeList || priceList, + details`One of priceList or tradeList must be specified`, + ); const unitValueIn = mathIn.getValue(unitAmountIn); @@ -91,7 +94,8 @@ export async function makeFakePriceAuthority(options) { mathIn.coerce(amountIn); const [tradeValueIn, tradeValueOut] = currentTrade(); const valueOut = natSafeMath.floorDivide( - natSafeMath.multiply(amountIn.value, tradeValueOut), tradeValueIn + natSafeMath.multiply(amountIn.value, tradeValueOut), + tradeValueIn, ); const quoteAmount = quoteMath.make( harden([ @@ -121,7 +125,10 @@ export async function makeFakePriceAuthority(options) { assertBrands(brandIn, amountOut.brand); const valueOut = mathOut.getValue(amountOut); const [tradeValueIn, tradeValueOut] = currentTrade(); - const valueIn = natSafeMath.ceilDivide(natSafeMath.multiply(valueOut, tradeValueIn), tradeValueOut); + 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 113f3a019eb..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, force?: boolean) + * @property {(pa: ERef, brandIn: Brand, brandOut: Brand, force: + * boolean | undefined) * => Deleter} registerPriceAuthority Add a unique price authority for a given * pair */ From d32f416374eee889d11c78cb049697a613830718 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 6 Nov 2020 12:37:59 -0600 Subject: [PATCH 5/5] refactor: rename fake trades --- .../lib/ag-solo/vats/bootstrap.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js index 99d96d709d1..e9a20ae30dd 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js @@ -64,7 +64,7 @@ export function buildRootObject(vatPowers, vatParameters) { * @property {Array} [issuerArgs] * @property {string} pursePetname * @property {number} mintValue - * @property {Array<[number, number]>} tradesGivenCentral + * @property {Array<[number, number]>} [fakeTradesGivenCentral] */ /** @type {Map} */ const issuerNameToRecord = new Map( @@ -75,7 +75,7 @@ export function buildRootObject(vatPowers, vatParameters) { issuerArgs: [undefined, { decimalPlaces: 3 }], mintValue: 20000, pursePetname: 'Local currency', - tradesGivenCentral: [[1, 1]], + fakeTradesGivenCentral: [[1, 1]], }, ], [ @@ -84,7 +84,7 @@ export function buildRootObject(vatPowers, vatParameters) { issuerArgs: [undefined, { decimalPlaces: 6 }], mintValue: 7 * 10 ** 6, pursePetname: 'Oracle fee', - tradesGivenCentral: [ + fakeTradesGivenCentral: [ [1000, 3000000], [1000, 2500000], [1000, 2750000], @@ -96,7 +96,7 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Fun budget', - tradesGivenCentral: [ + fakeTradesGivenCentral: [ [10, 1], [13, 1], [12, 1], @@ -110,7 +110,7 @@ export function buildRootObject(vatPowers, vatParameters) { { mintValue: 1900, pursePetname: 'Nest egg', - tradesGivenCentral: [ + fakeTradesGivenCentral: [ [2135, 50], [2172, 50], [2124, 50], @@ -165,17 +165,22 @@ export function buildRootObject(vatPowers, vatParameters) { // Create priceAuthority pairs for centralIssuerIndex based on the // FakePriceAuthority. console.debug(`Creating ${issuerNames[i]}-${CENTRAL_ISSUER_NAME}`); - const { tradesGivenCentral } = issuerNameToRecord.get(issuerNames[i]); - const tradesGivenOther = + const { fakeTradesGivenCentral } = issuerNameToRecord.get( + issuerNames[i], + ); + if (!fakeTradesGivenCentral) { + return; + } + const fakeTradesGivenOther = centralIssuer !== issuer && - tradesGivenCentral.map(([valueCentral, valueOther]) => [ + fakeTradesGivenCentral.map(([valueCentral, valueOther]) => [ valueOther, valueCentral, ]); await Promise.all([ - makeFakePriceAuthority(centralIssuer, issuer, tradesGivenCentral), - tradesGivenOther && - makeFakePriceAuthority(issuer, centralIssuer, tradesGivenOther), + makeFakePriceAuthority(centralIssuer, issuer, fakeTradesGivenCentral), + fakeTradesGivenOther && + makeFakePriceAuthority(issuer, centralIssuer, fakeTradesGivenOther), ]); }), );