diff --git a/packages/run-protocol/package.json b/packages/run-protocol/package.json index 906e9efaec4..db51137e388 100644 --- a/packages/run-protocol/package.json +++ b/packages/run-protocol/package.json @@ -10,6 +10,7 @@ "scripts": { "build": "yarn build:bundles", "build:bundles": "node ./scripts/build-bundles.js", + "deploy:proposal": "agoric deploy ./scripts/init-core.js", "test": "ava", "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", "test:xs": "exit 0", diff --git a/packages/run-protocol/scripts/init-core.js b/packages/run-protocol/scripts/init-core.js index 70f86f0e60d..820c5e711e2 100644 --- a/packages/run-protocol/scripts/init-core.js +++ b/packages/run-protocol/scripts/init-core.js @@ -1,68 +1,169 @@ /* global process */ +// @ts-check import { makeHelpers } from '@agoric/deploy-script-support'; +import { + getManifestForRunProtocol, + getManifestForEconCommittee, + getManifestForMain, + getManifestForRunStake, + getManifestForPSM, +} from '../src/core-proposal.js'; + +const { details: X } = assert; + +/** @type {Record>} */ +const installKeyGroups = { + econCommittee: { + contractGovernor: [ + '@agoric/governance/src/contractGovernor.js', + '../bundles/bundle-contractGovernor.js', + ], + committee: [ + '@agoric/governance/src/committee.js', + '../bundles/bundle-committee.js', + ], + binaryVoteCounter: [ + '@agoric/governance/src/binaryVoteCounter.js', + '../bundles/bundle-binaryVoteCounter.js', + ], + }, + runStake: { + runStake: ['../src/runStake/runStake.js', '../bundles/bundle-runStake.js'], + }, + main: { + amm: [ + '../src/vpool-xyk-amm/multipoolMarketMaker.js', + '../bundles/bundle-amm.js', + ], + vaultFactory: [ + '../src/vaultFactory/vaultFactory.js', + '../bundles/bundle-vaultFactory.js', + ], + liquidate: [ + '../src/vaultFactory/liquidateMinimum.js', + '../bundles/bundle-liquidateMinimum.js', + ], + reserve: ['../src/reserve/assetReserve.js', '../bundles/bundle-reserve.js'], + }, + psm: { + psm: ['../src/psm/psm.js', '../bundles/bundle-psm.js'], + }, +}; + +const { entries, fromEntries } = Object; + +/** @type { (obj: Record, f: (t: T) => U) => Record} */ +const mapValues = (obj, f) => + // @ts-ignore entries() loses the K type + harden(fromEntries(entries(obj).map(([p, v]) => [p, f(v)]))); + +const committeeProposalBuilder = async ({ publishRef, install }) => { + const { ROLE = 'chain' } = process.env; + + // preload ERTP, marshal, store, etc. + const [mod0, bundle0] = installKeyGroups.econCommittee.binaryVoteCounter; + const install0 = await install(mod0, bundle0, { persist: true }); + + /** @param { Record } group */ + const publishGroup = group => + mapValues(group, ([mod, bundle]) => + publishRef( + mod === mod0 && bundle === bundle0 ? install0 : install(mod, bundle), + ), + ); + return harden({ + sourceSpec: '../src/core-proposal.js', + getManifestCall: [ + getManifestForEconCommittee.name, + { + ROLE, + installKeys: { + ...publishGroup(installKeyGroups.econCommittee), + }, + }, + ], + }); +}; + +const mainProposalBuilder = async ({ publishRef, install }) => { + const { ROLE = 'chain', VAULT_FACTORY_CONTROLLER_ADDR } = process.env; + + /** @param { Record } group */ + const publishGroup = group => + mapValues(group, ([mod, bundle]) => publishRef(install(mod, bundle))); + return harden({ + sourceSpec: '../src/core-proposal.js', + getManifestCall: [ + getManifestForMain.name, + { + ROLE, + vaultFactoryControllerAddress: VAULT_FACTORY_CONTROLLER_ADDR, + installKeys: { + ...publishGroup(installKeyGroups.main), + }, + }, + ], + }); +}; + +const runStakeProposalBuilder = async ({ publishRef, install }) => { + const [mod0, bundle0] = installKeyGroups.runStake.runStake; + + return harden({ + sourceSpec: '../src/core-proposal.js', + getManifestCall: [ + getManifestForRunStake.name, + { + installKeys: { + runStake: publishRef(install(mod0, bundle0)), + }, + }, + ], + }); +}; + +const psmProposalBuilder = async ({ publishRef, install }) => { + const { ROLE = 'chain', ANCHOR_DENOM } = process.env; + + assert.typeof(ANCHOR_DENOM, 'string', X`missing ANCHOR_DENOM`); + const [mod0, bundle0] = installKeyGroups.psm.psm; + + return harden({ + sourceSpec: '../src/core-proposal.js', + getManifestCall: [ + getManifestForPSM.name, + { + ROLE, + installKeys: { + psm: publishRef(install(mod0, bundle0)), + }, + }, + ], + options: { denom: ANCHOR_DENOM }, + }); +}; + +// Build proposal for sim-chain etc. export const defaultProposalBuilder = async ({ publishRef, install }) => { const { ROLE = 'chain', VAULT_FACTORY_CONTROLLER_ADDR } = process.env; + /** @param { Record } group */ + const publishGroup = group => + mapValues(group, ([mod, bundle]) => publishRef(install(mod, bundle))); + return harden({ sourceSpec: '../src/core-proposal.js', getManifestCall: [ - 'getManifestForRunProtocol', + getManifestForRunProtocol.name, { ROLE, vaultFactoryControllerAddress: VAULT_FACTORY_CONTROLLER_ADDR, installKeys: { - runStake: publishRef( - install( - '../src/runStake/runStake.js', - '../bundles/bundle-runStake.js', - ), - ), - amm: publishRef( - install( - '../src/vpool-xyk-amm/multipoolMarketMaker.js', - '../bundles/bundle-amm.js', - ), - ), - vaultFactory: publishRef( - install( - '../src/vaultFactory/vaultFactory.js', - '../bundles/bundle-vaultFactory.js', - ), - ), - liquidate: publishRef( - install( - '../src/vaultFactory/liquidateMinimum.js', - '../bundles/bundle-liquidateMinimum.js', - ), - ), - reserve: publishRef( - install( - '../src/reserve/assetReserve.js', - '../bundles/bundle-reserve.js', - ), - ), - psm: publishRef( - install('../src/psm/psm.js', '../bundles/bundle-psm.js'), - ), - contractGovernor: publishRef( - install( - '@agoric/governance/src/contractGovernor.js', - '../bundles/bundle-contractGovernor.js', - ), - ), - committee: publishRef( - install( - '@agoric/governance/src/committee.js', - '../bundles/bundle-committee.js', - ), - ), - binaryVoteCounter: publishRef( - install( - '@agoric/governance/src/binaryVoteCounter.js', - '../bundles/bundle-binaryVoteCounter.js', - ), - ), + ...publishGroup(installKeyGroups.econCommittee), + ...publishGroup(installKeyGroups.runStake), + ...publishGroup(installKeyGroups.main), + ...publishGroup(installKeyGroups.psm), }, }, ], @@ -71,5 +172,16 @@ export const defaultProposalBuilder = async ({ publishRef, install }) => { export default async (homeP, endowments) => { const { writeCoreProposal } = await makeHelpers(homeP, endowments); - await writeCoreProposal('gov-run-protocol', defaultProposalBuilder); + + await Promise.all([ + writeCoreProposal('gov-econ-committee', committeeProposalBuilder), + writeCoreProposal('gov-runStake', runStakeProposalBuilder), + writeCoreProposal('gov-amm-vaults-etc', mainProposalBuilder), + ]); + + if (!process.env.ANCHOR_DENOM) { + console.warn('SKIP psm proposal: missing ANCHOR_DENOM'); + return; + } + await writeCoreProposal('gov-psm', psmProposalBuilder); }; diff --git a/packages/run-protocol/src/core-proposal.js b/packages/run-protocol/src/core-proposal.js index bc2acb48f75..2936ecbb4de 100644 --- a/packages/run-protocol/src/core-proposal.js +++ b/packages/run-protocol/src/core-proposal.js @@ -1,11 +1,10 @@ export * from './econ-behaviors.js'; export * from './sim-behaviors.js'; -const SHARED_POST_BOOT_MANIFEST = harden({ +const ECON_COMMITTEE_MANIFEST = harden({ startEconomicCommittee: { consume: { zoe: true, - governanceBundles: true, }, produce: { economicCommitteeCreatorFacet: 'economicCommittee' }, installation: { @@ -15,6 +14,9 @@ const SHARED_POST_BOOT_MANIFEST = harden({ produce: { economicCommittee: 'economicCommittee' }, }, }, +}); + +const SHARED_MAIN_MANIFEST = harden({ setupAmm: { consume: { chainTimerService: 'timer', @@ -122,8 +124,12 @@ const SHARED_POST_BOOT_MANIFEST = harden({ }, }); -export const CHAIN_POST_BOOT_MANIFEST = harden({ - ...SHARED_POST_BOOT_MANIFEST, +const SHARED_POST_BOOT_MANIFEST = harden({ + ...ECON_COMMITTEE_MANIFEST, + ...SHARED_MAIN_MANIFEST, +}); + +const REWARD_MANIFEST = harden({ startRewardDistributor: { consume: { chainTimerService: true, @@ -141,6 +147,90 @@ export const CHAIN_POST_BOOT_MANIFEST = harden({ }, }); +const RUN_STAKE_MANIFEST = harden({ + startLienBridge: { + consume: { bridgeManager: true }, + produce: { lienBridge: true }, + brand: { + consume: { BLD: 'BLD' }, + }, + }, + startRunStake: { + consume: { + zoe: 'zoe', + feeMintAccess: 'zoe', + lienBridge: true, + client: 'provisioning', + chainTimerService: 'timer', + economicCommitteeCreatorFacet: 'economicCommittee', + }, + produce: { + runStakeCreatorFacet: 'runStake', + runStakeGovernorCreatorFacet: 'runStake', + }, + installation: { + consume: { contractGovernor: 'zoe', runStake: 'zoe' }, + }, + instance: { + consume: { economicCommittee: 'economicCommittee' }, + produce: { runStake: 'runStake' }, + }, + brand: { + consume: { BLD: 'BLD', RUN: 'zoe' }, + produce: { Attestation: 'runStake' }, + }, + issuer: { + consume: { BLD: 'BLD' }, + produce: { Attestation: 'runStake' }, + }, + }, +}); + +export const CHAIN_POST_BOOT_MANIFEST = harden({ + ...SHARED_POST_BOOT_MANIFEST, + ...REWARD_MANIFEST, + ...RUN_STAKE_MANIFEST, +}); + +const MAIN_MANIFEST = harden({ + ...SHARED_MAIN_MANIFEST, + ...REWARD_MANIFEST, +}); + +const PSM_MANIFEST = harden({ + makeAnchorAsset: { + consume: { bankManager: 'bank' }, + issuer: { + produce: { AUSD: true }, + }, + brand: { + produce: { AUSD: true }, + }, + }, + startPSM: { + consume: { + zoe: 'zoe', + feeMintAccess: 'zoe', + economicCommitteeCreatorFacet: 'economicCommittee', + chainTimerService: 'timer', + }, + produce: { psmCreatorFacet: 'psm', psmGovernorCreatorFacet: 'psmGovernor' }, + installation: { + consume: { contractGovernor: 'zoe', psm: 'zoe' }, + }, + instance: { + consume: { economicCommittee: 'economicCommittee' }, + produce: { psm: 'psm', psmGovernor: 'psm' }, + }, + brand: { + consume: { AUSD: 'bank', RUN: 'zoe' }, + }, + issuer: { + consume: { AUSD: 'bank' }, + }, + }, +}); + export const SIM_CHAIN_POST_BOOT_MANIFEST = harden({ ...SHARED_POST_BOOT_MANIFEST, fundAMM: { @@ -185,12 +275,10 @@ export const getManifestForRunProtocol = ( return { manifest: roleToGovernanceActions[ROLE], installations: { - runStake: restoreRef(installKeys.runStake), amm: restoreRef(installKeys.amm), VaultFactory: restoreRef(installKeys.vaultFactory), liquidate: restoreRef(installKeys.liquidate), reserve: restoreRef(installKeys.reserve), - psm: restoreRef(installKeys.psm), contractGovernor: restoreRef(installKeys.contractGovernor), committee: restoreRef(installKeys.committee), binaryVoteCounter: restoreRef(installKeys.binaryVoteCounter), @@ -200,3 +288,56 @@ export const getManifestForRunProtocol = ( }, }; }; + +export const getManifestForEconCommittee = ( + { restoreRef }, + { installKeys }, +) => { + return { + manifest: ECON_COMMITTEE_MANIFEST, + installations: { + contractGovernor: restoreRef(installKeys.contractGovernor), + committee: restoreRef(installKeys.committee), + binaryVoteCounter: restoreRef(installKeys.binaryVoteCounter), + }, + }; +}; + +export const getManifestForMain = ( + { restoreRef }, + { installKeys, vaultFactoryControllerAddress }, +) => { + return { + manifest: MAIN_MANIFEST, + installations: { + amm: restoreRef(installKeys.amm), + VaultFactory: restoreRef(installKeys.vaultFactory), + liquidate: restoreRef(installKeys.liquidate), + reserve: restoreRef(installKeys.reserve), + }, + options: { + vaultFactoryControllerAddress, + }, + }; +}; + +export const getManifestForRunStake = ({ restoreRef }, { installKeys }) => { + return { + manifest: RUN_STAKE_MANIFEST, + installations: { + runStake: restoreRef(installKeys.runStake), + }, + }; +}; + +export const getManifestForPSM = ({ restoreRef }, { installKeys, denom }) => { + return { + manifest: PSM_MANIFEST, + installations: { + runStake: restoreRef(installKeys.runStake), + }, + options: { + denom, + }, + }; +}; diff --git a/packages/run-protocol/src/econ-behaviors.js b/packages/run-protocol/src/econ-behaviors.js index cf311eafb3c..295e4bc5707 100644 --- a/packages/run-protocol/src/econ-behaviors.js +++ b/packages/run-protocol/src/econ-behaviors.js @@ -7,7 +7,8 @@ import '@agoric/governance/exported.js'; import '@agoric/vats/exported.js'; import '@agoric/vats/src/core/types.js'; -import { AmountMath } from '@agoric/ertp'; +import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp'; +import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance'; import { makeGovernedTerms } from './vaultFactory/params.js'; import { makeAmmTerms } from './vpool-xyk-amm/params.js'; import { makeReserveTerms } from './reserve/params.js'; @@ -16,6 +17,7 @@ import '../exported.js'; import * as Collect from './collect.js'; import { makeRunStakeTerms } from './runStake/params.js'; +import { makeStakeReporter } from './my-lien.js'; const { details: X } = assert; @@ -33,6 +35,8 @@ const CENTRAL_DENOM_NAME = 'urun'; * ammCreatorFacet: XYKAMMCreatorFacet, * ammGovernorCreatorFacet: GovernedContractFacetAccess, * economicCommitteeCreatorFacet: CommitteeElectorateCreatorFacet, + * psmCreatorFacet: unknown, + * psmGovernorCreatorFacet: GovernedContractFacetAccess, * reservePublicFacet: unknown, * reserveCreatorFacet: GovernedContractFacetAccess, * reserveGovernorCreatorFacet: GovernedContractFacetAccess, @@ -508,17 +512,28 @@ export const startRewardDistributor = async ({ harden(startRewardDistributor); /** - * TODO: refactor vats/core/types.js like this - * - * @template T - * @typedef {{ - * consume: { [P in keyof T]: ERef }, - * produce: { [P in keyof T]: Producer }, - * }} PromiseMarket + * @param {BootstrapPowers & PromiseSpaceOf<{ + * lienBridge: StakingAuthority, + * }>} powers */ +export const startLienBridge = async ({ + consume: { bridgeManager: bridgeOptP }, + produce: { lienBridge }, + brand: { + consume: { BLD: bldP }, + }, +}) => { + const bridgeManager = await bridgeOptP; + if (!bridgeManager) { + return; + } + const bldBrand = await bldP; + const reporter = makeStakeReporter(bridgeManager, bldBrand); + lienBridge.resolve(reporter); +}; /** - * @typedef {EconomyBootstrapPowers & PromiseMarket<{ + * @typedef {EconomyBootstrapPowers & PromiseSpaceOf<{ * client: ClientManager, * lienBridge: StakingAuthority, * }>} RunStakeBootstrapPowers @@ -655,3 +670,153 @@ export const startRunStake = async ( ]); }; harden(startRunStake); + +/** + * @param {EconomyBootstrapPowers & WellKnownSpaces} powers + * @param {Object} config + * @param {bigint} config.WantStableFeeBP + * @param {bigint} config.GiveStableFeeBP + * @param {bigint} config.MINT_LIMIT + */ +export const startPSM = async ( + { + consume: { + zoe, + feeMintAccess: feeMintAccessP, + economicCommitteeCreatorFacet, + chainTimerService, + }, + produce: { psmCreatorFacet, psmGovernorCreatorFacet }, + installation: { + consume: { contractGovernor, psm: psmInstallP }, + }, + instance: { + consume: { economicCommittee }, + produce: { psm: psmInstanceR, psmGovernor: psmGovernorR }, + }, + brand: { + consume: { AUSD: anchorBrandP, RUN: runBrandP }, + }, + issuer: { + consume: { AUSD: anchorIssuerP }, + }, + }, + { + WantStableFeeBP = 1n, + GiveStableFeeBP = 3n, + MINT_LIMIT = 20_000_000n * 1_000_000n, + }, +) => { + const [ + feeMintAccess, + runBrand, + anchorBrand, + anchorIssuer, + governor, + psmInstall, + timer, + ] = await Promise.all([ + feeMintAccessP, + runBrandP, + anchorBrandP, + anchorIssuerP, + contractGovernor, + psmInstallP, + chainTimerService, + ]); + + const poserInvitationP = E( + economicCommitteeCreatorFacet, + ).getPoserInvitation(); + const [initialPoserInvitation, electorateInvitationAmount] = + await Promise.all([ + poserInvitationP, + E(E(zoe).getInvitationIssuer()).getAmountOf(poserInvitationP), + ]); + + const mintLimit = AmountMath.make(anchorBrand, MINT_LIMIT); + const terms = { + anchorBrand, + anchorPerStable: makeRatio(100n, anchorBrand, 100n, runBrand), + governedParams: { + WantStableFeeBP: { type: ParamTypes.NAT, value: WantStableFeeBP }, + GiveStableFeeBP: { type: ParamTypes.NAT, value: GiveStableFeeBP }, + MintLimit: { type: ParamTypes.AMOUNT, value: mintLimit }, + }, + [CONTRACT_ELECTORATE]: { + type: ParamTypes.INVITATION, + value: electorateInvitationAmount, + }, + }; + + const governorFacets = await E(zoe).startInstance( + governor, + {}, + { + timer, + economicCommittee, + governedContractInstallation: psmInstall, + governed: harden({ + terms, + issuerKeywordRecord: { AUSD: anchorIssuer }, + privateArgs: { feeMintAccess, initialPoserInvitation }, + }), + }, + harden({ economicCommitteeCreatorFacet }), + ); + + const governedInstance = await E(governorFacets.creatorFacet).getInstance(); + const creatorFacet = E(governorFacets.creatorFacet).getCreatorFacet(); + + psmInstanceR.resolve(governedInstance); + psmGovernorR.resolve(governorFacets.instance); + psmCreatorFacet.resolve(creatorFacet); + psmGovernorCreatorFacet.resolve(governorFacets.creatorFacet); + psmInstanceR.resolve(governedInstance); +}; +harden(startPSM); + +/** + * Make anchor issuer out of a Cosmos asset; presumably + * USDC over IBC. Add it to BankManager. + * + * @param {EconomyBootstrapPowers & WellKnownSpaces} powers + * @param {{ options: { + * denom: string, + * name?: string, + * proposedName: string, + * decimalPlaces?: number, + * }}} config + */ +export const makeAnchorAsset = async ( + { + consume: { bankManager }, + issuer: { + produce: { AUSD: issuerP }, + }, + brand: { + produce: { AUSD: brandP }, + }, + }, + { + options: { + denom, + proposedName = 'USDC Anchor', + name = 'AUSD', + decimalPlaces = 6, + }, + }, +) => { + // ISSUE: use mintHolder? + const kit = makeIssuerKit(name, AssetKind.NAT, { decimalPlaces }); + + issuerP.resolve(kit.issuer); + brandP.resolve(kit.brand); + return E(bankManager).addAsset( + denom, + name, + proposedName, + kit, // with mint + ); +}; +harden(makeAnchorAsset); diff --git a/packages/run-protocol/src/my-lien.js b/packages/run-protocol/src/my-lien.js new file mode 100644 index 00000000000..ffb71449c27 --- /dev/null +++ b/packages/run-protocol/src/my-lien.js @@ -0,0 +1,74 @@ +// @ts-check +import { AmountMath } from '@agoric/ertp'; +import { E, Far } from '@endo/far'; + +const { details: X } = assert; + +/** + * per golang/cosmos/x/lien/lien.go + * + * @typedef { 'bonded' | 'liened' | 'locked' | 'total' | 'unbonding' } AccountProperty + */ +const XLien = /** @type { const } */ ({ + name: 'lien', + LIEN_GET_ACCOUNT_STATE: 'LIEN_GET_ACCOUNT_STATE', + LIEN_SET_LIENED: 'LIEN_SET_LIENED', +}); + +/** + * @typedef { Record & { currentTime: bigint } } AccountState + * @template T + */ + +/** + * @param {ERef} bridgeManager + * @param {Brand<'nat'>} stake + * @param {string} [denom] + * @returns {StakingAuthority} + */ +export const makeStakeReporter = (bridgeManager, stake, denom = 'ubld') => { + const { make: makeAmt } = AmountMath; + /** @param {string} numeral */ + const toStake = numeral => makeAmt(stake, BigInt(numeral)); + + /** @type {StakingAuthority} */ + const stakeReporter = Far('stakeReporter', { + getAccountState: async (address, wantedBrand) => { + assert( + wantedBrand === stake, + X`Cannot getAccountState for ${wantedBrand}. Expected ${stake}.`, + ); + /** @type { AccountState } */ + const { currentTime, bonded, liened, locked, total, unbonding } = await E( + bridgeManager, + ).toBridge(XLien.name, { + type: XLien.LIEN_GET_ACCOUNT_STATE, + address, + denom, + amount: '0', + }); + return harden({ + bonded: toStake(bonded), + liened: toStake(liened), + locked: toStake(locked), + total: toStake(total), + unbonding: toStake(unbonding), + currentTime: BigInt(currentTime), + }); + }, + setLiened: async (address, previous, target) => { + assert.typeof(address, 'string'); + AmountMath.coerce(stake, previous); // TODO + const amount = AmountMath.getValue(stake, target); + const success = await E(bridgeManager).toBridge(XLien.name, { + type: XLien.LIEN_SET_LIENED, + address, + denom, + amount: `${amount}`, + }); + assert(success); + }, + }); + + return stakeReporter; +};