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

Release empty escrowed payments #9401

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 a3p-integration/proposals/a:upgrade-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"coreProposals": []
},
"sdk-generate": [
"vats/probe-zcf-bundle.js probe-submission"
"vats/probe-zcf-bundle.js probe-submission",
"vats/release-empty-escrow.js release-empty-escrow"
],
"type": "Software Upgrade Proposal"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from 'ava';

import { evalBundles, getIncarnation } from '@agoric/synthetic-chain';
import { getVatObjectCount } from './sql-tools.js';

const SUBMISSION_DIR = 'release-empty-escrow';

test('release empty payments from Zoe Escrow', async t => {
await null;

t.assert((await getIncarnation('zoe')) === 2, 'zoe incarnation must be two');

const n9 = await getVatObjectCount(`v9`);
console.log('census v9', n9);

await evalBundles(SUBMISSION_DIR);
});
2 changes: 2 additions & 0 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,8 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte
vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/add-auction.js"),
// upgrade vaultFactory.
vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/upgradeVaults.js"),
// upgrade scaledPriceAuthorities.
vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/upgradeScaledPriceAuthorities.js"),
}
}

Expand Down
9 changes: 3 additions & 6 deletions packages/ERTP/src/issuerKit.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,9 @@ export const upgradeIssuerKit = (
) {
Fail`Cannot (yet?) upgrade from 'noRecoverySets' to 'hasRecoverySets'`;
}
if (
oldRecoverySetsState === 'hasRecoverySets' &&
recoverySetsOption === 'noRecoverySets'
) {
Fail`Cannot (yet?) upgrade from 'hasRecoverySets' to 'noRecoverySets'`;
}
// Extant sets are not currently deleted. If the new option is
// 'noRecoverySets', they won't be used but extant ones will remain. Future
// upgrades may make it possible to delete elements from them.
const recoverySetsState = recoverySetsOption || oldRecoverySetsState;
return setupIssuerKit(
issuerRecord,
Expand Down
18 changes: 18 additions & 0 deletions packages/builders/scripts/vats/release-empty-escrow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) =>
harden({
sourceSpec: '@agoric/vats/src/proposals/releaseEmptyEscrow-proposal.js',
getManifestCall: [
'getManifestForReleaseEmptyEscrow',
{
zoeRef: publishRef(install('@agoric/vats/src/vat-zoe.js')),
},
],
});

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('releaseEmptyEscrow', defaultProposalBuilder);
};
25 changes: 25 additions & 0 deletions packages/builders/scripts/vats/upgradeScaledPriceAuthorities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) =>
harden({
sourceSpec:
'@agoric/inter-protocol/src/proposals/upgrade-scaledPriceAuthorities.js',
getManifestCall: [
'getManifestForUpgradeScaledPriceAuthorities',
{
scaledPARef: publishRef(
install('@agoric/zoe/src/contracts/scaledPriceAuthority.js'),
),
},
],
});

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);

await writeCoreProposal(
'upgradeScaledPriceAuthorities',
defaultProposalBuilder,
);
};
35 changes: 28 additions & 7 deletions packages/deploy-script-support/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,35 @@ To install code on chain or in the a3p-integration environment, you'll have to
generate a proposal, and write a script to build the core proposal. The
proposals have limited access to bootstrap powers, described by their manifests.

There are collections of proposals in .../vats/src/proposals,
smart-wallet/src/proposals, orchestration/src/proposals, pegasus/src/proposals.

The overall format is a proposalBuilder script (There are several in
.../builders/scripts/vats/) which has a default export that passes resources to
the proposal. The resources include bundled source code and string-value
parameters. The ProposalBuilder specifies (as an import string) the proposal
it uses, identifies the "manifest", (which associates permissions to access
powers in the bootstrap space with functions to be called), and builds bundles
of source code needed by the proposal.

`.../builders/scripts/vats/upgradeVaults.js` is a canonical example. It says the
proposal to run is '@agoric/inter-protocol/src/proposals/upgrade-vaults.js',
lists the manifest there as `'getManifestForUpgradeVaults'`, and directs the
creation of a bundle from
'@agoric/inter-protocol/src/vaultFactory/vaultFactory.js', which will be made
available to the proposal as `vaultsRef` in options.

`upgrade-vaults.js` defines `getManifestForUpgradeVaults()`, which returns a
`manifest` that says `upgradeVaults()` should be executed, and specifies what
powers it should have access to.

### Proposal

