Skip to content

Commit

Permalink
chore(vat-upgrade): upgrade agoricNames, test old values are preserved (
Browse files Browse the repository at this point in the history
#10616)

closes: #10408

## Description

As stated in #8158, we need to be able to upgrade any vat we want and make sure the system keeps working. This PR focuses on upgrading `vat-agoricNames`.

### Security Considerations

I consider `vat-agoricNames` as a critical vat as it's the source trust for object identities. So it's crucial it keeps working after the upgrade. It's important to point out that if there were any keywords reserved before the upgrade, they will be lost after the upgrade as the reservations stored in ephemera. If this is important, we might consider re-reserving in the core eval.

### Scaling Considerations

None.

### Documentation Considerations

Don't think it's necessary as the functionality of `nameHub` is left untouched. 

### Testing Considerations

If we decide to re-reserve some keywords, we will need to add a test that verifies the core eval has indeed reserved those keywords. Please see below for the test plan I followed:

https://github.com/Agoric/agoric-sdk/blob/1e4bebac1e3ee6df48dbe58e3e5bdf0e9e37cca2/a3p-integration/proposals/p%3Aupgrade-19/agoricNames.test.js#L3-L46

#### EDIT
2195ace removes ` write-chain-info.js` from `f:fast-usdc`. So we simulate what is does (relative to `agoricNames`) in order to test ephemeral `onUpdate` callbacks keep working after an `agoricNames` upgrade.


### Upgrade Considerations

This PR itself upgrade `vat-agoricNames` and comes with a3p tests to verify the upgrade goes through and `agoricNames` keep working.
  • Loading branch information
mergify[bot] authored Dec 9, 2024
2 parents 74ab972 + cd35800 commit 86e3b2d
Show file tree
Hide file tree
Showing 19 changed files with 2,212 additions and 30 deletions.
3 changes: 3 additions & 0 deletions a3p-integration/proposals/p:upgrade-19/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
replaceFeeDistributor/
testUpgradedBoard/
addUsdLemons/
addUsdOlives/
upgradeProvisionPool/
upgradeAgoricNames/
publishTestInfo/
236 changes: 236 additions & 0 deletions a3p-integration/proposals/p:upgrade-19/agoricNames.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/* eslint-env node */

/**
* @file The goal of this file is to test different aspects of agoricNames to make sure
* everything works after an upgrade. Here's the test plan;
* 1. publish a new node called 'testInfo' under agoricNames
* CONTEXT: onUpdate callback of testInfo nameAdmin is registered in a core-eval. Which means it is
* both ephemeral and lives in bootstrap vat. We create a scenario like this to make sure any ephemeral
* onUpdate keeps working after an agoricNames upgrade.
* 2. upgrade agoricNames
* 3. send a core-eval that writes into children of agoricNames (brand, issuer, instance...)
* 3b. expect a child nameHub of agoricNames will publish ALL its entries when a new item is written to it
* 3c. check the values in the vstorage match before and after the upgrade
* 3d. also check that new items are in the vstorage as well
* 4. append new chain
* CONTEXT: there are two new children introduced to agoricNames by orchestration and their
* onUpdate callback isn't durable. So we must check that if we write a new chain info to those child
* nameHubs, we should observe the new value in vstorage.
* 4b. send a core-eval that writes new chain info to published.agoricNames.chain and published.agoricNames.chainConnection
* 4c. wait until the expected data observed in vstorage
*
*
* TESTING CODE THAT HOLDS ONTO 'agoricNames': smartWallet is one of the vats that depend on agoricNames to work properly the most.
* smartWallet uses agoricNames to;
* - create purses for known brands
* - looks for the brand in agoricNames.brand
* - creates a purse for the brand using the issuer in agoricNames.issuer
* - create invitations for a from the publicFacet of a given instance (agoricNames.instance)
*
* So the fact that a user can complete an offer successfully means;
* - smartWallet can find the instance on agoricNames.instance (for invitationSource = 'agoricContract')
* - smartWallet can find, if not present create, a purse for known brand, agoricNames.brand
* and agoricNames.issuer returned correct values
*
*
* 5. add a new PSM and swap against it
* 5b. adding the new PSM requires introducing a new asset to the chain and writing
* the PSM instance to agoricNames.instance
* 5c. being able to deposit the new asset to a user means that smartWallet created a purse
* for the new brand
* 5d. being able to send the offer to the PSM instance means smartWallet can find the instance
* in agoricNames.instance
*
* 6. we want to make sure objects that were already in agoricNames works as well, so open a vault
* in an existing collateralManager
* 6a. fund GOV1 with ATOM
* 6b. open a vault
* 6c. check the vault is opened successfully
*
*/

import '@endo/init';
import test from 'ava';
import {
agoric,
ATOM_DENOM,
evalBundles,
getIncarnation,
GOV1ADDR,
openVault,
} from '@agoric/synthetic-chain';
import { makeVstorageKit, retryUntilCondition } from '@agoric/client-utils';
import {
bankSend,
extractBalance,
psmSwap,
tryISTBalances,
} from './test-lib/psm-lib.js';
import { getBalances, listVaults } from './test-lib/utils.js';
import { walletUtils } from './test-lib/index.js';

const AGORIC_NAMES_UPGRADE_DIR = 'agoricNamesCoreEvals/upgradeAgoricNames';
const WRITE_AGORIC_NAMES_DIR = 'agoricNamesCoreEvals/writeToAgoricNames';
const ADD_USD_OLIVES_DIR = 'agoricNamesCoreEvals/addUsdOlives';
const DEPOSIT_USD_OLIVES_DIR = 'agoricNamesCoreEvals/depositUsdOlives';
const PUBLISH_TEST_INFO_DIR = 'agoricNamesCoreEvals/publishTestInfo';
const WRITE_TEST_INFO_DIR = 'agoricNamesCoreEvals/writeToTestInfo';

const makeWaitUntilKeyFound = (keyFinder, vstorage) => (path, targetKey) =>
retryUntilCondition(
() => vstorage.keys(path),
keys => keyFinder(keys, targetKey),
'Key not found.',
{ maxRetries: 5, retryIntervalMs: 2000, log: console.log, setTimeout },
);

test.before(async t => {
const vstorageKit = await makeVstorageKit(
{ fetch },
{ rpcAddrs: ['http://localhost:26657'], chainName: 'agoriclocal' },
);

t.context = {
vstorageKit,
};
});

test.serial('publish test info', async t => {
// @ts-expect-error casting
const { vstorageKit } = t.context;

const waitUntilKeyFound = makeWaitUntilKeyFound(
(keys, targetKey) => keys.includes(targetKey),
vstorageKit.vstorage,
);

await evalBundles(PUBLISH_TEST_INFO_DIR);
await waitUntilKeyFound('published.agoricNames', 'testInfo');

const testInfo = await vstorageKit.readLatestHead(
'published.agoricNames.testInfo',
);
t.deepEqual(Object.fromEntries(testInfo), {
agoric: {
isAwesome: 'yes',
tech: ['HardenedJs', 'Orchestration', 'Async_Execution'],
},
});
});

test.serial('upgrade agoricNames', async t => {
await evalBundles(AGORIC_NAMES_UPGRADE_DIR);

const incarnation = await getIncarnation('agoricNames');
t.is(incarnation, 1, 'incorrect incarnation');
});

test.serial('check all existing values are preserved', async t => {
// @ts-expect-error casting
const { vstorageKit } = t.context;
const agoricNamesChildren = [
'brand',
'installation',
'instance',
'issuer',
'oracleBrand',
'vbankAsset',
];

const getAgoricNames = () =>
Promise.all(
agoricNamesChildren.map(async child => {
const content = await vstorageKit.readLatestHead(
`published.agoricNames.${child}`,
);
return [child, Object.fromEntries(content)];
}),
).then(rawAgoricNames => Object.fromEntries(rawAgoricNames));

const agoricNamesBefore = await getAgoricNames();
console.log('AGORIC_NAMES_BEFORE', agoricNamesBefore);

await evalBundles(WRITE_AGORIC_NAMES_DIR);

const agoricNamesAfter = await getAgoricNames();
t.like(agoricNamesAfter, agoricNamesBefore);

agoricNamesChildren.forEach(child =>

Check warning on line 158 in a3p-integration/proposals/p:upgrade-19/agoricNames.test.js

View workflow job for this annotation

GitHub Actions / lint-rest

Prefer for...of instead of Array.forEach

Check warning on line 158 in a3p-integration/proposals/p:upgrade-19/agoricNames.test.js

View workflow job for this annotation

GitHub Actions / lint-rest

Prefer for...of instead of Array.forEach
assert(
agoricNamesAfter[child][`test${child}`],
'we should be able to add new value',
),
);
});

test.serial('check testInfo still works', async t => {
// @ts-expect-error casting
const { vstorageKit } = t.context;
await evalBundles(WRITE_TEST_INFO_DIR);

const testInfo = await vstorageKit.readLatestHead(
'published.agoricNames.testInfo',
);
t.deepEqual(Object.fromEntries(testInfo), {
agoric: {
isAwesome: 'yes',
tech: ['HardenedJs', 'Orchestration', 'Async_Execution'],
},
ethereum: {
isAwesome: 'yes',
tech: ['Solidity', 'EVM'],
},
});
});

test.serial('check contracts depend on agoricNames are not broken', async t => {
await evalBundles(ADD_USD_OLIVES_DIR);
await evalBundles(DEPOSIT_USD_OLIVES_DIR);

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

const balancesBefore = await getBalances([GOV1ADDR]);

await psmSwap(
GOV1ADDR,
['swap', '--pair', 'IST.USD_OLIVES', '--wantMinted', 1],
psmSwapIo,
);

const balancesAfter = await getBalances([GOV1ADDR]);
await tryISTBalances(
t,
extractBalance(balancesAfter, 'uist'),
extractBalance(balancesBefore, 'uist') + 1000000, // in uist
);
});

test.serial('open a vault', async t => {
await bankSend(GOV1ADDR, `200000000000000000${ATOM_DENOM}`);
const istBalanceBefore = await getBalances([GOV1ADDR]);
const activeVaultsBefore = await listVaults(GOV1ADDR, walletUtils);

const mint = '5.0';
const collateral = '10.0';
await openVault(GOV1ADDR, mint, collateral);

const istBalanceAfter = await getBalances([GOV1ADDR]);
const activeVaultsAfter = await listVaults(GOV1ADDR, walletUtils);

await tryISTBalances(
t,
extractBalance(istBalanceAfter, 'uist'),
extractBalance(istBalanceBefore, 'uist') + 5000000,
);

t.is(
activeVaultsAfter.length,
activeVaultsBefore.length + 1,
`The number of active vaults should increase after opening a new vault.`,
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"consume": {
"contractKits": true,
"namesByAddressAdmin": true,
"agoricNames": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @ts-nocheck
/* eslint-disable no-undef */
const GOV_ONE_ADDR = 'agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q';

const depositUsdOlives = async powers => {
const {
consume: {
contractKits: contractKitsP,
namesByAddressAdmin: namesByAddressAdminP,
agoricNames,
},
} = powers;

const namesByAddressAdmin = await namesByAddressAdminP;

const getDepositFacet = async address => {
const hub = E(E(namesByAddressAdmin).lookupAdmin(address)).readonly();
return E(hub).lookup('depositFacet');
};

const [contractKits, usdOlivesIssuer, usdOlivesBrand, ppDepositFacet] =
await Promise.all([
contractKitsP,
E(agoricNames).lookup('issuer', 'USD_OLIVES'),
E(agoricNames).lookup('brand', 'USD_OLIVES'),
getDepositFacet(GOV_ONE_ADDR),
]);

console.log('[CONTRACT_KITS]', contractKits);
console.log('[ISSUER]', usdOlivesIssuer);

let usdOlivesMint;
for (const { publicFacet, creatorFacet: mint } of contractKits.values()) {
if (publicFacet === usdOlivesIssuer) {
usdOlivesMint = mint;
console.log('BINGO', mint);
break;
}
}

console.log('Minting USD_OLIVES');
const helloPayment = await E(usdOlivesMint).mintPayment(
harden({ brand: usdOlivesBrand, value: 1_000_000n }),
);

console.log('Funding provision pool...');
await E(ppDepositFacet).receive(helloPayment);

console.log('Done.');
};

depositUsdOlives;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"consume": {
"agoricNamesAdmin": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-nocheck
/* eslint-disable no-undef */
const writeToAgoricNames = async powers => {
const {
consume: { agoricNamesAdmin },
} = powers;

console.log('writing to agoricNames...');
const agoricNamesChildren = [
'brand',
'installation',
'instance',
'issuer',
'oracleBrand',
'vbankAsset',
];

await Promise.all(
agoricNamesChildren.map(async (child, index) =>
E(E(agoricNamesAdmin).lookupAdmin(child)).update(
`test${child}`,
Far(`test${child}`, { getBoardId: () => `board${index}` }),
),
),
);

console.log('DONE');
};

writeToAgoricNames;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"consume": {
"agoricNamesAdmin": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @ts-nocheck
/* eslint-disable no-undef */
const writeToTestInfo = async powers => {
const {
consume: { agoricNamesAdmin },
} = powers;

console.log('writing to testInfo...');

E(E(agoricNamesAdmin).lookupAdmin('testInfo')).update('ethereum', {
isAwesome: 'yes',
tech: ['Solidity', 'EVM'],
});

console.log('DONE');
};

writeToTestInfo;
6 changes: 5 additions & 1 deletion a3p-integration/proposals/p:upgrade-19/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"vats/upgrade-provisionPool.js upgradeProvisionPool",
"vats/upgrade-paRegistry.js",
"vats/upgrade-board.js",
"testing/test-upgraded-board.js testUpgradedBoard"
"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"
]
},
"type": "module",
Expand All @@ -21,6 +24,7 @@
"@endo/errors": "1.2.7",
"@endo/init": "^1.1.5",
"@endo/marshal": "^1.5.4",
"agoric": "dev",
"ava": "^5.3.1",
"better-sqlite3": "^9.6.0",
"execa": "9.1.0"
Expand Down
Loading

0 comments on commit 86e3b2d

Please sign in to comment.