Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: start PSM contracts with metrics, governance from previous published state #7480

Merged
merged 20 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/inter-protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"@endo/far": "^0.2.18",
"@endo/marshal": "^0.8.5",
"@endo/nat": "^4.1.27",
"agoric": "^0.18.2"
"agoric": "^0.18.2",
"jessie.js": "^0.3.2"
},
"devDependencies": {
"@agoric/deploy-script-support": "^0.9.4",
Expand Down
1 change: 1 addition & 0 deletions packages/inter-protocol/src/proposals/econ-behaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const BASIS_POINTS = 10_000n;
* periodicFeeCollectors: import('../feeDistributor.js').PeriodicFeeCollector[],
* bankMints: Mint[],
* psmKit: MapStore<Brand, PSMKit>,
* anchorBalancePayments: MapStore<Brand, Payment<'nat'>>,
* econCharterKit: EconCharterStartResult,
* reserveKit: GovernanceFacetKit<import('../reserve/assetReserve.js')['start']>,
* stakeFactoryKit: GovernanceFacetKit<import('../stakeFactory/stakeFactory.js')['start']>,
Expand Down
124 changes: 107 additions & 17 deletions packages/inter-protocol/src/proposals/startPSM.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// @jessie-check

import { makeMap } from 'jessie.js';
import { AmountMath, AssetKind } from '@agoric/ertp';
import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance';
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js';
import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js';
import { E } from '@endo/far';
import { Stable } from '@agoric/vats/src/tokens.js';
import { makeHistoryReviver } from '@agoric/vats/tools/board-utils.js';
import { deeplyFulfilledObject } from '@agoric/internal';
import { makeScalarMapStore } from '@agoric/vat-data';

Expand All @@ -18,24 +20,57 @@ import {
} from './committee-proposal.js';

/** @typedef {import('@agoric/vats/src/core/lib-boot.js').BootstrapManifest} BootstrapManifest */
/** @typedef {import('../psm/psm.js').MetricsNotification} MetricsNotification */
/** @typedef {import('./econ-behaviors.js').EconomyBootstrapPowers} EconomyBootstrapPowers */

const BASIS_POINTS = 10000n;
const { details: X } = assert;

export { inviteCommitteeMembers, startEconCharter, inviteToEconCharter };

const stablePsmKey = `published.psm.${Stable.symbol}`;

/**
* @param {Array<[key: string, value: string]>} chainStorageEntries
* @param {string} keyword
* @param {{ minted: Brand<'nat'>, anchor: Brand<'nat'> }} brands
* @returns {{ metrics?: MetricsNotification, governance?: GovernanceSubscriptionState }}
*/
const findOldPSMState = (chainStorageEntries, keyword, brands) => {
// In this reviver, object references are revived as boardIDs
// from the pre-bulldozer board.
const toSlotReviver = makeHistoryReviver(chainStorageEntries);
if (!toSlotReviver.has(`${stablePsmKey}.${keyword}.metrics`)) {
return {};
}
const metricsWithOldBoardIDs = toSlotReviver.getItem(
`${stablePsmKey}.${keyword}.metrics`,
);
const oldIDtoNewBrand = makeMap([
[metricsWithOldBoardIDs.feePoolBalance.brand, brands.minted],
[metricsWithOldBoardIDs.anchorPoolBalance.brand, brands.anchor],
]);
// revive brands; other object references map to undefined
const brandReviver = makeHistoryReviver(chainStorageEntries, s =>
oldIDtoNewBrand.get(s),
);
return {
metrics: brandReviver.getItem(`${stablePsmKey}.${keyword}.metrics`),
governance: brandReviver.getItem(`${stablePsmKey}.${keyword}.governance`),
};
};

/**
* @param {EconomyBootstrapPowers & WellKnownSpaces} powers
* @param {EconomyBootstrapPowers & WellKnownSpaces & ChainStorageVatParams} powers
* @param {object} [config]
* @param {bigint} [config.WantMintedFeeBP]
* @param {bigint} [config.GiveMintedFeeBP]
* @param {bigint} [config.MINT_LIMIT]
* @param {{ anchorOptions?: AnchorOptions } } [config.options]
*
* @typedef {import('./econ-behaviors.js').EconomyBootstrapPowers} EconomyBootstrapPowers
*/
export const startPSM = async (
{
vatParameters: { chainStorageEntries = [] },
consume: {
agoricNamesAdmin,
board,
Expand All @@ -47,6 +82,7 @@ export const startPSM = async (
chainStorage,
chainTimerService,
psmKit,
anchorBalancePayments: anchorBalancePaymentsP,
},
produce: { psmKit: producepsmKit },
installation: {
Expand Down Expand Up @@ -97,6 +133,12 @@ export const startPSM = async (
const mintLimit = AmountMath.make(minted, MINT_LIMIT);
const anchorDecimalPlaces = anchorInfo.decimalPlaces || 1n;
const mintedDecimalPlaces = mintedInfo.decimalPlaces || 1n;

const oldState = findOldPSMState(chainStorageEntries, keyword, {
minted,
anchor: anchorBrand,
});

const terms = await deeplyFulfilledObject(
harden({
anchorBrand,
Expand All @@ -107,10 +149,6 @@ export const startPSM = async (
minted,
),
governedParams: {
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: electorateInvitationAmount,
},
WantMintedFee: {
type: ParamTypes.RATIO,
value: makeRatio(WantMintedFeeBP, minted, BASIS_POINTS),
Expand All @@ -120,10 +158,14 @@ export const startPSM = async (
value: makeRatio(GiveMintedFeeBP, minted, BASIS_POINTS),
},
MintLimit: { type: ParamTypes.AMOUNT, value: mintLimit },
},
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: electorateInvitationAmount,
// Override numeric config values from restored state.
...oldState.governance?.current,
// Don't override the invitation amount;
// the electorate is re-constituted rather than restored.
[CONTRACT_ELECTORATE]: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs more docs on why oldState.governance isn't sufficient.

type: ParamTypes.INVITATION,
value: electorateInvitationAmount,
},
},
}),
);
Expand Down Expand Up @@ -170,6 +212,23 @@ export const startPSM = async (
E(governorFacets.creatorFacet).getAdminFacet(),
]);

/** @param {MetricsNotification} metrics */
const restoreMetrics = async metrics => {
const anchorBalancePayments = await anchorBalancePaymentsP;
const anchorPmt = anchorBalancePayments.get(anchorBrand);

const { anchorPoolBalance: _a, ...nonPaymentMetrics } = metrics;

const seat = E(zoe).offer(
E(psmCreatorFacet).makeRestoreMetricsInvitation(),
harden({ give: { Anchor: metrics.anchorPoolBalance } }),
harden({ Anchor: anchorPmt }),
harden(nonPaymentMetrics),
);
await E(seat).getPayouts();
};
await (oldState.metrics && restoreMetrics(oldState.metrics));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why await first? we generally pass the promise in,

Suggested change
await (oldState.metrics && restoreMetrics(oldState.metrics));
await restoreMetrics(oldState.metrics);

it's an async function so it can do the awaitiing,

const metrics = await metricsP;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to restoreMetrics only if there are some oldState.metrics to restore, avoiding the expensive E(zoe).offer(...) otherwise.

oldState.metrics is not a promise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oic. normally I'd expect,

if (oldState.metrics) {
  await restoreMetrics(oldState.metrics));
}

but I suppose that trips on the jessie awaits rule


/** @typedef {import('./econ-behaviors.js').PSMKit} psmKit */
/** @type {psmKit} */
const newpsmKit = {
Expand Down Expand Up @@ -217,18 +276,25 @@ harden(startPSM);
* Make anchor issuer out of a Cosmos asset; presumably
* USDC over IBC. Add it to BankManager.
*
* Also, if vatParameters shows an anchorPoolBalance for this asset,
* mint a payment for that balance.
*
* TODO: address redundancy with publishInterchainAssetFromBank
*
* @param {EconomyBootstrapPowers & WellKnownSpaces} powers
* @param {EconomyBootstrapPowers & WellKnownSpaces & ChainStorageVatParams} powers
* @param {{options?: { anchorOptions?: AnchorOptions } }} [config]
*/
export const makeAnchorAsset = async (
{
consume: { agoricNamesAdmin, bankManager, zoe },
vatParameters: { chainStorageEntries = [] },
consume: { agoricNamesAdmin, bankManager, zoe, anchorBalancePayments },
installation: {
consume: { mintHolder },
},
produce: { testFirstAnchorKit },
produce: {
testFirstAnchorKit,
anchorBalancePayments: produceAnchorBalancePayments,
},
},
{ options: { anchorOptions = {} } = {} },
) => {
Expand Down Expand Up @@ -268,7 +334,23 @@ export const makeAnchorAsset = async (

testFirstAnchorKit.resolve(kit);

return Promise.all([
const toSlotReviver = makeHistoryReviver(chainStorageEntries);
const metricsKey = `${stablePsmKey}.${keyword}.metrics`;
if (toSlotReviver.has(metricsKey)) {
const metrics = toSlotReviver.getItem(metricsKey);
produceAnchorBalancePayments.resolve(makeScalarMapStore());
// XXX this rule should only apply to the 1st await
// eslint-disable-next-line @jessie.js/no-nested-await
const anchorPaymentMap = await anchorBalancePayments;

// eslint-disable-next-line @jessie.js/no-nested-await
const pmt = await E(mint).mintPayment(
AmountMath.make(brand, metrics.anchorPoolBalance.value),
);
anchorPaymentMap.init(brand, pmt);
}

await Promise.all([
E(E(agoricNamesAdmin).lookupAdmin('issuer')).update(keyword, kit.issuer),
E(E(agoricNamesAdmin).lookupAdmin('brand')).update(keyword, kit.brand),
E(bankManager).addAsset(
Expand Down Expand Up @@ -376,11 +458,18 @@ export const INVITE_PSM_COMMITTEE_MANIFEST = harden(
/** @type {BootstrapManifest} */
export const PSM_MANIFEST = {
[makeAnchorAsset.name]: {
consume: { agoricNamesAdmin: true, bankManager: 'bank', zoe: 'zoe' },
vatParameters: { chainStorageEntries: true },
consume: {
agoricNamesAdmin: true,
bankManager: 'bank',
zoe: 'zoe',
anchorBalancePayments: true,
},
installation: { consume: { mintHolder: 'zoe' } },
produce: { testFirstAnchorKit: true },
produce: { testFirstAnchorKit: true, anchorBalancePayments: true },
},
[startPSM.name]: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incidentally, powersFor('startPSM') isn't using this. please touch that up in boot-psm.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. boot-psm.js imports this same PSM_MANIFEST, and allPowers there includes vatParameters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- a/packages/vats/src/core/boot-psm.js
+++ b/packages/vats/src/core/boot-psm.js
@@ -221,7 +221,7 @@ export const buildRootObject = (vatPowers, vatParameters) => {
         }),
       ),
       ...anchorAssets.map(anchorOptions =>
-        startPSM(powersFor('startPSM'), {
+        startPSM(powersFor(startPSM.name), {
           options: { anchorOptions },
         }),
       ),

vatParameters: { chainStorageEntries: true },
consume: {
agoricNamesAdmin: true,
board: true,
Expand All @@ -392,6 +481,7 @@ export const PSM_MANIFEST = {
econCharterKit: 'econCommitteeCharter',
chainTimerService: 'timer',
psmKit: true,
anchorBalancePayments: true,
},
produce: { psmKit: 'true' },
installation: {
Expand Down
54 changes: 53 additions & 1 deletion packages/inter-protocol/src/psm/psm.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ import {
import { M, prepareExo, provide } from '@agoric/vat-data';
import {
atomicRearrange,
atomicTransfer,
ceilMultiplyBy,
floorDivideBy,
floorMultiplyBy,
} from '@agoric/zoe/src/contractSupport/index.js';
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

import { mustMatch } from '@agoric/store';
import { makeCollectFeesInvitation } from '../collectFees.js';
import { makeMetricsPublishKit } from '../contractSupport.js';
import {
makeMetricsPublishKit,
makeNatAmountShape,
} from '../contractSupport.js';

const { Fail } = assert;

Expand Down Expand Up @@ -134,6 +139,41 @@ export const start = async (zcf, privateArgs, baggage) => {
};
updateMetrics();

const AnchorAmountShape = makeNatAmountShape(anchorBrand);
const StableAmountShape = makeNatAmountShape(stableBrand);

/**
* @param {ZCFSeat} seat
* @param {Omit<MetricsNotification, 'anchorPoolBalance'>} target
*/
const restoreMetricsHook = (seat, target) => {
assert(
AmountMath.isEmpty(anchorPool.getAmountAllocated('Anchor', anchorBrand)),
'cannot restoreMetrics: anchorPool is not empty',
);
assert(
AmountMath.isEmpty(feePool.getAmountAllocated('Minted', stableBrand)),
'cannot restoreMetrics: feePool is not empty',
);
mustMatch(
target,
harden({
feePoolBalance: StableAmountShape,
mintedPoolBalance: StableAmountShape,
totalAnchorProvided: AnchorAmountShape,
totalMintedProvided: StableAmountShape,
}),
);
const {
give: { Anchor },
} = seat.getProposal();
stableMint.mintGains({ Minted: target.feePoolBalance }, feePool);
atomicTransfer(zcf, seat, anchorPool, { Anchor });
({ mintedPoolBalance, totalAnchorProvided, totalMintedProvided } = target);
seat.exit();
updateMetrics();
};

/**
* @param {Amount<'nat'>} toMint
*/
Expand Down Expand Up @@ -294,6 +334,18 @@ export const start = async (zcf, privateArgs, baggage) => {
makeCollectFeesInvitation() {
return makeCollectFeesInvitation(zcf, feePool, stableBrand, 'Minted');
},
makeRestoreMetricsInvitation() {
return zcf.makeInvitation(
restoreMetricsHook,
'restoreMetrics',
undefined,
M.splitRecord({
give: {
Anchor: AnchorAmountShape,
},
}),
);
},
});

const governorFacet = makeFarGovernorFacet(limitedCreatorFacet);
Expand Down
Loading