Skip to content

Commit

Permalink
feat: restore PSM state from former VM vstorage contents
Browse files Browse the repository at this point in the history
fixes: #6645

 - compute total fees
 - style: keep to jessie-check in startPSM
 - use vatPowers.chainStorageEntries in restorePSM
  • Loading branch information
dckc committed Apr 25, 2023
1 parent 8f0d936 commit cf2f84e
Show file tree
Hide file tree
Showing 5 changed files with 525 additions and 24 deletions.
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
2 changes: 2 additions & 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,8 @@ const BASIS_POINTS = 10_000n;
* periodicFeeCollectors: import('../feeDistributor.js').PeriodicFeeCollector[],
* bankMints: Mint[],
* psmKit: MapStore<Brand, PSMKit>,
* psmFeePurse: ERef<Purse<'nat'>>,
* anchorBalancePayments: MapStore<Brand, Payment<'nat'>>,
* econCharterKit: EconCharterStartResult,
* reserveKit: GovernanceFacetKit<import('../reserve/assetReserve.js')['start']>,
* stakeFactoryKit: GovernanceFacetKit<import('../stakeFactory/stakeFactory.js')['start']>,
Expand Down
240 changes: 226 additions & 14 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, makeSet } 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 { boardSlottingMarshaller } from '@agoric/vats/tools/board-utils.js';
import { deeplyFulfilledObject } from '@agoric/internal';
import { makeScalarMapStore } from '@agoric/vat-data';

Expand All @@ -18,24 +20,157 @@ 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;
const { details: X, Fail } = assert;

export { inviteCommitteeMembers, startEconCharter, inviteToEconCharter };

/**
* @param {EconomyBootstrapPowers & WellKnownSpaces} powers
* Decode vstorage value to CapData
* XXX already written somewhere?
*
* @param {unknown} value
*/
const decodeToCapData = value => {
assert.typeof(value, 'string');

// { blockHeight: 123, values: [ ... ] }
const item = JSON.parse(value); // or throw
assert.typeof(item, 'object');
assert(item);
const { values } = item;
assert(Array.isArray(values));

assert.equal(values.length, 1);
// { body: "...", slots: [ ... ] }
const data = JSON.parse(values[0]);
assert.typeof(data, 'object');
assert(data);
assert.typeof(data.body, 'string');
assert(Array.isArray(data.slots));

/** @type {import('@endo/marshal').CapData<string>} */
// @ts-expect-error cast
const capData = data;
return capData;
};

/**
* Provide access to object graphs serialized in vstorage.
*
* @param {Array<[string, string]>} entries
* @param {(slot: string, iface?: string) => any} [slotToVal]
*/
export const makeHistoryReviver = (entries, slotToVal = undefined) => {
const board = boardSlottingMarshaller(slotToVal);
const vsMap = makeMap(entries);

const getItem = key => {
const raw = vsMap.get(key) || Fail`no ${key}`;
const capData = decodeToCapData(raw);
return harden(board.fromCapData(capData));
};
const children = prefix => [
...makeSet(
entries
.map(([k, _]) => k)
.filter(k => k.length > prefix.length && k.startsWith(prefix))
.map(k => k.slice(prefix.length).split('.')[0]),
),
];
return harden({ getItem, children, has: k => vsMap.has(k) });
};

