diff --git a/packages/run-protocol/src/econ-behaviors.js b/packages/run-protocol/src/econ-behaviors.js index 7f8326e0994..1dc8dd781a0 100644 --- a/packages/run-protocol/src/econ-behaviors.js +++ b/packages/run-protocol/src/econ-behaviors.js @@ -122,9 +122,7 @@ export const setupAmm = async ({ governorInstallation, {}, ammGovernorTerms, - { - electorateCreatorFacet: committeeCreator, - }, + { electorateCreatorFacet: committeeCreator }, ); const [creatorFacet, ammPublicFacet, instance] = await Promise.all([ @@ -525,7 +523,7 @@ harden(startRewardDistributor); */ /** - * @typedef {EconomyBootstrapPowers & WellKnownSpaces & PromiseMarket<{ + * @typedef {EconomyBootstrapPowers & PromiseMarket<{ * runStakeBundle: SourceBundle, * client: ClientManager, * lienBridge: StakingAuthority, diff --git a/packages/run-protocol/src/makeTracer.js b/packages/run-protocol/src/makeTracer.js index 32be9a72473..f5d62f92c2d 100644 --- a/packages/run-protocol/src/makeTracer.js +++ b/packages/run-protocol/src/makeTracer.js @@ -17,8 +17,12 @@ const makeTracer = (name, enable = true) => { return infoTick; } default: { - const debugTick = (...args) => { - console.log(key, (debugCount += 1), ...args); + const debugTick = (optLog, ...args) => { + if (optLog.log) { + optLog.log(key, (debugCount += 1), ...args); + } else { + console.info(key, (debugCount += 1), optLog, ...args); + } }; return debugTick; } diff --git a/packages/run-protocol/src/vaultFactory/liquidateMinimum.js b/packages/run-protocol/src/vaultFactory/liquidateMinimum.js index c132c405cb7..9116e419756 100644 --- a/packages/run-protocol/src/vaultFactory/liquidateMinimum.js +++ b/packages/run-protocol/src/vaultFactory/liquidateMinimum.js @@ -53,12 +53,9 @@ const start = async zcf => { // check whether swapIn liquidated assets until getOfferResult() returns. // Of course, we also can't exit the seat until one or the other // liquidation takes place. - const [offerResult, amounts, _deposited] = await Promise.all([ - E(liqSeat).getOfferResult(), - E(liqSeat).getCurrentAllocation(), - deposited, - ]); - trace('offerResult', offerResult, amounts); + const amounts = await deposited; + await E(liqSeat).getOfferResult(); + trace('exact sell result', amounts); // if swapOut failed to make the trade, we'll sell it all const sellAllIfUnsold = async () => { diff --git a/packages/run-protocol/src/vaultFactory/vaultManager.js b/packages/run-protocol/src/vaultFactory/vaultManager.js index 17ed86171c0..193cb5d8e90 100644 --- a/packages/run-protocol/src/vaultFactory/vaultManager.js +++ b/packages/run-protocol/src/vaultFactory/vaultManager.js @@ -178,7 +178,9 @@ const helperBehavior = { const debtPost = AmountMath.add(totalDebt, toMint); const limit = factoryPowers.getGovernedParams().getDebtLimit(); if (AmountMath.isGTE(debtPost, limit)) { - assert.fail(X`Minting would exceed total debt limit ${q(limit)}`); + assert.fail( + X`Minting ${q(toMint)} would exceed total debt limit ${q(limit)}`, + ); } }, diff --git a/packages/run-protocol/test/psm/test-psm.js b/packages/run-protocol/test/psm/test-psm.js index 61e3941fd19..f3adf48b943 100644 --- a/packages/run-protocol/test/psm/test-psm.js +++ b/packages/run-protocol/test/psm/test-psm.js @@ -7,12 +7,7 @@ import '../../src/vaultFactory/types.js'; import path from 'path'; import { E } from '@endo/eventual-send'; -import bundleSource from '@endo/bundle-source'; -import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js'; -import { makeZoeKit } from '@agoric/zoe'; import { AmountMath, makeIssuerKit } from '@agoric/ertp'; -import { resolve as importMetaResolve } from 'import-meta-resolve'; -import { makeLoopback } from '@endo/captp'; import { makeRatio, floorDivideBy, @@ -20,6 +15,7 @@ import { natSafeMath as NatMath, } from '@agoric/zoe/src/contractSupport/index.js'; import { makeTracer } from '../../src/makeTracer.js'; +import { makeBundle, setUpZoeForTest } from '../supports.js'; const pathname = new URL(import.meta.url).pathname; const dirname = path.dirname(pathname); @@ -27,13 +23,6 @@ const dirname = path.dirname(pathname); const psmRoot = `${dirname}/../../src/psm/psm.js`; const trace = makeTracer('TestPSM', false); -const makeBundle = async sourceRoot => { - const url = await importMetaResolve(sourceRoot, import.meta.url); - const contractBundle = await bundleSource(new URL(url).pathname); - console.log(`makeBundle ${sourceRoot}`); - return contractBundle; -}; - const BASIS_POINTS = 10000n; const WantStableFeeBP = 1n; const GiveStableFeeBP = 3n; @@ -88,16 +77,9 @@ test.before(async t => { // makeBundle is slow, so we bundle each contract once and reuse in all tests. const psmBundle = await makeBundle(psmRoot); t.context.bundles = { psmBundle }; - const { makeFar, makeNear: makeRemote } = makeLoopback('zoeTest'); - const { zoeService, feeMintAccess: nonFarFeeMintAccess } = makeZoeKit( - makeFakeVatAdmin(setJig, makeRemote).admin, - ); - /** @type {ERef} */ - const zoe = makeFar(zoeService); - t.context.zoe = zoe; - trace('makeZoe'); - const feeMintAccess = await makeFar(nonFarFeeMintAccess); - t.context.feeMintAccess = feeMintAccess; + const { zoe, feeMintAccess } = setUpZoeForTest(setJig); + t.context.zoe = await zoe; + t.context.feeMintAccess = await feeMintAccess; const runIssuer = await E(zoe).getFeeIssuer(); const runBrand = await E(runIssuer).getBrand(); diff --git a/packages/run-protocol/test/runStake/test-runStake.js b/packages/run-protocol/test/runStake/test-runStake.js index bdadac8efa2..608dcf3542f 100644 --- a/packages/run-protocol/test/runStake/test-runStake.js +++ b/packages/run-protocol/test/runStake/test-runStake.js @@ -25,6 +25,7 @@ import { economyBundles, } from '../../src/importedBundles.js'; import * as Collect from '../../src/collect.js'; +import { setUpZoeForTest } from '../supports.js'; import { KW } from '../../src/runStake/params.js'; // 8 Partial repayment from reward stream - TODO @@ -68,29 +69,13 @@ test.before(async t => { */ const theBundles = t => /** @type { any } */ (t.context).bundles; -export const setUpZoeForTest = async () => { - const { makeFar } = makeLoopback('zoeTest'); - - const { zoeService, feeMintAccess: nonFarFeeMintAccess } = makeZoeKit( - makeFakeVatAdmin(() => {}).admin, - ); - /** @type {ERef} */ - const zoe = makeFar(zoeService); - const feeMintAccess = await makeFar(nonFarFeeMintAccess); - return { - zoe, - feeMintAccess, - }; -}; -harden(setUpZoeForTest); - export const setupBootstrap = async ( bundles, timer = buildManualTimer(console.log), zoe, ) => { if (!zoe) { - ({ zoe } = await setUpZoeForTest()); + zoe = await setUpZoeForTest().zoe; } const space = /** @type {any} */ (makePromiseSpace()); diff --git a/packages/run-protocol/test/supports.js b/packages/run-protocol/test/supports.js index fe8585edda2..786f94249b3 100644 --- a/packages/run-protocol/test/supports.js +++ b/packages/run-protocol/test/supports.js @@ -1,5 +1,20 @@ +// @ts-check +/* global setImmediate */ + import { AmountMath } from '@agoric/ertp'; import { Far } from '@endo/marshal'; +import bundleSource from '@endo/bundle-source'; +import { makeLoopback } from '@endo/captp'; + +import { resolve as importMetaResolve } from 'import-meta-resolve'; +import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js'; +import { makeZoeKit } from '@agoric/zoe'; +import { + makeAgoricNamesAccess, + makePromiseSpace, +} from '@agoric/vats/src/core/utils.js'; +import buildManualTimer from '@agoric/zoe/tools/manualTimer.js'; +import { governanceBundles, economyBundles } from '../src/importedBundles.js'; /** * @@ -8,11 +23,11 @@ import { Far } from '@endo/marshal'; * @param {Amount} initCollateral * @returns {InnerVault & {setDebt: (Amount) => void}} */ -export function makeFakeInnerVault( +export const makeFakeInnerVault = ( vaultId, initDebt, initCollateral = AmountMath.make(initDebt.brand, 100n), -) { +) => { let debt = initDebt; let collateral = initCollateral; const vault = Far('Vault', { @@ -24,6 +39,76 @@ export function makeFakeInnerVault( getIdInManager: () => vaultId, liquidate: () => {}, }); - // @ts-expect-error pretend this is compatible with VaultKit + // @ts-expect-error cast return vault; -} +}; + +/** + * + * @param {string} sourceRoot + * @returns {Promise} + */ +export const makeBundle = async sourceRoot => { + const url = await importMetaResolve(sourceRoot, import.meta.url); + const path = new URL(url).pathname; + const contractBundle = await bundleSource(path); + console.log(`makeBundle ${sourceRoot}`); + return contractBundle; +}; +harden(makeBundle); + +// Some notifier updates aren't propagating sufficiently quickly for +// the tests. This invocation waits for all promises that can fire to +// have all their callbacks run +export const waitForPromisesToSettle = async () => + new Promise(resolve => setImmediate(resolve)); +harden(waitForPromisesToSettle); + +/** + * Returns promises for `zoe` and the `feeMintAccess`. + * + * @param {() => void} setJig + */ +export const setUpZoeForTest = (setJig = () => {}) => { + const { makeFar } = makeLoopback('zoeTest'); + + const { zoeService, feeMintAccess: nonFarFeeMintAccess } = makeZoeKit( + makeFakeVatAdmin(setJig).admin, + ); + /** @type {ERef} */ + const zoe = makeFar(zoeService); + const feeMintAccess = makeFar(nonFarFeeMintAccess); + return { + zoe, + feeMintAccess, + }; +}; +harden(setUpZoeForTest); + +export const setupBootstrap = (t, optTimer = undefined) => { + const space = /** @type {any} */ (makePromiseSpace(t.log)); + const { produce, consume } = /** @type {EconomyBootstrapPowers} */ (space); + + const timer = optTimer || buildManualTimer(t.log); + produce.chainTimerService.resolve(timer); + + const { + zoe, + feeMintAccess, + runKit: { brand: runBrand, issuer: runIssuer }, + } = t.context; + produce.zoe.resolve(zoe); + produce.feeMintAccess.resolve(feeMintAccess); + + const { agoricNames, spaces } = makeAgoricNamesAccess(); + produce.agoricNames.resolve(agoricNames); + + const { brand, issuer } = spaces; + brand.produce.RUN.resolve(runBrand); + issuer.produce.RUN.resolve(runIssuer); + + produce.governanceBundles.resolve(governanceBundles); + produce.centralSupplyBundle.resolve(economyBundles.centralSupply); + + return { produce, consume, ...spaces }; +}; diff --git a/packages/run-protocol/test/vaultFactory/test-bootstrapPayment.js b/packages/run-protocol/test/vaultFactory/test-bootstrapPayment.js index c970317fad8..1f6732312d7 100644 --- a/packages/run-protocol/test/vaultFactory/test-bootstrapPayment.js +++ b/packages/run-protocol/test/vaultFactory/test-bootstrapPayment.js @@ -7,25 +7,14 @@ import '../../src/vaultFactory/types.js'; import path from 'path'; import { E } from '@endo/eventual-send'; -import bundleSource from '@endo/bundle-source'; -import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js'; -import { makeZoeKit } from '@agoric/zoe'; import { AmountMath } from '@agoric/ertp'; -import { resolve as importMetaResolve } from 'import-meta-resolve'; -import { makeLoopback } from '@endo/captp'; +import { makeBundle, setUpZoeForTest } from '../supports.js'; const pathname = new URL(import.meta.url).pathname; const dirname = path.dirname(pathname); const centralSupplyRoot = `${dirname}/../../src/centralSupply.js`; -const makeBundle = async sourceRoot => { - const url = await importMetaResolve(sourceRoot, import.meta.url); - const contractBundle = await bundleSource(new URL(url).pathname); - console.log(`makeBundle ${sourceRoot}`); - return contractBundle; -}; - // makeBundle is slow, so we bundle each contract once and reuse in all tests. const [centralSupplyBundle] = await Promise.all([ makeBundle(centralSupplyRoot), @@ -33,24 +22,11 @@ const [centralSupplyBundle] = await Promise.all([ const installBundle = (zoe, contractBundle) => E(zoe).install(contractBundle); -const setUpZoeForTest = async setJig => { - const { makeFar } = makeLoopback('zoeTest'); - const { zoeService, feeMintAccess: nonFarFeeMintAccess } = makeZoeKit( - makeFakeVatAdmin(setJig).admin, - ); - /** @type {ERef} */ - const zoe = makeFar(zoeService); - const feeMintAccess = await makeFar(nonFarFeeMintAccess); - return { - zoe, - feeMintAccess, - }; -}; - const startContract = async bootstrapPaymentValue => { - const { zoe, feeMintAccess } = await setUpZoeForTest(() => {}); + const { zoe, feeMintAccess: feeMintAccessP } = setUpZoeForTest(); const runIssuer = E(zoe).getFeeIssuer(); const runBrand = await E(runIssuer).getBrand(); + const feeMintAccess = await feeMintAccessP; /** @type {import('@agoric/zoe/src/zoeService/utils').Installation} */ const centralSupplyInstall = await installBundle(zoe, centralSupplyBundle); diff --git a/packages/run-protocol/test/vaultFactory/test-vaultFactory.js b/packages/run-protocol/test/vaultFactory/test-vaultFactory.js index 247ab87dac4..71f01e68692 100644 --- a/packages/run-protocol/test/vaultFactory/test-vaultFactory.js +++ b/packages/run-protocol/test/vaultFactory/test-vaultFactory.js @@ -1,47 +1,52 @@ -// @ts-check -/* global setImmediate */ +// @ts-nocheck import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import '@agoric/zoe/exported.js'; -import { resolve as importMetaResolve } from 'import-meta-resolve'; - import { E } from '@endo/eventual-send'; -import bundleSource from '@endo/bundle-source'; +import { deeplyFulfilled } from '@endo/marshal'; + import { makeIssuerKit, AssetKind, AmountMath } from '@agoric/ertp'; import buildManualTimer from '@agoric/zoe/tools/manualTimer.js'; import { makeRatio, ceilMultiplyBy, } from '@agoric/zoe/src/contractSupport/index.js'; -import { makePromiseKit } from '@endo/promise-kit'; import { makeScriptedPriceAuthority } from '@agoric/zoe/tools/scriptedPriceAuthority.js'; +import { makeManualPriceAuthority } from '@agoric/zoe/tools/manualPriceAuthority.js'; import { assertAmountsEqual } from '@agoric/zoe/test/zoeTestHelpers.js'; import { makeParamManagerBuilder } from '@agoric/governance'; -import { - setUpZoeForTest, - setupAmmServices, -} from '../amm/vpool-xyk-amm/setup.js'; - import { makeTracer } from '../../src/makeTracer.js'; import { SECONDS_PER_YEAR } from '../../src/interest.js'; import { CHARGING_PERIOD_KEY, RECORDING_PERIOD_KEY, } from '../../src/vaultFactory/params.js'; -import { startVaultFactory } from '../../src/econ-behaviors.js'; +import { + startEconomicCommittee, + startVaultFactory, + setupAmm, +} from '../../src/econ-behaviors.js'; import '../../src/vaultFactory/types.js'; import * as Collect from '../../src/collect.js'; import { calculateCurrentDebt } from '../../src/interest-math.js'; +import { + makeBundle, + waitForPromisesToSettle, + setUpZoeForTest, + setupBootstrap, +} from '../supports.js'; + // #region Support // TODO path resolve these so refactors detect const contractRoots = { - faucet: './faucet.js', - liquidate: '../../src/vaultFactory/liquidateMinimum.js', - VaultFactory: '../../src/vaultFactory/vaultFactory.js', + faucet: '../test/vaultFactory/faucet.js', + liquidate: '../src/vaultFactory/liquidateMinimum.js', + VaultFactory: '../src/vaultFactory/vaultFactory.js', + amm: '../src/vpool-xyk-amm/multipoolMarketMaker.js', }; /** @typedef {import('../../src/vaultFactory/vaultFactory').VaultFactoryContract} VFC */ @@ -49,6 +54,8 @@ const contractRoots = { const trace = makeTracer('TestST'); const BASIS_POINTS = 10000n; +const SECONDS_PER_DAY = SECONDS_PER_YEAR / 365n; +const SECONDS_PER_WEEK = SECONDS_PER_DAY * 7n; // Define locally to test that vaultFactory uses these values export const Phase = /** @type {const} */ ({ @@ -59,44 +66,6 @@ export const Phase = /** @type {const} */ ({ TRANSFER: 'transfer', }); -/** - * - * @param {string} sourceRoot - * @returns {Promise} - */ -async function makeBundle(sourceRoot) { - const url = await importMetaResolve(sourceRoot, import.meta.url); - const path = new URL(url).pathname; - const contractBundle = await bundleSource(path); - trace('makeBundle', sourceRoot); - return contractBundle; -} - -// makeBundle is a slow step, so we do it once for all the tests. -const bundlePs = { - faucet: makeBundle(contractRoots.faucet), - liquidate: makeBundle(contractRoots.liquidate), - VaultFactory: makeBundle(contractRoots.VaultFactory), -}; - -function setupAssets() { - // setup collateral assets - const aethKit = makeIssuerKit('aEth'); - - return harden({ - aethKit, - }); -} - -// Some notifier updates aren't propagating sufficiently quickly for the tests. -// This invocation (thanks to Warner) waits for all promises that can fire to -// have all their callbacks run -async function waitForPromisesToSettle() { - const pk = makePromiseKit(); - setImmediate(pk.resolve); - return pk.promise; -} - /** * dL: 1M, lM: 105%, lP: 10%, iR: 100, lF: 500 * @@ -116,28 +85,65 @@ function defaultParamValues(debtBrand) { }); } -async function setupAmmAndElectorate( - timer, - zoe, - aethLiquidity, - runLiquidity, - runIssuer, - aethIssuer, - electorateTerms, -) { - const runBrand = await E(runIssuer).getBrand(); - const centralR = { issuer: runIssuer, brand: runBrand }; +// makeBundle is a slow step, so we do it once for all the tests. +test.before(async t => { + const { zoe, feeMintAccess } = setUpZoeForTest(); + const runIssuer = E(zoe).getFeeIssuer(); + const runBrand = E(runIssuer).getBrand(); + const aethKit = makeIssuerKit('aEth'); + const contextPs = { + bundles: { + faucet: makeBundle(contractRoots.faucet), + liquidate: makeBundle(contractRoots.liquidate), + VaultFactory: makeBundle(contractRoots.VaultFactory), + amm: makeBundle(contractRoots.amm), + }, + zoe, + feeMintAccess, + electorateTerms: undefined, + // { + // committeeName: 'Star Chamber', + // committeeSize: 3, + // }, + aethKit, + runKit: { issuer: runIssuer, brand: runBrand }, + loanTiming: { + chargingPeriod: 2n, + recordingPeriod: 6n, + }, + rates: runBrand.then(r => defaultParamValues(r)), + aethInitialLiquidity: AmountMath.make(aethKit.brand, 300n), + }; + const frozenCtx = await deeplyFulfilled(harden(contextPs)); + t.context = { ...frozenCtx }; + trace(t, 'CONTEXT'); +}); +const setupAmmAndElectorate = async (t, aethLiquidity, runLiquidity) => { const { - amm, - committeeCreator, - governor, - invitationAmount, - electorateInstance, - space, - } = await setupAmmServices(electorateTerms, centralR, timer, zoe); - - const liquidityIssuer = E(amm.ammPublicFacet).addPool(aethIssuer, 'Aeth'); + zoe, + aethKit: { issuer: aethIssuer }, + electorateTerms, + bundles: { amm }, + timer, + } = t.context; + + const space = setupBootstrap(t, timer); + const { produce, consume, instance } = space; + produce.ammBundle.resolve(amm); + startEconomicCommittee(space, electorateTerms); + setupAmm(space); + + const governorCreatorFacet = consume.ammGovernorCreatorFacet; + const governorInstance = await instance.consume.ammGovernor; + const governorPublicFacet = await E(zoe).getPublicFacet(governorInstance); + const governedInstance = E(governorPublicFacet).getGovernedContract(); + + /** @type { GovernedPublicFacet } */ + // @ts-expect-error cast from unknown + const ammPublicFacet = await E(governorCreatorFacet).getPublicFacet(); + + const liquidityIssuer = E(ammPublicFacet).addPool(aethIssuer, 'Aeth'); const liquidityBrand = await E(liquidityIssuer).getBrand(); const liqProposal = harden({ @@ -147,9 +153,7 @@ async function setupAmmAndElectorate( }, want: { Liquidity: AmountMath.makeEmpty(liquidityBrand) }, }); - const liqInvitation = await E( - amm.ammPublicFacet, - ).makeAddLiquidityInvitation(); + const liqInvitation = await E(ammPublicFacet).makeAddLiquidityInvitation(); const ammLiquiditySeat = await E(zoe).offer( liqInvitation, @@ -160,36 +164,29 @@ async function setupAmmAndElectorate( }), ); + // TODO get the creator directly const newAmm = { - ammCreatorFacet: amm.ammCreatorFacet, - ammPublicFacet: amm.ammPublicFacet, - instance: amm.governedInstance, + ammCreatorFacet: await consume.ammCreatorFacet, + ammPublicFacet, + instance: governedInstance, ammLiquidity: E(ammLiquiditySeat).getPayout('Liquidity'), }; - return { - governor, - amm: newAmm, - committeeCreator, - electorateInstance, - invitationAmount, - space, - }; -} + return { amm: newAmm, space }; +}; /** - * @param {ERef} zoe - * @param {ERef} feeMintAccess - * @param {Brand} runBrand + * + * @param {ExecutionContext} t * @param {bigint} runInitialLiquidity */ -async function getRunFromFaucet( - zoe, - feeMintAccess, - runBrand, - runInitialLiquidity, -) { - const bundle = await bundlePs.faucet; +const getRunFromFaucet = async (t, runInitialLiquidity) => { + const { + bundles: { faucet: bundle }, + zoe, + feeMintAccess, + runKit: { brand: runBrand }, + } = t.context; /** @type {Promise>} */ // @ts-expect-error cast const installation = E(zoe).install(bundle); @@ -212,79 +209,81 @@ async function getRunFromFaucet( const runPayment = await E(faucetSeat).getPayout('RUN'); return runPayment; -} +}; /** * NOTE: called separately by each test so AMM/zoe/priceAuthority don't interfere * - * @param {LoanTiming} loanTiming - * @param {unknown} priceList + * @param {ExecutionContext} t + * @param {Array | Ratio} priceOrList * @param {Amount} unitAmountIn - * @param {Brand} aethBrand - * @param {unknown} electorateTerms * @param {TimerService} timer * @param {unknown} quoteInterval - * @param {unknown} aethLiquidity * @param {bigint} runInitialLiquidity - * @param {Issuer} aethIssuer */ async function setupServices( - loanTiming, - priceList, + t, + priceOrList, unitAmountIn, - aethBrand, - electorateTerms, - timer = buildManualTimer(console.log), + timer = buildManualTimer(t.log), quoteInterval, - aethLiquidity, runInitialLiquidity, - aethIssuer, ) { - const { zoe, feeMintAccess } = await setUpZoeForTest(); - const runIssuer = await E(zoe).getFeeIssuer(); - const runBrand = await E(runIssuer).getBrand(); - const runPayment = await getRunFromFaucet( + const { zoe, - feeMintAccess, - runBrand, - runInitialLiquidity, - ); + runKit: { issuer: runIssuer, brand: runBrand }, + aethKit: { brand: aethBrand, issuer: aethIssuer, mint: aethMint }, + bundles: { VaultFactory, liquidate }, + loanTiming, + rates, + aethInitialLiquidity, + } = t.context; + t.context.timer = timer; + + const runPayment = await getRunFromFaucet(t, runInitialLiquidity); + trace(t, 'faucet', { runInitialLiquidity, runPayment }); const runLiquidity = { proposal: harden(AmountMath.make(runBrand, runInitialLiquidity)), payment: runPayment, }; + const aethLiquidity = { + proposal: aethInitialLiquidity, + payment: aethMint.mintPayment(aethInitialLiquidity), + }; const { amm: ammFacets, space } = await setupAmmAndElectorate( - timer, - zoe, + t, aethLiquidity, runLiquidity, - runIssuer, - aethIssuer, - electorateTerms, ); const { consume, produce } = space; + trace(t, 'amm', { ammFacets }); const quoteMint = makeIssuerKit('quote', AssetKind.SET).mint; - const priceAuthority = makeScriptedPriceAuthority({ - actualBrandIn: aethBrand, - actualBrandOut: runBrand, - priceList, - timer, - quoteMint, - unitAmountIn, - quoteInterval, - }); - produce.priceAuthority.resolve(priceAuthority); - - produce.feeMintAccess.resolve(feeMintAccess); - const vaultBundles = await Collect.allValues({ - VaultFactory: bundlePs.VaultFactory, - liquidate: bundlePs.liquidate, - }); - produce.vaultBundles.resolve(vaultBundles); + // Cheesy hack for easy use of manual price authority + const pa = Array.isArray(priceOrList) + ? makeScriptedPriceAuthority({ + actualBrandIn: aethBrand, + actualBrandOut: runBrand, + priceList: priceOrList, + timer, + quoteMint, + unitAmountIn, + quoteInterval, + }) + : makeManualPriceAuthority({ + actualBrandIn: aethBrand, + actualBrandOut: runBrand, + initialPrice: priceOrList, + timer, + quoteMint, + }); + produce.priceAuthority.resolve(pa); + + produce.vaultBundles.resolve({ VaultFactory, liquidate }); await startVaultFactory(space, { loanParams: loanTiming }); + const agoricNames = consume.agoricNames; const installs = await Collect.allValues({ vaultFactory: E(agoricNames).lookup('installation', 'VaultFactory'), @@ -296,13 +295,30 @@ async function setupServices( const vaultFactoryCreatorFacet = /** @type { any } */ ( E(governorCreatorFacet).getCreatorFacet() ); + + // Add a vault that will lend on aeth collateral + const aethVaultManagerP = E(vaultFactoryCreatorFacet).addVaultType( + aethIssuer, + 'AEth', + rates, + ); + /** @type {[any, VaultFactory, VFC['publicFacet']]} */ // @ts-expect-error cast - const [governorInstance, vaultFactory, lender] = await Promise.all([ + const [ + governorInstance, + vaultFactory, + lender, + aethVaultManager, + priceAuthority, + ] = await Promise.all([ E(agoricNames).lookup('instance', 'VaultFactoryGovernor'), vaultFactoryCreatorFacet, E(governorCreatorFacet).getPublicFacet(), + aethVaultManagerP, + pa, ]); + trace(t, 'pa', { governorInstance, vaultFactory, lender, priceAuthority }); const { g, v } = { g: { @@ -313,6 +329,7 @@ async function setupServices( v: { vaultFactory, lender, + aethVaultManager, }, }; @@ -331,43 +348,25 @@ async function setupServices( test('first', async t => { const { aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const loanTiming = { + runKit: { issuer: runIssuer, brand: runBrand }, + zoe, + rates, + } = t.context; + t.context.loanTiming = { chargingPeriod: 2n, recordingPeriod: 10n, }; - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; - const services = await setupServices( - loanTiming, + t, [500n, 15n], AmountMath.make(aethBrand, 900n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, - buildManualTimer(console.log), undefined, - aethLiquidity, + undefined, 500n, - aethIssuer, - ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - - // Add a vault that will lend on aeth collateral - const rates = defaultParamValues(runBrand); - const aethVaultManager = await E(vaultFactory).addVaultType( - aethIssuer, - 'AEth', - rates, ); + const { vaultFactory, lender, aethVaultManager } = services.vaultFactory; + trace(t, 'services', { services, vaultFactory, lender }); // Create a loan for 470 RUN with 1100 aeth collateral const collateralAmount = AmountMath.make(aethBrand, 1100n); @@ -395,7 +394,7 @@ test('first', async t => { AmountMath.add(loanAmount, fee), 'vault lent 470 RUN', ); - trace('correct debt', debtAmount); + trace(t, 'correct debt', debtAmount); const { RUN: lentAmount } = await E(vaultSeat).getCurrentAllocation(); const loanProceeds = await E(vaultSeat).getPayouts(); @@ -472,46 +471,35 @@ test('first', async t => { test('price drop', async t => { const { + zoe, aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); + runKit: { brand: runBrand }, + rates, + } = t.context; - const manualTimer = buildManualTimer(console.log); + const manualTimer = buildManualTimer(t.log); // When the price falls to 636, the loan will get liquidated. 636 for 900 // Aeth is 1.4 each. The loan is 270 RUN. The margin is 1.05, so at 636, 400 // Aeth collateral could support a loan of 268. - const loanTiming = { + t.context.loanTiming = { chargingPeriod: 2n, recordingPeriod: 10n, }; - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; const services = await setupServices( - loanTiming, - [1000n, 677n, 636n], + t, + makeRatio(1000n, runBrand, 900n, aethBrand), AmountMath.make(aethBrand, 900n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, manualTimer, undefined, - aethLiquidity, 500n, - aethIssuer, ); - trace('setup'); + trace(t, 'setup'); const { - zoe, - runKit: { brand: runBrand }, + vaultFactory: { vaultFactory, lender }, + priceAuthority, } = services; - const { vaultFactory, lender } = services.vaultFactory; - - // Add a vault that will lend on aeth collateral - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); // Create a loan for 270 RUN with 400 aeth collateral const collateralAmount = AmountMath.make(aethBrand, 400n); @@ -527,13 +515,13 @@ test('price drop', async t => { Collateral: aethMint.mintPayment(collateralAmount), }), ); - trace('loan made', loanAmount); + trace(t, 'loan made', loanAmount); const { vault, publicNotifiers: { vault: vaultNotifier }, } = await E(vaultSeat).getOfferResult(); - trace('offer result', vault); + trace(t, 'offer result', vault); const debtAmount = await E(vault).getCurrentDebt(); const fee = ceilMultiplyBy(loanAmount, rates.loanFee); t.deepEqual( @@ -543,7 +531,7 @@ test('price drop', async t => { ); let notification = await E(vaultNotifier).getUpdateSince(); - trace('got notificaation', notification); + trace(t, 'got notificaation', notification); t.is(notification.value.vaultState, Phase.ACTIVE); t.deepEqual((await notification.value).debtSnapshot, { @@ -557,15 +545,21 @@ test('price drop', async t => { AmountMath.make(aethBrand, 400n), 'vault holds 11 Collateral', ); - await manualTimer.tick(); + trace(t, 'pa2', priceAuthority); + + priceAuthority.setPrice(makeRatio(677n, runBrand, 900n, aethBrand)); + trace(t, 'price dropped a little'); + // await manualTimer.tick(); + notification = await E(vaultNotifier).getUpdateSince(); t.is(notification.value.vaultState, Phase.ACTIVE); - await manualTimer.tick(); + await E(priceAuthority).setPrice(makeRatio(636n, runBrand, 900n, aethBrand)); + // await manualTimer.tick(); notification = await E(vaultNotifier).getUpdateSince( notification.updateCount, ); - trace('price changed to liquidate', notification.value.vaultState); + trace(t, 'price changed to liquidate', notification.value.vaultState); t.is(notification.value.vaultState, Phase.LIQUIDATING); t.deepEqual( @@ -578,8 +572,11 @@ test('price drop', async t => { AmountMath.make(runBrand, 284n), 'Debt remains while liquidating', ); + trace(t, 'debt remains', AmountMath.make(runBrand, 284n)); + await E(priceAuthority).setPrice(makeRatio(1000n, runBrand, 900n, aethBrand)); + // await manualTimer.tick(); + trace(t, 'debt gone'); - await manualTimer.tick(); notification = await E(vaultNotifier).getUpdateSince( notification.updateCount, ); @@ -618,12 +615,16 @@ test('price drop', async t => { test('price falls precipitously', async t => { const { + zoe, aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const loanTiming = { + runKit: { brand: runBrand }, + rates, + } = t.context; + t.context.loanTiming = { chargingPeriod: 2n, recordingPeriod: 10n, }; + t.context.aethInitialLiquidity = AmountMath.make(aethBrand, 900n); // The borrower will deposit 4 Aeth, and ask to borrow 470 RUN. The // PriceAuthority's initial quote is 180. The max loan on 4 Aeth would be 600 @@ -633,34 +634,17 @@ test('price falls precipitously', async t => { // The Autowap provides 534 RUN for the 4 Aeth collateral, so the borrower // gets 41 back - const manualTimer = buildManualTimer(console.log); - const aethInitialLiquidity = AmountMath.make(aethBrand, 900n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + const manualTimer = buildManualTimer(t.log); const services = await setupServices( - loanTiming, + t, [2200n, 19180n, 1650n, 150n], AmountMath.make(aethBrand, 900n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, manualTimer, undefined, - aethLiquidity, 1500n, - aethIssuer, ); - const { - zoe, - runKit: { brand: runBrand }, - } = services; const { vaultFactory, lender } = services.vaultFactory; - // Add a vault that will lend on aeth collateral - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); - // Create a loan for 370 RUN with 400 aeth collateral const collateralAmount = AmountMath.make(aethBrand, 400n); const loanAmount = AmountMath.make(runBrand, 370n); @@ -687,7 +671,7 @@ test('price falls precipitously', async t => { AmountMath.add(loanAmount, fee), 'borrower owes 388 RUN', ); - trace('correct debt', debtAmount); + trace(t, 'correct debt', debtAmount); const { RUN: lentAmount } = await E(userSeat).getCurrentAllocation(); t.deepEqual(lentAmount, loanAmount, 'received 470 RUN'); @@ -787,46 +771,28 @@ test('price falls precipitously', async t => { }); test('vaultFactory display collateral', async t => { - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 900n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + aethKit: { brand: aethBrand }, + runKit: { brand: runBrand }, + rates: defaultRates, + } = t.context; + t.context.aethInitialLiquidity = AmountMath.make(aethBrand, 900n); + t.context.rates = harden({ + ...defaultRates, + loanFee: makeRatio(530n, runBrand, BASIS_POINTS), + }); const services = await setupServices( - loanTiming, + t, [500n, 1500n], AmountMath.make(aethBrand, 90n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - runKit: { brand: runBrand }, - } = services; const { vaultFactory } = services.vaultFactory; - - const rates = harden({ - ...defaultParamValues(runBrand), - loanFee: makeRatio(530n, runBrand, BASIS_POINTS), - }); - - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); - const collaterals = await E(vaultFactory).getCollaterals(); - t.deepEqual(collaterals[0], { brand: aethBrand, liquidationMargin: makeRatio(105n, runBrand), @@ -839,46 +805,33 @@ test('vaultFactory display collateral', async t => { // charging period is 1 week. Clock ticks by days test('interest on multiple vaults', async t => { const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const secondsPerDay = SECONDS_PER_YEAR / 365n; - const loanTiming = { - chargingPeriod: secondsPerDay * 7n, - recordingPeriod: secondsPerDay * 7n, + zoe, + aethKit: { mint: aethMint, brand: aethBrand }, + runKit: { issuer: runIssuer, brand: runBrand }, + rates: defaultRates, + } = t.context; + const rates = { + ...defaultRates, + interestRate: makeRatio(5n, runBrand), }; - // Clock ticks by days - const manualTimer = buildManualTimer(console.log, 0n, secondsPerDay); - - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), + t.context.rates = rates; + t.context.loanTiming = { + chargingPeriod: SECONDS_PER_WEEK, + recordingPeriod: SECONDS_PER_WEEK, }; + // Clock ticks by days + const manualTimer = buildManualTimer(t.log, 0n, SECONDS_PER_DAY); const services = await setupServices( - loanTiming, + t, [500n, 1500n], AmountMath.make(aethBrand, 90n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, manualTimer, - secondsPerDay, - aethLiquidity, + SECONDS_PER_DAY, 500n, - aethIssuer, ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; const { vaultFactory, lender } = services.vaultFactory; - const rates = { - ...defaultParamValues(runBrand), - interestRate: makeRatio(5n, runBrand), - }; - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); - // Create a loan for Alice for 4700 RUN with 1100 aeth collateral const collateralAmount = AmountMath.make(aethBrand, 1100n); const aliceLoanAmount = AmountMath.make(runBrand, 4700n); @@ -1066,40 +1019,22 @@ test('interest on multiple vaults', async t => { }); test('adjust balances', async t => { - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; - const { + zoe, aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + runKit: { issuer: runIssuer, brand: runBrand }, + rates, + } = t.context; const services = await setupServices( - loanTiming, + t, [15n], AmountMath.make(aethBrand, 1n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); + const { lender } = services.vaultFactory; // initial loan ///////////////////////////////////// @@ -1320,40 +1255,21 @@ test('adjust balances', async t => { }); test('transfer vault', async t => { - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; - const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + aethKit: { mint: aethMint, brand: aethBrand }, + zoe, + runKit: { issuer: runIssuer, brand: runBrand }, + } = t.context; const services = await setupServices( - loanTiming, + t, [15n], AmountMath.make(aethBrand, 1n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); + const { lender } = services.vaultFactory; // initial loan ///////////////////////////////////// @@ -1387,7 +1303,7 @@ test('transfer vault', async t => { // TODO this should not need `await` const transferInvite = await E(aliceVault).makeTransferInvitation(); const inviteProps = await getInvitationProperties(transferInvite); - trace('TRANSFER INVITE', transferInvite, inviteProps); + trace(t, 'TRANSFER INVITE', transferInvite, inviteProps); /** @type {UserSeat} */ const transferSeat = await E(zoe).offer(transferInvite); const { @@ -1488,41 +1404,23 @@ test('transfer vault', async t => { // Alice will over repay her borrowed RUN. In order to make that possible, // Bob will also take out a loan and will give her the proceeds. test('overdeposit', async t => { - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; - const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + aethKit: { mint: aethMint, brand: aethBrand }, + zoe, + runKit: { issuer: runIssuer, brand: runBrand }, + rates, + } = t.context; const services = await setupServices( - loanTiming, + t, [15n], AmountMath.make(aethBrand, 1n), - aethBrand, - { committeeName: 'TheCabal', committeeSize: 5 }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; const { vaultFactory, lender } = services.vaultFactory; - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); - // Alice's loan ///////////////////////////////////// // Create a loan for Alice for 5000 RUN with 1000 aeth collateral @@ -1642,51 +1540,36 @@ test('overdeposit', async t => { // charged interest (twice), which will trigger liquidation. test('mutable liquidity triggers and interest', async t => { const { + zoe, aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + runKit: { issuer: runIssuer, brand: runBrand }, + rates: defaultRates, + } = t.context; - const secondsPerDay = SECONDS_PER_YEAR / 365n; - const loanTiming = { - chargingPeriod: secondsPerDay * 7n, - recordingPeriod: secondsPerDay * 7n, + // Add a vaultManager with 10000 aeth collateral at a 200 aeth/RUN rate + const rates = harden({ + ...defaultRates, + // charge 5% interest + interestRate: makeRatio(5n, runBrand), + }); + t.context.rates = rates; + + t.context.loanTiming = { + chargingPeriod: SECONDS_PER_WEEK, + recordingPeriod: SECONDS_PER_WEEK, }; // charge interest on every tick - const manualTimer = buildManualTimer(console.log, 0n, secondsPerDay * 7n); + const manualTimer = buildManualTimer(t.log, 0n, SECONDS_PER_WEEK); const services = await setupServices( - loanTiming, + t, [10n, 7n], AmountMath.make(aethBrand, 1n), - aethBrand, - { - committeeName: 'TheCabal', - committeeSize: 5, - }, manualTimer, - secondsPerDay * 7n, - aethLiquidity, + SECONDS_PER_WEEK, 500n, - aethIssuer, ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - - // Add a vaultManager with 10000 aeth collateral at a 200 aeth/RUN rate - const rates = harden({ - ...defaultParamValues(runBrand), - // charge 5% interest - interestRate: makeRatio(5n, runBrand), - }); - - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); + const { lender } = services.vaultFactory; // initial loans ///////////////////////////////////// @@ -1833,7 +1716,7 @@ test('bad chargingPeriod', async t => { chargingPeriod: 2, recordingPeriod: 10n, }; - + t.context.loanTiming = loanTiming; t.throws( () => makeParamManagerBuilder() @@ -1846,47 +1729,28 @@ test('bad chargingPeriod', async t => { }); test('collect fees from loan and AMM', async t => { - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 10n, - }; - const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); + zoe, + aethKit: { mint: aethMint, brand: aethBrand }, + runKit: { brand: runBrand }, + rates, + } = t.context; const priceList = [500n, 15n]; const unitAmountIn = AmountMath.make(aethBrand, 900n); - const electorateTerms = { committeeName: 'TheCabal', committeeSize: 5 }; - const manualTimer = buildManualTimer(console.log); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + const manualTimer = buildManualTimer(t.log); + + // Add a pool with 900 aeth collateral at a 201 aeth/RUN rate const services = await setupServices( - loanTiming, + t, priceList, unitAmountIn, - aethBrand, - electorateTerms, manualTimer, undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { brand: runBrand }, - } = services; const { vaultFactory, lender } = services.vaultFactory; - // Add a pool with 900 aeth collateral at a 201 aeth/RUN rate - const rates = defaultParamValues(runBrand); - - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); - // Create a loan for 470 RUN with 1100 aeth collateral const collateralAmount = AmountMath.make(aethBrand, 1100n); const loanAmount = AmountMath.make(runBrand, 470n); @@ -1906,7 +1770,7 @@ test('collect fees from loan and AMM', async t => { const debtAmount = await E(vault).getCurrentDebt(); const fee = ceilMultiplyBy(AmountMath.make(runBrand, 470n), rates.loanFee); t.deepEqual(debtAmount, AmountMath.add(loanAmount, fee), 'vault loaned RUN'); - trace('correct debt', debtAmount); + trace(t, 'correct debt', debtAmount); const { RUN: lentAmount } = await E(vaultSeat).getCurrentAllocation(); const loanProceeds = await E(vaultSeat).getPayouts(); @@ -1950,40 +1814,22 @@ test('collect fees from loan and AMM', async t => { test('close loan', async t => { const { + zoe, aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; + runKit: { issuer: runIssuer, brand: runBrand }, + rates, + } = t.context; + const services = await setupServices( - loanTiming, + t, [15n], AmountMath.make(aethBrand, 1n), - aethBrand, - { - committeeName: 'Star Chamber', - committeeSize: 5, - }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); + const { lender } = services.vaultFactory; // initial loan ///////////////////////////////////// @@ -2087,40 +1933,20 @@ test('close loan', async t => { test('excessive loan', async t => { const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; + zoe, + aethKit: { mint: aethMint, brand: aethBrand }, + runKit: { brand: runBrand }, + } = t.context; + const services = await setupServices( - loanTiming, + t, [15n], AmountMath.make(aethBrand, 1n), - aethBrand, - { - committeeName: 'Star Chamber', - committeeSize: 5, - }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); + const { lender } = services.vaultFactory; // Try to Create a loan for Alice for 5000 RUN with 100 aeth collateral const collateralAmount = AmountMath.make(aethBrand, 100n); @@ -2150,41 +1976,20 @@ test('excessive loan', async t => { */ test('excessive debt on collateral type', async t => { const { - aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; - const loanTiming = { - chargingPeriod: 2n, - recordingPeriod: 6n, - }; + zoe, + aethKit: { mint: aethMint, brand: aethBrand }, + runKit: { brand: runBrand }, + } = t.context; + const services = await setupServices( - loanTiming, + t, [15n], AmountMath.make(aethBrand, 1n), - aethBrand, - { - committeeName: 'Star Chamber', - committeeSize: 5, - }, - buildManualTimer(console.log), + buildManualTimer(t.log), undefined, - aethLiquidity, 500n, - aethIssuer, ); - const { - zoe, - runKit: { brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; - - const rates = defaultParamValues(runBrand); - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); - + const { lender } = services.vaultFactory; const collateralAmount = AmountMath.make(aethBrand, 1_000_000n); const centralAmount = AmountMath.make(runBrand, 1_000_000n); /** @type {UserSeat} */ @@ -2200,7 +2005,7 @@ test('excessive debt on collateral type', async t => { ); await t.throwsAsync(() => E(loanSeat).getOfferResult(), { message: - 'Minting would exceed total debt limit {"brand":"[Alleged: RUN brand]","value":"[1000000n]"}', + 'Minting {"brand":"[Alleged: RUN brand]","value":"[1050000n]"} would exceed total debt limit {"brand":"[Alleged: RUN brand]","value":"[1000000n]"}', }); }); @@ -2213,53 +2018,38 @@ test('excessive debt on collateral type', async t => { // a floorDivideBy and a ceilingDivideBy will leave her unliquidated. test('mutable liquidity sensitivity of triggers and interest', async t => { const { + zoe, aethKit: { mint: aethMint, issuer: aethIssuer, brand: aethBrand }, - } = setupAssets(); - const aethInitialLiquidity = AmountMath.make(aethBrand, 300n); - const aethLiquidity = { - proposal: aethInitialLiquidity, - payment: aethMint.mintPayment(aethInitialLiquidity), - }; + runKit: { issuer: runIssuer, brand: runBrand }, + rates: defaultRates, + } = t.context; - const secondsPerDay = SECONDS_PER_YEAR / 365n; - const loanTiming = { - chargingPeriod: secondsPerDay * 7n, - recordingPeriod: secondsPerDay * 7n, + t.context.loanTiming = { + chargingPeriod: SECONDS_PER_WEEK, + recordingPeriod: SECONDS_PER_WEEK, }; - // charge interest on every tick - const manualTimer = buildManualTimer(console.log, 0n, secondsPerDay * 7n); - const services = await setupServices( - loanTiming, - [10n, 7n], - AmountMath.make(aethBrand, 1n), - aethBrand, - { - committeeName: 'TheCabal', - committeeSize: 5, - }, - manualTimer, - secondsPerDay * 7n, - aethLiquidity, - 500n, - aethIssuer, - ); - - const { - zoe, - runKit: { issuer: runIssuer, brand: runBrand }, - } = services; - const { vaultFactory, lender } = services.vaultFactory; // Add a vaultManager with 10000 aeth collateral at a 200 aeth/RUN rate const rates = harden({ - ...defaultParamValues(runBrand), + ...defaultRates, // charge 5% interest loanFee: makeRatio(500n, runBrand, BASIS_POINTS), }); + t.context.rates = rates; - await E(vaultFactory).addVaultType(aethIssuer, 'AEth', rates); + // charge interest on every tick + const manualTimer = buildManualTimer(t.log, 0n, SECONDS_PER_WEEK); + const services = await setupServices( + t, + [10n, 7n], + AmountMath.make(aethBrand, 1n), + manualTimer, + SECONDS_PER_WEEK, + 500n, + ); // initial loans ///////////////////////////////////// + const { lender } = services.vaultFactory; // Create a loan for Alice for 5000 RUN with 1000 aeth collateral const aliceCollateralAmount = AmountMath.make(aethBrand, 1000n); diff --git a/packages/zoe/src/contractFacet/internal-types.js b/packages/zoe/src/contractFacet/internal-types.js index a4973a90496..a708a80681b 100644 --- a/packages/zoe/src/contractFacet/internal-types.js +++ b/packages/zoe/src/contractFacet/internal-types.js @@ -1,5 +1,9 @@ // @ts-check +/** + * @typedef {( {zcf: ZCF} ) => void} TestJigSetter + */ + /** * @callback MakeZCFZygote * @@ -10,7 +14,7 @@ * @param {VatPowers} powers * @param {ERef} zoeService * @param {Issuer} invitationIssuer - * @param {Function | undefined} testJigSetter + * @param {TestJigSetter} testJigSetter * @returns {ZCFZygote} */ diff --git a/packages/zoe/src/contractFacet/types.js b/packages/zoe/src/contractFacet/types.js index bdd942e118e..61fce8f17d1 100644 --- a/packages/zoe/src/contractFacet/types.js +++ b/packages/zoe/src/contractFacet/types.js @@ -120,13 +120,13 @@ * never in production; i.e., it is only called if `testJigSetter` * was supplied. * - * If no, \testFn\ is supplied, then an empty jig will be used. + * If no, `testFn` is supplied, then an empty jig will be used. * An additional `zcf` property set to the current ContractFacet * will be appended to the returned jig object (overriding any * provided by the `testFn`). * * @callback SetTestJig - * @param {() => any} testFn + * @param {() => Record} testFn * @returns {void} */ diff --git a/packages/zoe/src/contractFacet/vatRoot.js b/packages/zoe/src/contractFacet/vatRoot.js index 23deea30650..a2a1a684a38 100644 --- a/packages/zoe/src/contractFacet/vatRoot.js +++ b/packages/zoe/src/contractFacet/vatRoot.js @@ -15,7 +15,7 @@ import '../internal-types.js'; import { makeZCFZygote } from './zcfZygote.js'; /** - * @param {VatPowers} powers + * @param {VatPowers & { testJigSetter: TestJigSetter }} powers * @returns {{ executeContract: ExecuteContract}} */ export function buildRootObject(powers) { @@ -24,7 +24,6 @@ export function buildRootObject(powers) { // zygote vats (essentially freezing and then creating copies of // vats), `makeZCFZygote`, `zcfZygote.evaluateContract` and // `zcfZygote.startContract` should exposed separately. - // @ts-expect-error no TS defs for rickety test scaffolding const { testJigSetter } = powers; /** @type {ExecuteContract} */ diff --git a/packages/zoe/tools/manualPriceAuthority.js b/packages/zoe/tools/manualPriceAuthority.js new file mode 100644 index 00000000000..d90a3f9e56e --- /dev/null +++ b/packages/zoe/tools/manualPriceAuthority.js @@ -0,0 +1,81 @@ +// @ts-check + +import { AmountMath, makeIssuerKit, AssetKind } from '@agoric/ertp'; +import { E } from '@endo/eventual-send'; +import { Far } from '@endo/marshal'; +import { makeNotifierKit } from '@agoric/notifier'; +import { + makeOnewayPriceAuthorityKit, + floorMultiplyBy, + floorDivideBy, +} from '../src/contractSupport/index.js'; + +export function makeManualPriceAuthority(options) { + const { + actualBrandIn, + actualBrandOut, + initialPrice, // brandOut / brandIn + timer, + quoteIssuerKit = makeIssuerKit('quote', AssetKind.SET), + } = options; + const { brand, issuer: quoteIssuer, mint: quoteMint } = quoteIssuerKit; + + /** @type {Ratio} */ + let currentPrice = initialPrice; + + const { notifier, updater } = makeNotifierKit(); + updater.updateState(currentPrice); + + /** @param {PriceQuoteValue} quote */ + const authenticateQuote = quote => { + const quoteAmount = AmountMath.make(brand, harden(quote)); + const quotePayment = quoteMint.mintPayment(quoteAmount); + return harden({ quoteAmount, quotePayment }); + }; + const calcAmountOut = amountIn => { + AmountMath.coerce(actualBrandIn, amountIn); + return floorMultiplyBy(amountIn, currentPrice); + }; + + const calcAmountIn = amountOut => { + return floorDivideBy(amountOut, currentPrice); + }; + + function createQuote(priceQuery) { + const quote = priceQuery(calcAmountOut, calcAmountIn); + if (!quote) { + return undefined; + } + + const { amountIn, amountOut } = quote; + return E(timer) + .getCurrentTimestamp() + .then(now => + authenticateQuote([{ amountIn, amountOut, timer, timestamp: now }]), + ); + } + + /* --* @type {ERef>} */ + const priceAuthorityOptions = harden({ + timer, + createQuote, + actualBrandIn, + actualBrandOut, + quoteIssuer, + notifier, + }); + + const { + priceAuthority, + adminFacet: { fireTriggers }, + } = makeOnewayPriceAuthorityKit(priceAuthorityOptions); + + return Far('PriceAuthority', { + setPrice: newPrice => { + currentPrice = newPrice; + updater.updateState(currentPrice); + fireTriggers(createQuote); + }, + ...priceAuthority, + }); +}