Skip to content

Commit 5a4fc01

Browse files
authored
feat(fast-usdc): core-eval, builder script (#10477)
closes: #10301 ## Description - feat(fast-usdc): .start.js core-eval w/oracle invitations - the pool share brand is called `FastLP` , at least **UNTIL #10432** - needed contract tweak: STUB! makeOracleInvitation , so perhaps this should wait for #10478 - feat(builders): fast-usdc builder w/CLI config for fees, oracle addresses Also, rather than yet another ad-hoc way of passing terms containing amounts from builders to core-eval, this includes `LegibleCapData`, an approach for... - #7309 ### Security Considerations Oracle invitations are sent do parties identified by addresses, so the usual key management issues apply. In order to reduce "which address is for which operator?" hassle, configuration uses a record, like we do for `voterAddresses`, rather than a plain array as in price feed core-eval code. The core eval produces a `fastUSDCKit` including `privateArgs`; this was prompted by testing difficulties: `instancePrivateArgs` is a heap map, not a remotable, so `EV(instancePrivateArgs).get(instance)` is a no-go. But it also usefully avoids handing out all of `instancePrivateArgs` to any core-eval for upgrading this contract. ### Scaling Considerations The core-eval is O(1), I think. It's O(n) in the number of oracle operators, but that number is fixed by proposal time. ### Documentation Considerations The builder CLI args are lightly documented, in the source: ```js /** @type {ParseArgsConfig['options']} */ const optionsConfig = { contractFee: { type: 'string', default: '0.01' }, poolFee: { type: 'string', default: '0.01' }, oracle: { type: 'string', multiple: true }, }; const oraclesRequiredUsage = 'use --oracle name:address ...'; ``` ### Testing Considerations - [x] test(boot): fast-usdc core eval - needed chore: include noble USDC in orch integration test config - [x] test(boot): restart fastUSDC - [x] unit tests for config marshal ### Upgrade Considerations Restart is a pre-requisite for upgrade, so this is progress on testing upgrade of fastUSDC.
2 parents 5fd303a + 639500f commit 5a4fc01

12 files changed

+687
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
2+
3+
import type { TestFn } from 'ava';
4+
import type { FastUSDCKit } from '@agoric/fast-usdc/src/fast-usdc.start.js';
5+
import { Fail } from '@endo/errors';
6+
import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js';
7+
import { makeMarshal } from '@endo/marshal';
8+
import {
9+
makeWalletFactoryContext,
10+
type WalletFactoryTestContext,
11+
} from '../bootstrapTests/walletFactory.js';
12+
13+
const test: TestFn<WalletFactoryTestContext> = anyTest;
14+
15+
test.before('bootstrap', async t => {
16+
const config = '@agoric/vm-config/decentral-itest-orchestration-config.json';
17+
t.context = await makeWalletFactoryContext(t, config);
18+
});
19+
test.after.always(t => t.context.shutdown?.());
20+
21+
test.serial('oracles provision before contract deployment', async t => {
22+
const { walletFactoryDriver: wd } = t.context;
23+
const watcherWallet = await wd.provideSmartWallet('agoric1watcher1');
24+
t.truthy(watcherWallet);
25+
});
26+
27+
test.serial(
28+
'contract starts; adds to agoricNames; sends invitation',
29+
async t => {
30+
const {
31+
agoricNamesRemotes,
32+
evalProposal,
33+
buildProposal,
34+
refreshAgoricNamesRemotes,
35+
storage,
36+
walletFactoryDriver: wd,
37+
} = t.context;
38+
39+
const watcherWallet = await wd.provideSmartWallet('agoric1watcher1');
40+
41+
const materials = buildProposal(
42+
'@agoric/builders/scripts/fast-usdc/init-fast-usdc.js',
43+
['--oracle', 'a:agoric1watcher1'],
44+
);
45+
await evalProposal(materials);
46+
47+
// update now that fastUsdc is instantiated
48+
refreshAgoricNamesRemotes();
49+
t.truthy(agoricNamesRemotes.instance.fastUsdc);
50+
t.truthy(agoricNamesRemotes.brand.FastLP);
51+
52+
const { EV } = t.context.runUtils;
53+
const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames');
54+
const board = await EV.vat('bootstrap').consumeItem('board');
55+
const getBoardAux = async name => {
56+
const brand = await EV(agoricNames).lookup('brand', name);
57+
const id = await EV(board).getId(brand);
58+
t.truthy(storage.data.get(`published.boardAux.${id}`));
59+
return unmarshalFromVstorage(
60+
storage.data,
61+
`published.boardAux.${id}`,
62+
makeMarshal().fromCapData,
63+
-1,
64+
);
65+
};
66+
t.like(
67+
await getBoardAux('FastLP'),
68+
{
69+
allegedName: 'PoolShares', // misnomer, in some contexts
70+
displayInfo: {
71+
assetKind: 'nat',
72+
decimalPlaces: 6,
73+
},
74+
},
75+
'brand displayInfo available in boardAux',
76+
);
77+
78+
const current = watcherWallet.getCurrentWalletRecord();
79+
80+
// XXX We should be able to compare objects by identity like this:
81+
//
82+
// const invitationPurse = current.purses.find(
83+
// p => p.brand === agoricNamesRemotes.brand.Invitation,
84+
// );
85+
//
86+
// But agoricNamesRemotes and walletFactoryDriver
87+
// don't share a marshal context.
88+
// We should be able to map between them using
89+
// const walletStuff = w.fromCapData(a.toCapData(aStuff))
90+
// but the marshallers don't even preserve identity within themselves.
91+
92+
current.purses.length === 1 || Fail`test limited to 1 purse`;
93+
const [thePurse] = current.purses;
94+
const details = thePurse.balance.value as Array<any>;
95+
Array.isArray(details) || Fail`expected SET value`;
96+
t.is(details.length, 1, 'oracle wallet has 1 invitation');
97+
t.is(details[0].description, 'oracle operator invitation');
98+
// XXX t.is(details.instance, agoricNames.instance.fastUsdc) should work
99+
},
100+
);
101+
102+
test.serial('restart contract', async t => {
103+
const { EV } = t.context.runUtils;
104+
await null;
105+
const kit = await EV.vat('bootstrap').consumeItem('fastUsdcKit');
106+
const actual = await EV(kit.adminFacet).restartContract(kit.privateArgs);
107+
t.deepEqual(actual, { incarnationNumber: 1 });
108+
});

packages/boot/test/orchestration/contract-upgrade.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ test('resume', async t => {
8282

8383
t.deepEqual(getLogged(), [
8484
'sending {0} from cosmoshub to cosmos1whatever',
85-
'got info for denoms: ibc/toyatom, ibc/toyusdc, ubld, uist',
85+
'got info for denoms: ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9, ibc/toyatom, ibc/toyusdc, ubld, uist',
8686
'got info for chain: cosmoshub cosmoshub-4',
8787
'completed transfer to localAccount',
8888
]);
@@ -99,7 +99,7 @@ test('resume', async t => {
9999

100100
t.deepEqual(getLogged(), [
101101
'sending {0} from cosmoshub to cosmos1whatever',
102-
'got info for denoms: ibc/toyatom, ibc/toyusdc, ubld, uist',
102+
'got info for denoms: ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9, ibc/toyatom, ibc/toyusdc, ubld, uist',
103103
'got info for chain: cosmoshub cosmoshub-4',
104104
'completed transfer to localAccount',
105105
'completed transfer to cosmos1whatever',

packages/boot/tools/supports.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,18 @@ import type { SwingsetController } from '@agoric/swingset-vat/src/controller/con
4646
import type { BridgeHandler, IBCMethod } from '@agoric/vats';
4747
import type { BootstrapRootObject } from '@agoric/vats/src/core/lib-boot.js';
4848
import type { EProxy } from '@endo/eventual-send';
49+
import type { FastUSDCCorePowers } from '@agoric/fast-usdc/src/fast-usdc.start.js';
4950
import { icaMocks, protoMsgMockMap, protoMsgMocks } from './ibc/mocks.js';
5051

5152
const trace = makeTracer('BSTSupport', false);
5253

5354
type ConsumeBootrapItem = <N extends string>(
5455
name: N,
55-
) => N extends keyof EconomyBootstrapPowers['consume']
56-
? EconomyBootstrapPowers['consume'][N]
57-
: unknown;
56+
) => N extends keyof FastUSDCCorePowers['consume']
57+
? FastUSDCCorePowers['consume'][N]
58+
: N extends keyof EconomyBootstrapPowers['consume']
59+
? EconomyBootstrapPowers['consume'][N]
60+
: unknown;
5861

5962
// XXX should satisfy EVProxy from run-utils.js but that's failing to import
6063
/**

packages/builders/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"license": "Apache-2.0",
2525
"dependencies": {
2626
"@agoric/ertp": "^0.16.2",
27+
"@agoric/fast-usdc": "0.1.0",
2728
"@agoric/internal": "^0.3.2",
2829
"@agoric/notifier": "^0.6.2",
2930
"@agoric/smart-wallet": "^0.5.3",
@@ -36,6 +37,7 @@
3637
"@endo/far": "^1.1.8",
3738
"@endo/init": "^1.1.6",
3839
"@endo/marshal": "^1.6.1",
40+
"@endo/patterns": "^1.4.6",
3941
"@endo/promise-kit": "^1.1.7",
4042
"@endo/stream": "^1.2.7",
4143
"import-meta-resolve": "^2.2.1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// @ts-check
2+
import { makeHelpers } from '@agoric/deploy-script-support';
3+
import { AmountMath } from '@agoric/ertp';
4+
import {
5+
FastUSDCConfigShape,
6+
getManifestForFastUSDC,
7+
} from '@agoric/fast-usdc/src/fast-usdc.start.js';
8+
import { toExternalConfig } from '@agoric/fast-usdc/src/utils/config-marshal.js';
9+
import { objectMap } from '@agoric/internal';
10+
import {
11+
multiplyBy,
12+
parseRatio,
13+
} from '@agoric/zoe/src/contractSupport/ratio.js';
14+
import { Far } from '@endo/far';
15+
import { parseArgs } from 'node:util';
16+
17+
/**
18+
* @import {CoreEvalBuilder, DeployScriptFunction} from '@agoric/deploy-script-support/src/externalTypes.js'
19+
* @import {ParseArgsConfig} from 'node:util'
20+
* @import {FastUSDCConfig} from '@agoric/fast-usdc/src/fast-usdc.start.js'
21+
*/
22+
23+
/** @type {ParseArgsConfig['options']} */
24+
const options = {
25+
contractFee: { type: 'string', default: '0.01' },
26+
poolFee: { type: 'string', default: '0.01' },
27+
oracle: { type: 'string', multiple: true },
28+
};
29+
const oraclesRequiredUsage = 'use --oracle name:address ...';
30+
/**
31+
* @typedef {{
32+
* contractFee: string;
33+
* poolFee: string;
34+
* oracle?: string[];
35+
* }} FastUSDCOpts
36+
*/
37+
38+
const crossVatContext = /** @type {const} */ ({
39+
/** @type {Brand<'nat'>} */
40+
USDC: Far('USDC Brand'),
41+
});
42+
const { USDC } = crossVatContext;
43+
const USDC_DECIMALS = 6;
44+
const unit = AmountMath.make(USDC, 10n ** BigInt(USDC_DECIMALS));
45+
46+
/** @type {CoreEvalBuilder} */
47+
export const defaultProposalBuilder = async (
48+
{ publishRef, install },
49+
/** @type {FastUSDCConfig} */ config,
50+
) => {
51+
return harden({
52+
sourceSpec: '@agoric/fast-usdc/src/fast-usdc.start.js',
53+
/** @type {[string, Parameters<typeof getManifestForFastUSDC>[1]]} */
54+
getManifestCall: [
55+
getManifestForFastUSDC.name,
56+
{
57+
options: toExternalConfig(config, crossVatContext, FastUSDCConfigShape),
58+
installKeys: {
59+
fastUsdc: publishRef(
60+
install('@agoric/fast-usdc/src/fast-usdc.contract.js'),
61+
),
62+
},
63+
},
64+
],
65+
});
66+
};
67+
68+
/** @type {DeployScriptFunction} */
69+
export default async (homeP, endowments) => {
70+
const { writeCoreEval } = await makeHelpers(homeP, endowments);
71+
const { scriptArgs } = endowments;
72+
73+
/** @type {{ values: FastUSDCOpts }} */
74+
// @ts-expect-error ensured by options
75+
const {
76+
values: { oracle: oracleArgs, ...fees },
77+
} = parseArgs({ args: scriptArgs, options });
78+
79+
const parseOracleArgs = () => {
80+
if (!oracleArgs) throw Error(oraclesRequiredUsage);
81+
return Object.fromEntries(
82+
oracleArgs.map(arg => {
83+
const result = arg.match(/(?<name>[^:]+):(?<address>.+)/);
84+
if (!(result && result.groups)) throw Error(oraclesRequiredUsage);
85+
const { name, address } = result.groups;
86+
return [name, address];
87+
}),
88+
);
89+
};
90+
91+
/** @type {FastUSDCConfig} */
92+
const config = harden({
93+
oracles: parseOracleArgs(),
94+
terms: {
95+
...objectMap(fees, numeral =>
96+
multiplyBy(unit, parseRatio(numeral, USDC)),
97+
),
98+
usdcDenom: 'ibc/usdconagoric',
99+
},
100+
});
101+
102+
await writeCoreEval('start-fast-usdc', utils =>
103+
defaultProposalBuilder(utils, config),
104+
);
105+
};

packages/fast-usdc/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"@endo/patterns": "^1.4.6",
5555
"@endo/promise-kit": "^1.1.7",
5656
"@nick134-bit/noblejs": "0.0.2",
57-
"agoric": "^0.21.1",
5857
"bech32": "^2.0.0",
5958
"commander": "^12.1.0",
6059
"ethers": "^6.13.4"

packages/fast-usdc/src/fast-usdc.contract.js

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { AssetKind } from '@agoric/ertp';
2-
import { BrandShape } from '@agoric/ertp/src/typeGuards.js';
32
import { assertAllDefined, makeTracer } from '@agoric/internal';
43
import { observeIteration, subscribeEach } from '@agoric/notifier';
54
import { withOrchestration } from '@agoric/orchestration';
65
import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js';
76
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
8-
import { M } from '@endo/patterns';
97
import { prepareAdvancer } from './exos/advancer.js';
108
import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js';
119
import { prepareSettler } from './exos/settler.js';
1210
import { prepareStatusManager } from './exos/status-manager.js';
1311
import { prepareTransactionFeedKit } from './exos/transaction-feed.js';
1412
import { defineInertInvitation } from './utils/zoe.js';
13+
import { FastUSDCTermsShape } from './type-guards.js';
1514

1615
const trace = makeTracer('FastUsdc');
1716

@@ -30,13 +29,8 @@ const trace = makeTracer('FastUsdc');
3029
* usdcDenom: Denom;
3130
* }} FastUsdcTerms
3231
*/
33-
const NatAmountShape = { brand: BrandShape, value: M.nat() };
3432
export const meta = {
35-
customTermsShape: {
36-
contractFee: NatAmountShape,
37-
poolFee: NatAmountShape,
38-
usdcDenom: M.string(),
39-
},
33+
customTermsShape: FastUSDCTermsShape,
4034
};
4135
harden(meta);
4236

0 commit comments

Comments
 (0)