Skip to content

Commit

Permalink
feat!: use contractGovernor to govern Treasury using ParamManager
Browse files Browse the repository at this point in the history
extract params to a separate file
integrate contract governance into treasury
swingset test for treasury governance
  • Loading branch information
Chris-Hibbert committed Aug 3, 2021
1 parent cb88451 commit 1c82754
Show file tree
Hide file tree
Showing 27 changed files with 1,279 additions and 473 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"timeout": "30m"
},
"dependencies": {
"@endo/eslint-config": "^0.3.9",
"@endo/eslint-config": "^0.3.10",
"@endo/eslint-plugin": "^0.3.6",
"patch-package": "^6.2.2"
}
}
1 change: 1 addition & 0 deletions packages/governance/src/exported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './types';
7 changes: 7 additions & 0 deletions packages/governance/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
* @property {string} question
*/

/**
* @typedef {Object} QuestionTermsShort - ballot details for the Registrar
* @property {BallotSpec} ballotSpec
* @property {ClosingRule} closingRule
* @property {QuorumRule} quorumRule
*/

/**
* @typedef { 'amount' | 'brand' | 'installation' | 'instance' | 'nat' | 'ratio' | 'string' | 'unknown' } ParamType
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/treasury/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
"@agoric/deploy-script-support": "^0.2.19",
"@agoric/ertp": "^0.11.11",
"@agoric/eventual-send": "^0.13.23",
"@agoric/governance": "^0.1.0",
"@agoric/marshal": "^0.4.20",
"@agoric/nat": "^4.1.0",
"@agoric/notifier": "^0.3.23",
"@agoric/promise-kit": "^0.2.21",
"@agoric/same-structure": "^0.1.20",
"@agoric/store": "^0.4.23",
"@agoric/swingset-vat": "^0.19.0",
"@agoric/zoe": "^0.17.6"
Expand Down Expand Up @@ -69,7 +71,7 @@
},
"eslintConfig": {
"extends": [
"@endo"
"@agoric"
]
},
"eslintIgnore": [
Expand Down
12 changes: 12 additions & 0 deletions packages/treasury/scripts/build-bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ async function main() {
`@agoric/zoe/src/contracts/multipoolAutoswap/multipoolAutoswap`,
`${__dirname}/../bundles/bundle-multipoolAutoswap.js`,
],
[
'@agoric/governance/src/contractGovernor',
`${__dirname}/../bundles/bundle-contractGovernor.js`,
],
[
'@agoric/governance/src/committeeRegistrar',
`${__dirname}/../bundles/bundle-committeeRegistrar.js`,
],
[
'@agoric/governance/src/binaryBallotCounter',
`${__dirname}/../bundles/bundle-binaryBallotCounter.js`,
],
];
for (const [contractFilename, outputPath] of contractOutputs) {
// eslint-disable-next-line no-await-in-loop
Expand Down
84 changes: 84 additions & 0 deletions packages/treasury/src/params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @ts-check

import { buildParamManager, ParamType } from '@agoric/governance';

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 INTEREST_RATE_KEY = 'InterestRate';
export const LOAN_FEE_KEY = 'LoanFee';

export const governedParameterTerms = {
loanParams: [POOL_FEE_KEY, PROTOCOL_FEE_KEY],
poolParams: [
CHARGING_PERIOD_KEY,
RECORDING_PERIOD_KEY,
INITIAL_MARGIN_KEY,
LIQUIDATION_MARGIN_KEY,
INTEREST_RATE_KEY,
LOAN_FEE_KEY,
],
};

/** @type {{ FEE: 'fee', POOL: 'pool' }} */
export const ParamKey = {
FEE: 'fee',
POOL: 'pool',
};

export const makeFeeParamManager = loanParams => {
/** @type {FeeParamManager} */
return buildParamManager([
{
name: POOL_FEE_KEY,
value: loanParams.poolFee,
type: ParamType.NAT,
},
{
name: PROTOCOL_FEE_KEY,
value: loanParams.protocolFee,
type: ParamType.NAT,
},
]);
};

