Skip to content

Commit

Permalink
test(a3p): test mintHolder functionalities after contract null upgrade (
Browse files Browse the repository at this point in the history
#10617)

closes: #10410 

## Description


This pull request intends to ensure that the mintHolder contract continues to function as expected after a null upgrade. 

Key Changes:
- A new `core-eval` was built, which acordingly to the agoric chain variant, will select a list of vbankAsset and execute a null upgrade to the respective mintHolder contract of each suitable asset.
- A new acceptance test, `mintHolder.test.js`, has been added to the a3p-integration to verify the core-eval behaviour.

MintHolder Test Plan:
   - Provision a receiver wallet with the respective assets
   - Confirm the initial balance matches expectations
   - Perform a null upgrade for the mintHolder contracts
   - Execute a core-eval to:
         - Mint a payment for each selected asset
   	- Deposit the payment into the receiver's depositFacet
   - Verify that the balance has increased by the expected amount
   - Execute a PSM swap for selected assets with IST


### Security Considerations


The execution of `mintHolder.test.js` had to be done prior to the `./genesis-test.sh` acceptance test. Otherwise it would trigger the error `bundle ${id} not loaded` during the `evalBundle` call.
See this issue for more detail: #10620 

### Scaling Considerations


This PR does not introduce any change to the mintHolder source code, or how it is being consumed. For this reason that are no scaling considerations to be made.

### Documentation Considerations


### Testing Considerations


The implemented test design is expecting to have a core-eval for each mintHolder being updated.
If desired, this could be refactored to have a single core-eval executing a null upgrade to a predefined list of assets.
The second approach has the advantage of reducing the number of files being created, although it may hinder the testing flexibility.

Which approach is more desirable?

### Upgrade Considerations
  • Loading branch information
mergify[bot] authored Dec 12, 2024
2 parents bdf5c17 + aa864ce commit 059601c
Show file tree
Hide file tree
Showing 12 changed files with 890 additions and 392 deletions.
1 change: 1 addition & 0 deletions a3p-integration/proposals/p:upgrade-19/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ addUsdOlives/
upgradeProvisionPool/
upgradeAgoricNames/
publishTestInfo/
upgrade-mintHolder/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* global E */

/// <reference types="@agoric/vats/src/core/core-eval-env"/>
/// <reference types="@agoric/vats/src/core/types-ambient"/>

/**
* The primary purpose of this script is to mint a payment of a certain
* bankAsset and deposit in an user wallet.
*
* The receiverAddress and label placeholders should be replaced with
* the desired address and asset name during the execution of each test case.
*
* See z:acceptance/mintHolder.test.js
*
* @param {BootstrapPowers} powers
*/
const sendBankAsset = async powers => {
const {
consume: { namesByAddress, contractKits: contractKitsP },
} = powers;

const receiverAddress = '{{ADDRESS}}';
const label = '{{LABEL}}';
const valueStr = '{{VALUE}}';
const value = BigInt(valueStr)

console.log(`Start sendBankAsset for ${label}`);

const contractKits = await contractKitsP;
const mintHolderKit = Array.from(contractKits.values()).filter(
kit => kit.label && kit.label === label,
);

const { creatorFacet: mint, publicFacet: issuer } = mintHolderKit[0];

/*
* Ensure that publicFacet holds an issuer by verifying that has
* the makeEmptyPurse method.
*/
await E(issuer).makeEmptyPurse()

const brand = await E(issuer).getBrand();
const amount = harden({ value, brand });
const payment = await E(mint).mintPayment(amount);

const receiverDepositFacet = E(namesByAddress).lookup(
receiverAddress,
'depositFacet',
);

await E(receiverDepositFacet).receive(payment);

console.log(`Finished sendBankAsset for ${label}`);
};

sendBankAsset;
28 changes: 28 additions & 0 deletions a3p-integration/proposals/p:upgrade-19/mintHolder.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-env node */

import '@endo/init';
import test from 'ava';
import { addUser, provisionSmartWallet } from '@agoric/synthetic-chain';
import {
mintPayment,
getAssetList,
swap,
getPSMChildren,
upgradeMintHolder,
} from './test-lib/mintHolder-helpers.js';
import { networkConfig } from './test-lib/index.js';

test('mintHolder contract is upgraded', async t => {
const receiver = await addUser('receiver');
await provisionSmartWallet(receiver, `20000000ubld`);

let assetList = await getAssetList();
t.log('List of mintHolder vats being upgraded: ', assetList);
await upgradeMintHolder(`upgrade-mintHolder`, assetList);
await mintPayment(t, receiver, assetList, 10);

const psmLabelList = await getPSMChildren(fetch, networkConfig);
assetList = await getAssetList(psmLabelList);
t.log('List of assets being swapped with IST via PSM: ', assetList);
await swap(t, receiver, assetList, 5);
});
5 changes: 3 additions & 2 deletions a3p-integration/proposals/p:upgrade-19/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
"testing/test-upgraded-board.js testUpgradedBoard",
"vats/upgrade-agoricNames.js agoricNamesCoreEvals/upgradeAgoricNames",
"testing/add-USD-OLIVES.js agoricNamesCoreEvals/addUsdOlives",
"testing/publish-test-info.js agoricNamesCoreEvals/publishTestInfo"
"testing/publish-test-info.js agoricNamesCoreEvals/publishTestInfo",
"vats/upgrade-mintHolder.js upgrade-mintHolder A3P_INTEGRATION"
]
},
"type": "module",
"license": "Apache-2.0",
"dependencies": {
"@agoric/client-utils": "0.1.1-dev-02c06c4.0",
"@agoric/client-utils": "dev",
"@agoric/ertp": "dev",
"@agoric/internal": "dev",
"@agoric/synthetic-chain": "^0.4.3",
Expand Down
162 changes: 162 additions & 0 deletions a3p-integration/proposals/p:upgrade-19/test-lib/mintHolder-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/* eslint-env node */

import {
agoric,
evalBundles,
getDetailsMatchingVats,
getISTBalance,
} from '@agoric/synthetic-chain';
import { makeVstorageKit, retryUntilCondition } from '@agoric/client-utils';
import { readFile, writeFile } from 'node:fs/promises';
import { psmSwap, snapshotAgoricNames } from './psm-lib.js';

/**
* @param {string} fileName base file name without .tjs extension
* @param {Record<string, string>} replacements
*/
export const replaceTemplateValuesInFile = async (fileName, replacements) => {
let script = await readFile(`${fileName}.tjs`, 'utf-8');
for (const [template, value] of Object.entries(replacements)) {
script = script.replaceAll(`{{${template}}}`, value);
}
await writeFile(`${fileName}.js`, script);
};

export const getPSMChildren = async (fetch, networkConfig) => {
const {
vstorage: { keys },
} = await makeVstorageKit({ fetch }, networkConfig);

const children = await keys('published.psm.IST');

return children;
};

export const getAssetList = async labelList => {
const assetList = [];
const { vbankAssets } = await snapshotAgoricNames();

// Determine the assets to consider based on labelList
const assetsToConsider =
labelList || Object.values(vbankAssets).map(asset => asset.issuerName);

for (const label of assetsToConsider) {
if (label === 'IST') {
break;
}

const vbankAsset = Object.values(vbankAssets).find(
asset => asset.issuerName === label,
);
assert(vbankAsset, `vbankAsset not found for ${label}`);

const { denom } = vbankAsset;
const mintHolderVat = `zcf-mintHolder-${label}`;

assetList.push({ label, denom, mintHolderVat });
}

return assetList;
};

export const mintPayment = async (t, address, assetList, value) => {
const SUBMISSION_DIR = 'mint-payment';

for (const asset of assetList) {
const { label, denom } = asset;
const scaled = BigInt(parseInt(value, 10) * 1_000_000).toString();

await replaceTemplateValuesInFile(`${SUBMISSION_DIR}/send-script`, {
ADDRESS: address,
LABEL: label,
VALUE: scaled,
});

await evalBundles(SUBMISSION_DIR);

const balance = await getISTBalance(address, denom);

// Add to value the BLD provisioned to smart wallet
if (label === 'BLD') {
value += 10;
}

t.is(
balance,
value,
`receiver ${denom} balance ${balance} is not ${value}`,
);
}
};

export const swap = async (t, address, assetList, want) => {
for (const asset of assetList) {
const { label, denom } = asset;

// TODO: remove condition after fixing issue #10655
if (/^DAI/.test(label)) {
break;
}

const pair = `IST.${label}`;

const istBalanceBefore = await getISTBalance(address, 'uist');
const anchorBalanceBefore = await getISTBalance(address, denom);

const psmSwapIo = {
now: Date.now,
follow: agoric.follow,
setTimeout,
log: console.log,
};

await psmSwap(
address,
['swap', '--pair', pair, '--wantMinted', want],
psmSwapIo,
);

const istBalanceAfter = await getISTBalance(address, 'uist');
const anchorBalanceAfter = await getISTBalance(address, denom);

t.is(istBalanceAfter, istBalanceBefore + want);
t.is(anchorBalanceAfter, anchorBalanceBefore - want);
}
};

const getIncarnationForAllVats = async assetList => {
const vatsIncarnation = {};

for (const asset of assetList) {
const { label, mintHolderVat } = asset;
const matchingVats = await getDetailsMatchingVats(label);
const expectedVat = matchingVats.find(vat => vat.vatName === mintHolderVat);
vatsIncarnation[label] = expectedVat.incarnation;
}
assert(Object.keys(vatsIncarnation).length === assetList.length);

return vatsIncarnation;
};

const checkVatsUpgraded = (before, current) => {
for (const vatLabel in before) {
if (current[vatLabel] !== before[vatLabel] + 1) {
console.log(`${vatLabel} upgrade failed. `);
return false;
}
}
return true;
};

export const upgradeMintHolder = async (submissionPath, assetList) => {
const before = await getIncarnationForAllVats(assetList);

await evalBundles(submissionPath);

return retryUntilCondition(
async () => getIncarnationForAllVats(assetList),
current => checkVatsUpgraded(before, current),
`mintHolder upgrade not processed yet`,
{ setTimeout, retryIntervalMs: 5000, maxRetries: 15 },
);
};
27 changes: 26 additions & 1 deletion a3p-integration/proposals/p:upgrade-19/test-lib/psm-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@

import { execa } from 'execa';
import { getNetworkConfig } from 'agoric/src/helpers.js';
import { waitUntilOfferResult } from '@agoric/client-utils';
import {
waitUntilOfferResult,
makeFromBoard,
boardSlottingMarshaller,
} from '@agoric/client-utils';
import { deepMapObject } from '@agoric/internal';
import {
agd,
agoric,
agopsLocation,
CHAINID,
executeCommand,
Expand Down Expand Up @@ -285,3 +290,23 @@ export const tryISTBalances = async (t, actualBalance, expectedBalance) => {
const minFeeDebit = 200_000;
t.is(actualBalance + minFeeDebit, expectedBalance);
};

const fromBoard = makeFromBoard();
const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal);

/**
* @param {string} path
*/
const objectFromVstorageEntries = async path => {
const rawEntries = await agoric.follow('-lF', `:${path}`, '-o', 'text');
return Object.fromEntries(marshaller.fromCapData(JSON.parse(rawEntries)));
};

export const snapshotAgoricNames = async () => {
const [brands, instances, vbankAssets] = await Promise.all([
objectFromVstorageEntries('published.agoricNames.brand'),
objectFromVstorageEntries('published.agoricNames.instance'),
objectFromVstorageEntries('published.agoricNames.vbankAsset'),
]);
return { brands, instances, vbankAssets };
};
2 changes: 1 addition & 1 deletion a3p-integration/proposals/p:upgrade-19/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

yarn ava replaceFeeDistributor.test.js
yarn ava upgradedBoard.test.js

yarn ava mintHolder.test.js
yarn ava provisionPool.test.js
yarn ava agoricNames.test.js
Loading

0 comments on commit 059601c

Please sign in to comment.