diff --git a/packages/governance/src/exported.js b/packages/governance/src/exported.js new file mode 100644 index 000000000000..90d2c4eeb4ab --- /dev/null +++ b/packages/governance/src/exported.js @@ -0,0 +1 @@ +import './types'; diff --git a/packages/treasury/package.json b/packages/treasury/package.json index a0c8847ec068..19286843b183 100644 --- a/packages/treasury/package.json +++ b/packages/treasury/package.json @@ -37,6 +37,7 @@ "@agoric/deploy-script-support": "^0.2.9", "@agoric/ertp": "^0.11.2", "@agoric/eventual-send": "^0.13.14", + "@agoric/governance": "^0.1.0", "@agoric/marshal": "^0.4.11", "@agoric/nat": "^4.0.0", "@agoric/notifier": "^0.3.14", diff --git a/packages/treasury/src/paramKeys.js b/packages/treasury/src/paramKeys.js new file mode 100644 index 000000000000..9fb3e85449cb --- /dev/null +++ b/packages/treasury/src/paramKeys.js @@ -0,0 +1,13 @@ +// @ts-check + +export const POOL_FEE_KEY = 'poolFee'; +export const PROTOCOL_FEE_KEY = 'protocolFee'; + +export const CHARGING_PERIOD_KEY = 'chargingPeriod'; +export const RECORDING_PERIOD_KEY = 'recordingPeriod'; + +export const INITIAL_MARGIN_KEY = 'initialMargin'; +export const LIQUIDATION_MARGIN_KEY = 'liquidationMargin'; +export const INITIAL_PRICE_KEY = 'initialPrice'; +export const INTEREST_RATE_KEY = 'interestRate'; +export const LOAN_FEE_KEY = 'loanFee'; diff --git a/packages/treasury/src/stablecoinMachine.js b/packages/treasury/src/stablecoinMachine.js index f73c91b40fed..c8b22a7281e5 100644 --- a/packages/treasury/src/stablecoinMachine.js +++ b/packages/treasury/src/stablecoinMachine.js @@ -2,6 +2,7 @@ import '@agoric/zoe/exported'; import '@agoric/zoe/src/contracts/exported'; +import '@agoric/governance/src/exported'; // The StableCoinMachine owns a number of VaultManagers, and a mint for the // "RUN" stablecoin. This overarching SCM will hold ownershipTokens in the @@ -32,10 +33,26 @@ import { makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/ratio'; import { AmountMath } from '@agoric/ertp'; +import { + buildParamManager, + ParamType, +} from '@agoric/governance/src/param-manager'; + import { makeTracer } from './makeTracer'; import { makeVaultManager } from './vaultManager'; import { makeLiquidationStrategy } from './liquidateMinimum'; import { makeMakeCollectFeesInvitation } from './collectRewardFees'; +import { + PROTOCOL_FEE_KEY, + POOL_FEE_KEY, + RECORDING_PERIOD_KEY, + CHARGING_PERIOD_KEY, + INITIAL_MARGIN_KEY, + LIQUIDATION_MARGIN_KEY, + INITIAL_PRICE_KEY, + INTEREST_RATE_KEY, + LOAN_FEE_KEY, +} from './paramKeys'; const trace = makeTracer('ST'); @@ -64,6 +81,22 @@ export async function start(zcf) { )}) must be a BigInt`, ); + const { + publicFacet: paramManagerPublic, + manager: paramManager, + } = buildParamManager([ + { + name: POOL_FEE_KEY, + value: loanParams.poolFee, + type: ParamType.BIGINT, + }, + { + name: PROTOCOL_FEE_KEY, + value: loanParams.protocolFee, + type: ParamType.BIGINT, + }, + ]); + const [runMint, govMint] = await Promise.all([ zcf.makeZCFMint('RUN', undefined, harden({ decimalPlaces: 6 })), zcf.makeZCFMint('Governance', undefined, harden({ decimalPlaces: 6 })), @@ -114,11 +147,15 @@ export async function start(zcf) { { Central: runIssuer }, { timer: timerService, - poolFee: loanParams.poolFee, - protocolFee: loanParams.protocolFee, + // TODO(hibbert): make the AMM use a paramManager. For now, the values + // are fixed after creation of an autoswap instance. + poolFee: paramManagerPublic.lookup(POOL_FEE_KEY), + protocolFee: paramManagerPublic.lookup(PROTOCOL_FEE_KEY), }, ); + const rateManagers = makeStore('brand'); // Brand -> rateManager + // We process only one offer per collateralType. They must tell us the // dollar value of their collateral, and we create that many RUN. // collateralKeyword = 'aEth' @@ -131,6 +168,49 @@ export async function start(zcf) { const collateralBrand = zcf.getBrandForIssuer(collateralIssuer); assert(!collateralTypes.has(collateralBrand)); + const { + publicFacet: rateManagerPublic, + manager: rateManager, + } = buildParamManager([ + { + name: CHARGING_PERIOD_KEY, + value: loanParams.chargingPeriod, + type: ParamType.BIGINT, + }, + { + name: RECORDING_PERIOD_KEY, + value: loanParams.recordingPeriod, + type: ParamType.BIGINT, + }, + { + name: INITIAL_MARGIN_KEY, + value: rates.initialMargin, + type: ParamType.RATIO, + }, + { + name: LIQUIDATION_MARGIN_KEY, + value: rates.liquidationMargin, + type: ParamType.RATIO, + }, + { + name: INITIAL_PRICE_KEY, + value: rates.initialPrice, + type: ParamType.RATIO, + }, + { + name: INTEREST_RATE_KEY, + value: rates.interestRate, + type: ParamType.RATIO, + }, + { + name: LOAN_FEE_KEY, + value: rates.loanFee, + type: ParamType.RATIO, + }, + ]); + assert(!rateManagers.has(collateralBrand)); + rateManagers.init(collateralBrand, rateManager); + const { creatorFacet: liquidationFacet } = await E(zoe).startInstance( liquidationInstall, { RUN: runIssuer }, @@ -147,7 +227,8 @@ export async function start(zcf) { want: { Governance: _govOut }, // ownership of the whole stablecoin machine } = seat.getProposal(); assert(!collateralTypes.has(collateralBrand)); - const runAmount = multiplyBy(collateralIn, rates.initialPrice); + const initialPrice = rateManagerPublic.lookup(INITIAL_PRICE_KEY); + const runAmount = multiplyBy(collateralIn, initialPrice); // arbitrarily, give governance tokens equal to RUN tokens const govAmount = AmountMath.make(runAmount.value, govBrand); @@ -223,10 +304,10 @@ export async function start(zcf) { runMint, collateralBrand, priceAuthority, - rates, + rateManagerPublic, reallocateReward, timerService, - loanParams, + paramManagerPublic, liquidationStrategy, ); collateralTypes.init(collateralBrand, vm); @@ -318,8 +399,6 @@ export async function start(zcf) { return getBootstrapPayment; } - const getBootstrapPayment = mintBootstrapPayment(); - const publicFacet = harden({ getAMM() { return autoswapInstance; @@ -331,6 +410,7 @@ export async function start(zcf) { getRunIssuer() { return runIssuer; }, + getParamManager: () => paramManagerPublic, }); const { makeCollectFeesInvitation } = makeMakeCollectFeesInvitation( @@ -348,8 +428,10 @@ export async function start(zcf) { }, getCollaterals, getRewardAllocation, - getBootstrapPayment, + getBootstrapPayment: mintBootstrapPayment(), makeCollectFeesInvitation, + getParamManager: () => paramManager, + getRateManager: rateManagers.get, }); return harden({ creatorFacet: stablecoinMachine, publicFacet }); diff --git a/packages/treasury/src/types.js b/packages/treasury/src/types.js index 3cebed95c364..02cf73845c3a 100644 --- a/packages/treasury/src/types.js +++ b/packages/treasury/src/types.js @@ -32,6 +32,9 @@ * @property {(collateralIssuer: Issuer, collateralKeyword: Keyword, rates: Rates) => Promise} makeAddTypeInvitation * @property {() => Instance} getAMM * @property {() => Promise>} getCollaterals + * @property {() => Payment} getBootstrapPayment + * @property {() => ParamManager} paramManager + * @property {() => ParamManager} getRateManager */ /** @@ -65,6 +68,10 @@ * @property {() => Promise} getCollateralQuote * @property {() => Ratio} getInitialMargin * @property {() => Ratio} getInterestRate - The annual interest rate on a loan + * @property {() => RelativeTime} getChargingPeriod - The period (in seconds) at + * which interest is charged to the loan. + * @property {() => RelativeTime} getRecordingPeriod - The period (in seconds) + * at which interest is recorded to the loan. * @property {ReallocateReward} reallocateReward */ @@ -113,6 +120,12 @@ * @property {RelativeTime} recordingPeriod */ +/** + * @typedef {Object} AMMFees + * @property {bigint} poolFee + * @property {bigint} protocolFee + */ + /** * @typedef {Object} LiquidationStrategy * @property {() => KeywordKeywordRecord} keywordMapping @@ -127,10 +140,10 @@ * @param {ZCFMint} runMint * @param {Brand} collateralBrand * @param {ERef} priceAuthority - * @param {Rates} rates - * @param {StageReward} rewardPoolStaging + * @param {ParamManagerPublic} rateManager + * @param {ReallocateReward} reallocateReward * @param {TimerService} timerService - * @param {LoanParams} loanParams + * @param {ParamManagerPublic} paramManager * @param {LiquidationStrategy} liquidationStrategy * @returns {VaultManager} */ @@ -142,7 +155,7 @@ * @param {ZCFMint} runMint * @param {ERef} autoswap * @param {ERef} priceAuthority - * @param {LoanParams} loanParams + * @param {ParamManagerPublic} paramManager * @param {Timestamp} startTimeStamp * @returns {VaultKit} */ diff --git a/packages/treasury/src/vault.js b/packages/treasury/src/vault.js index c7446097213d..e83e3f127ef1 100644 --- a/packages/treasury/src/vault.js +++ b/packages/treasury/src/vault.js @@ -27,7 +27,7 @@ export function makeVaultKit( runMint, autoswap, priceAuthority, - loanParams, + loanParamManager, startTimeStamp, ) { const trace = makeTracer('VV'); @@ -57,8 +57,8 @@ export function makeVaultKit( const interestCalculator = makeInterestCalculator( runBrand, manager.getInterestRate(), - loanParams.chargingPeriod, - loanParams.recordingPeriod, + manager.getChargingPeriod(), + manager.getRecordingPeriod(), ); function getCollateralAllocated(seat) { diff --git a/packages/treasury/src/vaultManager.js b/packages/treasury/src/vaultManager.js index c491490425da..6a4cee43c8ea 100644 --- a/packages/treasury/src/vaultManager.js +++ b/packages/treasury/src/vaultManager.js @@ -17,6 +17,14 @@ import { makeVaultKit } from './vault'; import { makePrioritizedVaults } from './prioritizedVaults'; import { liquidate } from './liquidation'; import { makeTracer } from './makeTracer'; +import { + RECORDING_PERIOD_KEY, + LIQUIDATION_MARGIN_KEY, + INITIAL_MARGIN_KEY, + LOAN_FEE_KEY, + INTEREST_RATE_KEY, + CHARGING_PERIOD_KEY, +} from './paramKeys'; const trace = makeTracer(' VM '); @@ -32,10 +40,10 @@ export function makeVaultManager( runMint, collateralBrand, priceAuthority, - rates, + rateManager, reallocateReward, timerService, - loanParams, + loanParamManager, liquidationStrategy, ) { const { brand: runBrand } = runMint.getIssuerRecord(); @@ -43,17 +51,23 @@ export function makeVaultManager( const shared = { // loans below this margin may be liquidated getLiquidationMargin() { - return rates.liquidationMargin; + return rateManager.lookup(LIQUIDATION_MARGIN_KEY); }, // loans must initially have at least 1.2x collateralization getInitialMargin() { - return rates.initialMargin; + return rateManager.lookup(INITIAL_MARGIN_KEY); }, getLoanFee() { - return rates.loanFee; + return rateManager.lookup(LOAN_FEE_KEY); }, getInterestRate() { - return rates.interestRate; + return rateManager.lookup(INTEREST_RATE_KEY); + }, + getChargingPeriod() { + return rateManager.lookup(CHARGING_PERIOD_KEY); + }, + getRecordingPeriod() { + return rateManager.lookup(RECORDING_PERIOD_KEY); }, async getCollateralQuote() { // get a quote for one unit of the collateral @@ -174,7 +188,7 @@ export function makeVaultManager( const periodNotifier = E(timerService).makeNotifier( 0n, - loanParams.recordingPeriod, + rateManager.lookup(RECORDING_PERIOD_KEY), ); const { zcfSeat: poolIncrementSeat } = zcf.makeEmptySeatKit(); @@ -211,7 +225,7 @@ export function makeVaultManager( runMint, autoswap, priceAuthority, - loanParams, + loanParamManager, startTimeStamp, ); diff --git a/packages/treasury/test/test-bootstrapPayment.js b/packages/treasury/test/test-bootstrapPayment.js index 7316d722a8d7..7cdea2c09cc7 100644 --- a/packages/treasury/test/test-bootstrapPayment.js +++ b/packages/treasury/test/test-bootstrapPayment.js @@ -35,6 +35,8 @@ test('bootstrap payment', async t => { const loanParams = { chargingPeriod: 2n, recordingPeriod: 10n, + poolFee: 24n, + protocolFee: 6n, }; const manualTimer = buildManualTimer(console.log); // This test value is not a statement about the actual value to @@ -81,6 +83,8 @@ test('bootstrap payment - only minted once', async t => { const loanParams = { chargingPeriod: 2n, recordingPeriod: 10n, + poolFee: 24n, + protocolFee: 6n, }; const manualTimer = buildManualTimer(console.log); // This test value is not a statement about the actual value to @@ -136,6 +140,8 @@ test('bootstrap payment - default value is 0n', async t => { const loanParams = { chargingPeriod: 2n, recordingPeriod: 10n, + poolFee: 24n, + protocolFee: 6n, }; const manualTimer = buildManualTimer(console.log); const { creatorFacet: stablecoinMachine, instance } = await E( diff --git a/packages/treasury/test/test-stablecoin.js b/packages/treasury/test/test-stablecoin.js index 4f287c5b7d4b..9276c23c0df5 100644 --- a/packages/treasury/test/test-stablecoin.js +++ b/packages/treasury/test/test-stablecoin.js @@ -311,8 +311,8 @@ test('price drop', async t => { const loanParams = { chargingPeriod: 2n, recordingPeriod: 10n, - poolFee: 24, - protocolFee: 6, + poolFee: 24n, + protocolFee: 6n, }; const manualTimer = buildManualTimer(console.log); const { creatorFacet: stablecoinMachine, publicFacet: lender } = await E( diff --git a/packages/treasury/test/vault-contract-wrapper.js b/packages/treasury/test/vault-contract-wrapper.js index c1596224ee4c..6bb85badf34d 100644 --- a/packages/treasury/test/vault-contract-wrapper.js +++ b/packages/treasury/test/vault-contract-wrapper.js @@ -11,11 +11,12 @@ import { makeFakePriceAuthority } from '@agoric/zoe/tools/fakePriceAuthority'; import { makeRatio } from '@agoric/zoe/src/contractSupport/ratio'; import { Far } from '@agoric/marshal'; +import { buildParamManager } from '@agoric/governance/src/param-manager'; import { makeVaultKit } from '../src/vault'; import { paymentFromZCFMint } from '../src/burn'; -import { SECONDS_PER_YEAR } from '../src/interest'; const BASIS_POINTS = 10000n; +const SECONDS_PER_HOUR = 60n * 60n; /** @param {ContractFacet} zcf */ export async function start(zcf) { @@ -66,11 +67,16 @@ export async function start(zcf) { getInterestRate() { return makeRatio(5n, runBrand); }, + getChargingPeriod() { + return SECONDS_PER_HOUR * 24n; + }, + getRecordingPeriod() { + return SECONDS_PER_HOUR * 24n * 7n; + }, collateralBrand, reallocateReward, }; - const SECONDS_PER_HOUR = SECONDS_PER_YEAR / 365n / 24n; const timer = buildManualTimer(console.log, 0n, SECONDS_PER_HOUR * 24n); const options = { actualBrandIn: collateralBrand, @@ -82,16 +88,15 @@ export async function start(zcf) { }; const priceAuthority = makeFakePriceAuthority(options); + const { publicFacet } = buildParamManager([]); + const { vault, openLoan, accrueInterestAndAddToPool } = await makeVaultKit( zcf, managerMock, runMint, autoswapMock, priceAuthority, - { - chargingPeriod: SECONDS_PER_HOUR * 24n, - recordingPeriod: SECONDS_PER_HOUR * 24n * 7n, - }, + publicFacet, timer.getCurrentTimestamp(), ); diff --git a/yarn.lock b/yarn.lock index d972b694b7ce..ac5b86f2aaef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@agoric/babel-standalone/-/babel-standalone-7.14.3.tgz#1bf201417481d37d2797dd3283590dcbef775b4d" integrity sha512-Rdlxts19kvxHsqTjTrhGJv0yKgCyjCSVgXoLBZf9dZ7lyo/j+6xFQBiWjP03bXve3X74wUvA1QU55wVey8DEiQ== +"@agoric/babel-standalone@^7.14.3", "@agoric/babel-standalone@^7.9.5": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@agoric/babel-standalone/-/babel-standalone-7.14.3.tgz#1bf201417481d37d2797dd3283590dcbef775b4d" + integrity sha512-Rdlxts19kvxHsqTjTrhGJv0yKgCyjCSVgXoLBZf9dZ7lyo/j+6xFQBiWjP03bXve3X74wUvA1QU55wVey8DEiQ== + "@agoric/nat@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@agoric/nat/-/nat-4.0.0.tgz#330dcde37fcf8882dbc5012a3b2c4c135eee4930"