The proposal is called with `(powers, options)` available. The manifest
detailing the powers that will be used is usually in the same file, and
conventionally provided by a function named `getManifestForFoo`. The manifest
needs to have a unique name, since it will be referenced by name from the script.
The usual format is
needs to have a unique name, since it will be referenced by name from the
script. The usual format is
```js
export const foo = async (
{
Expand All @@ -24,6 +45,7 @@ export const foo = async (
},
options,
) => {
const { fooRef } = options;
// do the things using powers and options
};

Expand All @@ -44,14 +66,13 @@ export const getManifestForFoo = (powers, options) => {
`options` allows the proposal to be provided with arbitray other powerful
objects.

### Script
### proposalBuilder Script

The script describes how to build the core proposal. For
`agoric-3-proposals` and uploading to the chain, the script must be named in the
`CoreProposalSteps` section in
[`app.go`](https://github.com/Agoric/agoric-sdk/blob/b13743a2cccf0cb63a412b54384435596d4e81ea/golang/cosmos/app/app.go#L881),
and its `defaultProposalBuilder` will be invoked
directly.
and its `defaultProposalBuilder` will be invoked directly.

Script files should export `defaultProposalBuilder` and a `default` function
that invokes `writeCoreProposal` one or more times to generate sets of files
Expand Down Expand Up @@ -79,8 +100,8 @@ export default async (homeP, endowments) => {

The first element of `getManifestCall` is interpreted as the name of a proposal.
The second element of `getManifestCall` produces the `options` argument passed
to the proposal. (`foo` in the example above). A common thing to want to pass in
options is a reference to code to be installed on-chain. The `fooRef` example
to the proposal. (`fooRef` in the example above). A common thing to want to pass
in options is a reference to code to be installed on-chain. The `fooRef` example
above shows how. `publishRef(install(<path>))` is built from sources in the
sdk, and passed as a `bundleRef`, which contains a `bundleID` suitable for
passing to Zoe (for contracts) or `vatAdminService` (for non-contract vat code).
Expand Down
5 changes: 2 additions & 3 deletions packages/inter-protocol/src/proposals/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Proposals

These are code snippets that go into propoals to the BLDer DAO to start the Inter Protocol.
These scripts are referenced by proposals to the BLDer DAO to run on the chain.

One of the latest is `startPSM.js` so best to model after that. The style in `econ-behaviors.js` will be refactored to be like startPSM.
See the documentation in .../deploy-script-support/README.md

[syntax of the manifests](../../packages/vats/src/core/manifest.js)
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { makeTracer } from '@agoric/internal';
import { E } from '@endo/far';

const trace = makeTracer('upgradeScaledPA', true);

/**
* @param {ChainBootstrapSpace} powers
* @param {{ options: { scaledPARef: { bundleID: string } } }} options
*/
export const upgradeScaledPriceAuthorities = async (
{
consume: {
agoricNamesAdmin,
contractKits: contractKitsP,
instancePrivateArgs: instancePrivateArgsP,
zoe,
},
},
{ options },
) => {
trace('start');
const { scaledPARef } = options;
await null;

const bundleID = scaledPARef.bundleID;
if (scaledPARef && bundleID) {
await E.when(
E(zoe).installBundleID(bundleID),
installation =>
E(E(agoricNamesAdmin).lookupAdmin('installation')).update(
'scaledPriceAuthority',
installation,
),
err =>
console.error(
`🚨 failed to update scaledPriceAuthority installation`,
err,
),
);
}

const [contractKits, instancePrivateArgs] = await Promise.all([
contractKitsP,
instancePrivateArgsP,
]);
/** @type {StartedInstanceKit<any>[]} */
const scaledPAKitEntries = Array.from(contractKits.values()).filter(
kit => kit.label && kit.label.match(/scaledPriceAuthority/),
);

for (const kitEntry of scaledPAKitEntries) {
const { instance } = kitEntry;
const privateArgs = instancePrivateArgs.get(instance);
trace('upgrade scaledPriceAuthority', kitEntry.label);
await E(kitEntry.adminFacet).upgradeContract(bundleID, privateArgs);
}
};

const t = 'upgradeScaledPriceAuthority';
export const getManifestForUpgradeScaledPriceAuthorities = async (
_ign,
upgradeSPAOptions,
) => ({
manifest: {
[upgradeScaledPriceAuthorities.name]: {
consume: {
agoricNamesAdmin: t,
contractKits: t,
instancePrivateArgs: t,
zoe: t,
},
instance: {
produce: t,
},
},
},
options: { ...upgradeSPAOptions },
});
59 changes: 59 additions & 0 deletions packages/vats/src/proposals/releaseEmptyEscrow-proposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @ts-check
import { E } from '@endo/far';
import { Stable } from '@agoric/internal/src/tokens.js';

/**
* @param {BootstrapPowers & {
* consume: {
* vatAdminSvc: VatAdminSvc;
* vatStore: MapStore<
* string,
* import('@agoric/swingset-vat').CreateVatResults
* >;
* };
* }} powers
* @param {object} options
* @param {{ zoeRef }} options.options
*/
export const ReleaseEmptyEscrow = async (
{
consume: { vatAdminSvc, vatStore },
issuer: {
consume: { [Stable.symbol]: feeIssuerP },
},
brand: {
consume: { [Stable.symbol]: feeBrandP },
},
},
options,
) => {
const { zoeRef } = options.options;

const [feeIssuer, feeBrand] = await Promise.all([feeIssuerP, feeBrandP]);

assert(zoeRef.bundleID);
const zoeBundleCap = await E(vatAdminSvc).getBundleCap(zoeRef.bundleID);
const { adminNode, root: zoeRoot } = await E(vatStore).get('zoe');

await E(adminNode).upgrade(zoeBundleCap, {});

const zoeConfigFacet = await E(zoeRoot).getZoeConfigFacet();
await E(zoeConfigFacet).releaseEmptyPayments(feeIssuer, feeBrand, 1000);
console.log(`Released empty payments: `);
};

export const getManifestForReleaseEmptyEscrow = (_powers, { zoeRef }) => ({
manifest: {
[ReleaseEmptyEscrow.name]: {
consume: {
vatAdminSvc: 'vatAdminSvc',
vatStore: 'vatStore',
},
issuer: { consume: { [Stable.symbol]: 'feeIssuer' } },
brand: { consume: { [Stable.symbol]: 'feeBrand' } },
},
},
options: {
zoeRef,
},
});
2 changes: 2 additions & 0 deletions packages/zoe/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IssuerKitShape,
IssuerShape,
PaymentShape,
PurseShape,
} from '@agoric/ertp';
import { SubscriberShape } from '@agoric/notifier';
import { M } from '@agoric/store';
Expand Down Expand Up @@ -291,6 +292,7 @@ export const ZoeStorageManagerIKit = harden({
getProposalShapeForInvitation: M.call(InvitationHandleShape).returns(
M.opt(M.pattern()),
),
provideLocalPurse: M.call(IssuerShape, BrandShape).returns(PurseShape),
}),
makeOfferAccess: M.interface('ZoeStorage makeOffer access', {
getAssetKindByBrand: M.call(BrandShape).returns(AssetKindShape),
Expand Down
33 changes: 33 additions & 0 deletions packages/zoe/src/zoeService/zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';
import { makeScalarBigMapStore, prepareExo } from '@agoric/vat-data';
import { M } from '@agoric/store';
import { AmountMath, BrandShape, IssuerShape } from '@agoric/ertp';
import { makeTracer } from '@agoric/internal';
import { getCopySetKeys } from '@endo/patterns';

import { makeZoeStorageManager } from './zoeStorageManager.js';
import { makeStartInstance } from './startInstance.js';
Expand All @@ -32,6 +35,8 @@ import { ZoeServiceI } from '../typeGuards.js';

const { Fail } = assert;

const trace = makeTracer('ZOE');

/**
* Create a durable instance of Zoe.
*
Expand Down Expand Up @@ -177,6 +182,9 @@ const makeDurableZoeKit = ({

const ZoeConfigI = M.interface('ZoeConfigFacet', {
updateZcfBundleId: M.call(M.string()).returns(),
releaseEmptyPayments: M.call(IssuerShape, BrandShape, M.number()).returns(
M.promise(),
),
});

const zoeConfigFacet = prepareExo(zoeBaggage, 'ZoeConfigFacet', ZoeConfigI, {
Expand All @@ -193,6 +201,31 @@ const makeDurableZoeKit = ({
},
);
},
// TODO(8686) Remove this method once empty payments have been dropped
async releaseEmptyPayments(issuer, brand, max) {
const purse = dataAccess.provideLocalPurse(issuer, brand);
const recoverySet = purse.getRecoverySet();

await null;
trace(`${brand} recoverySet has ${recoverySet.payload.length} payments`);
let count = 0;
for (const p of getCopySetKeys(recoverySet)) {
// eslint-disable-next-line @jessie.js/no-nested-await
const amount = await issuer.getAmountOf(p);
if (AmountMath.isEmpty(amount, brand)) {
purse.deposit(p);
count += 1;
if (count >= max) {
trace(`released ${count} empty payments`);
return;
}
}
}
const finalCount = dataAccess
.provideLocalPurse(issuer, brand)
.getRecoverySet().payload.length;
trace(`released ${count} empty payments. ${finalCount} remain`);
},
});

/** @type {ZoeService} */
Expand Down
Loading
Loading