/**
* @param {Array<[key: string, value: string]>} chainStorageEntries
* @param {string} keyword
* @param {{ minted: Brand<'nat'>, anchor: Brand<'nat'> }} brands
* @returns {{ metrics?: MetricsNotification, governance?: Record<string, *> }}
*/
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(`published.psm.${Stable.symbol}.${keyword}.metrics`)) {
return {};
}
const metricsWithOldBoardIDs = toSlotReviver.getItem(
`published.psm.${Stable.symbol}.${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(`published.psm.IST.${keyword}.metrics`),
governance: brandReviver.getItem(`published.psm.IST.${keyword}.governance`)
.current,
};
};

/**
* Mint IST needed to restore feePoolBalance for each PSM.
*
* @param { EconomyBootstrapPowers & ChainStorageVatParams } powers
*/
export const mintPSMFees = async ({
vatParameters: { chainStorageEntries },
consume: { feeMintAccess: feeMintAccessP, zoe },
produce: { psmFeePurse },
installation: {
consume: { centralSupply },
},
}) => {
const old = makeHistoryReviver(chainStorageEntries || []);
const psmRootKey = `published.psm.${Stable.symbol}.`;
const psmNames = old.children(psmRootKey);
const purse = E(E(zoe).getFeeIssuer()).makeEmptyPurse();

const depositPayment = async () => {
const values = psmNames.map(
a => old.getItem(`${psmRootKey}${a}.metrics`).feePoolBalance.value,
);
const total = values.reduce((tot, v) => tot + v);
console.log('minting', total, ' fees for ', psmNames);

const feeMintAccess = await feeMintAccessP;
/** @type {Awaited<ReturnType<typeof import('@agoric/vats/src/centralSupply.js').start>>} */
const { creatorFacet } = await E(zoe).startInstance(
centralSupply,
{},
{ bootstrapPaymentValue: total },
{ feeMintAccess },
'centralSupply',
);
const payment = await E(creatorFacet).getBootstrapPayment();
await E(purse).deposit(payment);
};
await (psmNames.length > 0 && depositPayment());
psmFeePurse.resolve(purse);
};
harden(mintPSMFees);

/**
* @typedef {{
* vatParameters: { chainStorageEntries?: Array<[k: string, v: string]>,
* }}} ChainStorageVatParams
* @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 +182,8 @@ export const startPSM = async (
chainStorage,
chainTimerService,
psmKit,
anchorBalancePayments: anchorBalancePaymentsP,
psmFeePurse,
},
produce: { psmKit: producepsmKit },
installation: {
Expand Down Expand Up @@ -97,6 +234,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 +250,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,6 +259,11 @@ export const startPSM = async (
value: makeRatio(GiveMintedFeeBP, minted, BASIS_POINTS),
},
MintLimit: { type: ParamTypes.AMOUNT, value: mintLimit },
...oldState.governance,
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: electorateInvitationAmount,
},
},
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
Expand Down Expand Up @@ -170,6 +314,33 @@ 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 feePoolPmt = await E(psmFeePurse).withdraw(metrics.feePoolBalance);

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

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

/** @typedef {import('./econ-behaviors.js').PSMKit} psmKit */
/** @type {psmKit} */
const newpsmKit = {
Expand Down Expand Up @@ -217,18 +388,26 @@ 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 },
// XXX: prune testFirstAnchorKit in favor of anchorMints
produce: {
testFirstAnchorKit,
anchorBalancePayments: produceAnchorBalancePayments,
},
},
{ options: { anchorOptions = {} } = {} },
) => {
Expand Down Expand Up @@ -268,7 +447,23 @@ export const makeAnchorAsset = async (

testFirstAnchorKit.resolve(kit);

return Promise.all([
const toSlotReviver = makeHistoryReviver(chainStorageEntries || []);
const metricsKey = `published.psm.${Stable.symbol}.${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 @@ -374,12 +569,27 @@ export const INVITE_PSM_COMMITTEE_MANIFEST = harden(

/** @type {BootstrapManifest} */
export const PSM_MANIFEST = {
[mintPSMFees.name]: {
vatParameters: { chainStorageEntries: true },
consume: { feeMintAccess: 'zoe', zoe: 'zoe' },
produce: { psmFeePurse: true },
installation: {
consume: { centralSupply: 'zoe' },
},
},
[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]: {
vatParameters: { chainStorageEntries: true },
consume: {
agoricNamesAdmin: true,
board: true,
Expand All @@ -391,6 +601,8 @@ export const PSM_MANIFEST = {
econCharterKit: 'econCommitteeCharter',
chainTimerService: 'timer',
psmKit: true,
anchorBalancePayments: true,
psmFeePurse: true,
},
produce: { psmKit: 'true' },
installation: {
Expand Down
2 changes: 1 addition & 1 deletion packages/inter-protocol/test/psm/setupPsm.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const setupPsmBootstrap = async (
produce.chainStorage.resolve(mockChainStorage);
produce.board.resolve(makeBoard());

return { produce, consume, ...spaces, mockChainStorage };
return { vatParameters: {}, produce, consume, ...spaces, mockChainStorage };
};

/**
Expand Down
Loading

0 comments on commit cf2f84e

Please sign in to comment.