Skip to content

Commit

Permalink
feat: CosmosChainInfo includes pfmEnabled: bool (#10329)
Browse files Browse the repository at this point in the history
refs: #10006
refs: #10445 

## Description
- adds `pfmEnabled: bool` to `CosmosChainInfo` to support multi-hop forwarding logic
- adds and exports `withChainCapabilities` helper that mixes in `PfmEnabled` and `IcqEnabled` constants to `ChainInfo`
- adds and exports `registerChainsAndAssets` helper that registers info in a `chainHub` in a contract `startFn`
- implements `chainHub` initialization in `fast-usdc` and `send-anywhere` contracts

### Security Considerations
- `chain-capabilities.js` is authoritative, but consumers have the ability to provide their own data. It's not published to vstorage and will be mixed in to local ChainHub's in example contracts that rely on it.

### Scaling Considerations
- Authors must maintain `chain-capabilities.js` over time

### Documentation Considerations
- documented via typedoc

### Testing Considerations
- updates snapshot tests

### Upgrade Considerations
Library code, part of an NPM Orch release
  • Loading branch information
mergify[bot] authored Nov 26, 2024
2 parents 1dd4589 + 118bc6a commit 8657c4c
Show file tree
Hide file tree
Showing 28 changed files with 446 additions and 79 deletions.
2 changes: 1 addition & 1 deletion a3p-integration/proposals/z:acceptance/wallet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test.serial(`send invitation via namesByAddress`, async t => {
});

// FIXME https://github.com/Agoric/agoric-sdk/issues/10565
test.failing('exitOffer tool reclaims stuck payment', async t => {
test.skip('exitOffer tool reclaims stuck payment', async t => {
const istBalanceBefore = await getBalances([GOV1ADDR], 'uist');
t.log('istBalanceBefore', istBalanceBefore);

Expand Down
35 changes: 33 additions & 2 deletions multichain-testing/test/send-anywhere.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,53 @@ import { createWallet } from '../tools/wallet.js';
import { AmountMath } from '@agoric/ertp';
import { makeQueryClient } from '../tools/query.js';
import type { Amount } from '@agoric/ertp/src/types.js';
import chainInfo from '../starship-chain-info.js';
import { denomHash, withChainCapabilities } from '@agoric/orchestration';

const test = anyTest as TestFn<SetupContextWithWallets>;

const accounts = ['osmosis1', 'osmosis2', 'cosmoshub1', 'cosmoshub2'];

const contractName = 'sendAnywhere';
const contractBuilder =
'../packages/builders/scripts/testing/start-send-anywhere.js';
'../packages/builders/scripts/testing/init-send-anywhere.js';

test.before(async t => {
const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t);
deleteTestKeys(accounts).catch();
const wallets = await setupTestKeys(accounts);
t.context = { ...rest, wallets, deleteTestKeys };
const { startContract } = rest;
await startContract(contractName, contractBuilder);

const assetInfo = {
uosmo: {
baseName: 'osmosis',
chainName: 'osmosis',
baseDenom: 'uosmo',
},
[`ibc/${denomHash({ denom: 'uosmo', channelId: chainInfo.agoric.connections['osmosislocal'].transferChannel.channelId })}`]:
{
baseName: 'osmosis',
chainName: 'agoric',
baseDenom: 'uosmo',
},
uatom: {
baseName: 'cosmoshub',
chainName: 'cosmoshub',
baseDenom: 'uatom',
},
[`ibc/${denomHash({ denom: 'uatom', channelId: chainInfo.agoric.connections['gaialocal'].transferChannel.channelId })}`]:
{
baseName: 'cosmoshub',
chainName: 'agoric',
baseDenom: 'uatom',
},
};

await startContract(contractName, contractBuilder, {
chainInfo: JSON.stringify(withChainCapabilities(chainInfo)),
assetInfo: JSON.stringify(assetInfo),
});
});

test.after(async t => {
Expand Down
3 changes: 2 additions & 1 deletion multichain-testing/test/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const commonSetup = async (t: ExecutionContext) => {
const startContract = async (
contractName: string,
contractBuilder: string,
builderOpts?: Record<string, string>,
) => {
const { vstorageClient } = tools;
const instances = Object.fromEntries(
Expand All @@ -98,7 +99,7 @@ export const commonSetup = async (t: ExecutionContext) => {
return t.log('Contract found. Skipping installation...');
}
t.log('bundle and install contract', contractName);
await deployBuilder(contractBuilder);
await deployBuilder(contractBuilder, builderOpts);
await retryUntilCondition(
() => vstorageClient.queryData(`published.agoricNames.instance`),
res => contractName in Object.fromEntries(res),
Expand Down
13 changes: 10 additions & 3 deletions multichain-testing/tools/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { createRequire } from 'module';
import type { AgdTools } from './agd-tools.js';
import type { CoreEvalPlan } from '@agoric/deploy-script-support/src/writeCoreEvalParts.js';
import { flags } from './agd-lib.js';

const nodeRequire = createRequire(import.meta.url);

Expand All @@ -10,10 +11,16 @@ export const makeDeployBuilder = (
readJSON: typeof import('fs-extra').readJSON,
execa: typeof import('execa').execa,
) =>
async function deployBuilder(builder: string) {
async function deployBuilder(
builder: string,
builderOpts?: Record<string, string>,
) {
console.log(`building plan: ${builder}`);
// build the plan
const { stdout } = await execa`agoric run ${builder}`;
const args = ['run', builder];
if (builderOpts) {
args.push(...flags(builderOpts));
}
const { stdout } = await execa('agoric', args);
const match = stdout.match(/ (?<name>[-\w]+)-permit.json/);
if (!(match && match.groups)) {
throw Error('no permit found');
Expand Down
2 changes: 1 addition & 1 deletion packages/boot/test/orchestration/restart-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test.serial('send-anywhere', async t => {

t.log('start send-anywhere');
await evalProposal(
buildProposal('@agoric/builders/scripts/testing/start-send-anywhere.js'),
buildProposal('@agoric/builders/scripts/testing/init-send-anywhere.js'),
);

t.log('making offer');
Expand Down
84 changes: 82 additions & 2 deletions packages/builders/scripts/fast-usdc/init-fast-usdc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
getManifestForFastUSDC,
} from '@agoric/fast-usdc/src/fast-usdc.start.js';
import { toExternalConfig } from '@agoric/fast-usdc/src/utils/config-marshal.js';
import { denomHash, withChainCapabilities } from '@agoric/orchestration';
import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js';
import {
multiplyBy,
parseRatio,
Expand All @@ -17,12 +19,35 @@ import { parseArgs } from 'node:util';
* @import {CoreEvalBuilder, DeployScriptFunction} from '@agoric/deploy-script-support/src/externalTypes.js'
* @import {ParseArgsConfig} from 'node:util'
* @import {FastUSDCConfig} from '@agoric/fast-usdc/src/fast-usdc.start.js'
* @import {Passable} from '@endo/marshal';
* @import {CosmosChainInfo} from '@agoric/orchestration';
*/

const { keys } = Object;

const defaultAssetInfo = {
uusdc: {
baseName: 'noble',
chainName: 'noble',
baseDenom: 'uusdc',
},
[`ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId })}`]:
{
baseName: 'noble',
chainName: 'agoric',
baseDenom: 'uusdc',
brandKey: 'USDC',
},
[`ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.osmosis.connections['noble-1'].transferChannel.channelId })}`]:
{
baseName: 'noble',
chainName: 'osmosis',
baseDenom: 'uusdc',
},
};

/**
* @type {Record<string, Pick<FastUSDCConfig, 'oracles' | 'feedPolicy'>>}
* @type {Record<string, Pick<FastUSDCConfig, 'oracles' | 'feedPolicy' | 'chainInfo' | 'assetInfo' >>}
*
* TODO: determine OCW operator addresses
* meanwhile, use price oracle addresses (from updatePriceFeeds.js).
Expand All @@ -47,6 +72,10 @@ const configurations = {
},
},
},
chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
withChainCapabilities(fetchedChainInfo)
),
assetInfo: defaultAssetInfo,
},
MAINNET: {
oracles: {
Expand All @@ -69,6 +98,10 @@ const configurations = {
},
},
},
chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
withChainCapabilities(fetchedChainInfo)
),
assetInfo: defaultAssetInfo,
},
DEVNET: {
oracles: {
Expand All @@ -90,6 +123,10 @@ const configurations = {
},
},
},
chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
withChainCapabilities(fetchedChainInfo) // TODO: use devnet values
),
assetInfo: defaultAssetInfo, // TODO: use emerynet values
},
EMERYNET: {
oracles: {
Expand All @@ -108,6 +145,10 @@ const configurations = {
},
},
},
chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
withChainCapabilities(fetchedChainInfo) // TODO: use emerynet values
),
assetInfo: defaultAssetInfo, // TODO: use emerynet values
},
};

Expand All @@ -124,11 +165,17 @@ const options = {
default:
'ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9',
},
chainInfo: { type: 'string' },
assetInfo: { type: 'string' },
};
const oraclesUsage = 'use --oracle name:address ...';

const feedPolicyUsage = 'use --feedPolicy <policy> ...';

const chainInfoUsage = 'use --chainInfo chainName:CosmosChainInfo ...';
const assetInfoUsage =
'use --assetInfo denom:DenomInfo & {brandKey?: string} ...';

/**
* @typedef {{
* flatFee: string;
Expand All @@ -139,6 +186,8 @@ const feedPolicyUsage = 'use --feedPolicy <policy> ...';
* oracle?: string[];
* usdcDenom: string;
* feedPolicy?: string;
* chainInfo: string;
* assetInfo: string;
* }} FastUSDCOpts
*/

Expand Down Expand Up @@ -180,7 +229,15 @@ export default async (homeP, endowments) => {
/** @type {{ values: FastUSDCOpts }} */
// @ts-expect-error ensured by options
const {
values: { oracle: oracleArgs, net, usdcDenom, feedPolicy, ...fees },
values: {
oracle: oracleArgs,
net,
usdcDenom,
feedPolicy,
chainInfo,
assetInfo,
...fees
},
} = parseArgs({ args: scriptArgs, options });

const parseFeedPolicy = () => {
Expand Down Expand Up @@ -226,6 +283,27 @@ export default async (homeP, endowments) => {
};
};

const parseChainInfo = () => {
if (net) {
if (!(net in configurations)) {
throw Error(`${net} not in ${keys(configurations)}`);
}
return configurations[net].chainInfo;
}
if (!chainInfo) throw Error(chainInfoUsage);
return JSON.parse(chainInfo);
};
const parseAssetInfo = () => {
if (net) {
if (!(net in configurations)) {
throw Error(`${net} not in ${keys(configurations)}`);
}
return configurations[net].assetInfo;
}
if (!assetInfo) throw Error(assetInfoUsage);
return JSON.parse(assetInfo);
};

/** @type {FastUSDCConfig} */
const config = harden({
oracles: parseOracleArgs(),
Expand All @@ -234,6 +312,8 @@ export default async (homeP, endowments) => {
},
feeConfig: parseFeeConfigArgs(),
feedPolicy: parseFeedPolicy(),
chainInfo: parseChainInfo(),
assetInfo: parseAssetInfo(),
});

await writeCoreEval('start-fast-usdc', utils =>
Expand Down
67 changes: 67 additions & 0 deletions packages/builders/scripts/testing/init-send-anywhere.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { makeHelpers } from '@agoric/deploy-script-support';
import {
getManifest,
startSendAnywhere,
} from '@agoric/orchestration/src/proposals/start-send-anywhere.js';
import { parseArgs } from 'node:util';

/**
* @import {ParseArgsConfig} from 'node:util'
*/

/** @type {ParseArgsConfig['options']} */
const parserOpts = {
chainInfo: { type: 'string' },
assetInfo: { type: 'string' },
};

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
export const defaultProposalBuilder = async (
{ publishRef, install },
options,
) =>
harden({
sourceSpec: '@agoric/orchestration/src/proposals/start-send-anywhere.js',
getManifestCall: [
getManifest.name,
{
installationRef: publishRef(
install(
'@agoric/orchestration/src/examples/send-anywhere.contract.js',
),
),
options,
},
],
});

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */
export default async (homeP, endowments) => {
const { scriptArgs } = endowments;

const {
values: { chainInfo, assetInfo },
} = parseArgs({
args: scriptArgs,
options: parserOpts,
});

const parseChainInfo = () => {
if (typeof chainInfo !== 'string') return undefined;
return JSON.parse(chainInfo);
};
const parseAssetInfo = () => {
if (typeof assetInfo !== 'string') return undefined;
return JSON.parse(assetInfo);
};
const opts = harden({
chainInfo: parseChainInfo(),
assetInfo: parseAssetInfo(),
});

const { writeCoreEval } = await makeHelpers(homeP, endowments);

await writeCoreEval(startSendAnywhere.name, utils =>
defaultProposalBuilder(utils, opts),
);
};
Loading

0 comments on commit 8657c4c

Please sign in to comment.