export const makePoolParamManager = (loanParams, rates) => {
/** @type {PoolParamManager} */
return buildParamManager([
{
name: CHARGING_PERIOD_KEY,
value: loanParams.chargingPeriod,
type: ParamType.NAT,
},
{
name: RECORDING_PERIOD_KEY,
value: loanParams.recordingPeriod,
type: ParamType.NAT,
},
{
name: INITIAL_MARGIN_KEY,
value: rates.initialMargin,
type: ParamType.RATIO,
},
{
name: LIQUIDATION_MARGIN_KEY,
value: rates.liquidationMargin,
type: ParamType.RATIO,
},
{
name: INTEREST_RATE_KEY,
value: rates.interestRate,
type: ParamType.RATIO,
},
{
name: LOAN_FEE_KEY,
value: rates.loanFee,
type: ParamType.RATIO,
},
]);
};
133 changes: 105 additions & 28 deletions packages/treasury/src/stablecoinMachine.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
// @ts-check
import { Far } from '@agoric/marshal';

import '@agoric/zoe/exported';
import '@agoric/zoe/src/contracts/exported';

// The StableCoinMachine owns a number of VaultManagers, and a mint for the
// "RUN" stablecoin. This overarching SCM will hold ownershipTokens in the
// individual per-type vaultManagers.
//
// makeAddTypeInvitation is a closely held method that adds a brand new
// collateral type. It specifies the initial exchange rate for that type.
//
// a second closely held method (not implemented yet) would add collateral of a
// type for which there is an existing pool. It gets the current price from the
// pool.
//
// ownershipTokens for vaultManagers entitle holders to distributions, but you
// can't redeem them outright, that would drain the utility from the economy.
import '@agoric/governance/src/exported';

import { E } from '@agoric/eventual-send';
import { assert, details, q } from '@agoric/assert';
import { assert, q, details as X } from '@agoric/assert';
import makeStore from '@agoric/store';
import {
assertProposalShape,
Expand All @@ -33,13 +19,38 @@ import {
makeRatioFromAmounts,
} from '@agoric/zoe/src/contractSupport/ratio';
import { AmountMath } from '@agoric/ertp';
import { sameStructure } from '@agoric/same-structure';

import { Far } from '@agoric/marshal';
import { makeTracer } from './makeTracer';
import { makeVaultManager } from './vaultManager';
import { makeLiquidationStrategy } from './liquidateMinimum';
import { makeMakeCollectFeesInvitation } from './collectRewardFees';
import {
makePoolParamManager,
makeFeeParamManager,
PROTOCOL_FEE_KEY,
POOL_FEE_KEY,
governedParameterTerms as governedParameterLocal,
ParamKey,
} from './params';

const trace = makeTracer('ST');

// The StableCoinMachine owns a number of VaultManagers, and a mint for the
// "RUN" stablecoin. This overarching SCM will hold ownershipTokens in the
// individual per-type vaultManagers.
//
// makeAddTypeInvitation is a closely held method that adds a brand new
// collateral type. It specifies the initial exchange rate for that type.
//
// a second closely held method (not implemented yet) would add collateral of a
// type for which there is an existing pool. It gets the current price from the
// pool.
//
// ownershipTokens for vaultManagers entitle holders to distributions, but you
// can't redeem them outright, that would drain the utility from the economy.

/** @type {ContractStartFn} */
export async function start(zcf) {
// loanParams has time limits for charging interest
Expand All @@ -50,21 +61,28 @@ export async function start(zcf) {
timerService,
liquidationInstall,
bootstrapPaymentValue = 0n,
electionManager,
governedParams,
} = zcf.getTerms();
const governorPublic = E(zcf.getZoeService()).getPublicFacet(electionManager);

assert.typeof(
loanParams.chargingPeriod,
'bigint',
details`chargingPeriod (${q(loanParams.chargingPeriod)}) must be a BigInt`,
X`chargingPeriod (${q(loanParams.chargingPeriod)}) must be a BigInt`,
);
assert.typeof(
loanParams.recordingPeriod,
'bigint',
details`recordingPeriod (${q(
loanParams.recordingPeriod,
)}) must be a BigInt`,
X`recordingPeriod (${q(loanParams.recordingPeriod)}) must be a BigInt`,
);

assert(
sameStructure(governedParams, harden(governedParameterLocal)),
X`Terms must match ${governedParameterLocal}`,
);
const feeParams = makeFeeParamManager(loanParams);

const [runMint, govMint] = await Promise.all([
zcf.makeZCFMint('RUN', undefined, harden({ decimalPlaces: 6 })),
zcf.makeZCFMint('Governance', undefined, harden({ decimalPlaces: 6 })),
Expand Down Expand Up @@ -115,11 +133,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: feeParams.getParam(POOL_FEE_KEY).value,
protocolFee: feeParams.getParam(PROTOCOL_FEE_KEY).value,
},
);

const poolParamManagers = makeStore('brand'); // Brand -> poolGovernor

// 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'
Expand All @@ -132,6 +154,10 @@ export async function start(zcf) {
const collateralBrand = zcf.getBrandForIssuer(collateralIssuer);
assert(!collateralTypes.has(collateralBrand));

assert(!poolParamManagers.has(collateralBrand));
const poolParamManager = makePoolParamManager(loanParams, rates);
poolParamManagers.init(collateralBrand, poolParamManager);

const { creatorFacet: liquidationFacet } = await E(zoe).startInstance(
liquidationInstall,
{ RUN: runIssuer },
Expand All @@ -148,6 +174,7 @@ export async function start(zcf) {
want: { Governance: _govOut }, // ownership of the whole stablecoin machine
} = seat.getProposal();
assert(!collateralTypes.has(collateralBrand));
// initialPrice is in rates, but only used at creation, so not in governor
const runAmount = multiplyBy(collateralIn, rates.initialPrice);
// arbitrarily, give governance tokens equal to RUN tokens
const govAmount = AmountMath.make(runAmount.value, govBrand);
Expand Down Expand Up @@ -224,10 +251,9 @@ export async function start(zcf) {
runMint,
collateralBrand,
priceAuthority,
rates,
poolParamManager.getParams,
reallocateReward,
timerService,
loanParams,
liquidationStrategy,
);
collateralTypes.init(collateralBrand, vm);
Expand Down Expand Up @@ -255,7 +281,7 @@ export async function start(zcf) {
const { brand: brandIn } = collateralAmount;
assert(
collateralTypes.has(brandIn),
details`Not a supported collateral type ${brandIn}`,
X`Not a supported collateral type ${brandIn}`,
);
/** @type {VaultManager} */
const mgr = collateralTypes.get(brandIn);
Expand Down Expand Up @@ -319,7 +345,29 @@ export async function start(zcf) {
return getBootstrapPayment;
}

const getBootstrapPayment = mintBootstrapPayment();
const getParams = paramDesc => {
switch (paramDesc.key) {
case ParamKey.FEE:
return feeParams.getParams();
case ParamKey.POOL:
return poolParamManagers.get(paramDesc.collateralBrand).getParams();
default:
throw Error(`Unrecognized param key for Params '${paramDesc.key}'`);
}
};

const getParamState = paramDesc => {
switch (paramDesc.keyl) {
case ParamKey.FEE:
return feeParams.getParam(paramDesc.parameterName);
case ParamKey.POOL:
return poolParamManagers
.get(paramDesc.collateralBrand)
.getParam(paramDesc.parameterName);
default:
throw Error(`Unrecognized param key for State '${paramDesc.key}'`);
}
};

const publicFacet = Far('stablecoin public facet', {
getAMM() {
Expand All @@ -332,6 +380,9 @@ export async function start(zcf) {
getRunIssuer() {
return runIssuer;
},
getParamState,
getParams,
getContractGovernor: () => governorPublic,
});

const { makeCollectFeesInvitation } = makeMakeCollectFeesInvitation(
Expand All @@ -341,6 +392,22 @@ export async function start(zcf) {
runBrand,
);

const getParamMgrAccessor = () =>
Far('paramManagerAccessor', {
get: paramDesc => {
switch (paramDesc.key) {
case ParamKey.FEE:
return feeParams;
case ParamKey.POOL:
return poolParamManagers.get(paramDesc.collateralBrand);
default:
throw Error(
`Unrecognized param key for accessor '${paramDesc.key}'`,
);
}
},
});

/** @type {StablecoinMachine} */
const stablecoinMachine = Far('stablecoin machine', {
makeAddTypeInvitation,
Expand All @@ -349,9 +416,19 @@ export async function start(zcf) {
},
getCollaterals,
getRewardAllocation,
getBootstrapPayment,
getBootstrapPayment: mintBootstrapPayment(),
makeCollectFeesInvitation,
getContractGovernor: () => electionManager,
});

return harden({ creatorFacet: stablecoinMachine, publicFacet });
const stablecoinMachineWrapper = Far('powerful stablecoinMachine wrapper', {
getParamMgrAccessor,
getLimitedCreatorFacet: () => stablecoinMachine,
});

return harden({
creatorFacet: stablecoinMachineWrapper,
publicFacet,
ParamKey,
});
}
Loading

0 comments on commit 1c82754

Please sign in to comment.