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

chore: fusdc chain policy limits #10814

Merged
merged 3 commits into from
Jan 11, 2025
Merged
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
2 changes: 1 addition & 1 deletion multichain-testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"lint-fix": "yarn lint:eslint --fix",
"test": "echo 'Run specific test suites:\nyarn test:main (needs `make start`)\nyarn test:fast-usdc (needs `make start FILE=config.fusdc.yaml`)'",
"test:main": "ava --config ava.main.config.js",
"test:fast-usdc": "FILE=config.fusdc.yaml ava --config ava.fusdc.config.js",
"test:fast-usdc": "ava --config ava.fusdc.config.js",
"starship:setup": "make setup-deps setup-kind",
"starship:install": "make install",
"starship:port-forward": "make port-forward",
Expand Down
277 changes: 277 additions & 0 deletions multichain-testing/scripts/fast-usdc-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
#!/usr/bin/env -S node --import ts-blank-space/register
/**
* @file tools for local integration testing for FastUSDC. See USAGE.
*/
import '@endo/init';
import { parseArgs } from 'node:util';
import type { ExecutionContext } from 'ava';
import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
import { AmountMath, type Brand } from '@agoric/ertp';
import type { USDCProposalShapes } from '@agoric/fast-usdc/src/pool-share-math.js';
import type { PoolMetrics } from '@agoric/fast-usdc/src/types.js';
import { divideBy } from '@agoric/zoe/src/contractSupport/ratio.js';
import { makeDenomTools } from '../tools/asset-info.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import { commonSetup } from '../test/support.js';
import {
makeFeedPolicyPartial,
oracleMnemonics,
} from '../test/fast-usdc/config.js';

const USAGE = `
Usage:
No arguments - start the contract and fund the liquidity pool
start - only start the contract
fund-pool - fund the FUSDC Liquidity Pool
provision-wallet - provision a smart wallet (requires --mnemonic)
fund-faucet - fund the faucet account with bridged USDC
register-forwarding - register forwarding account on noble (requires --eud)
--oracle - Comma-separated list of oracle addresses


Examples:
./fast-usdc-tool.ts
./fast-usdc-tool.ts --oracle oracle1:addr1,oracle2:addr2
./fast-usdc-tool.ts start
./fast-usdc-tool.ts start --oracle oracle1:addr1,oracle2:addr2
./fast-usdc-tool.ts fund-pool
./fast-usdc-tool.ts fund-faucet
./fast-usdc-tool.ts register-forwarding --eud osmo123
`;

const contractName = 'fastUsdc';
const contractBuilder =
'../packages/builders/scripts/fast-usdc/init-fast-usdc.js';

/** ava test context partial, to appease dependencies expecting this */
const runT = {
log: console.log,
is: (actual: unknown, expected: unknown, message: string) => {
if (actual !== expected) {
throw new Error(
`Condition: ${message} failed. Expected ${expected} got ${actual}.`,
);
}
},
} as ExecutionContext;

// from ../test/fast-usdc/fast-usdc.test.ts
type VStorageClient = Awaited<ReturnType<typeof commonSetup>>['vstorageClient'];
const agoricNamesQ = (vsc: VStorageClient) =>
harden({
brands: <K extends AssetKind>(_assetKind: K) =>
vsc
.queryData('published.agoricNames.brand')
.then(pairs => Object.fromEntries(pairs) as Record<string, Brand<K>>),
});

const fastLPQ = (vsc: VStorageClient) =>
harden({
metrics: () =>
vsc.queryData(`published.fastUsdc.poolMetrics`) as Promise<PoolMetrics>,
info: () =>
vsc.queryData(`published.${contractName}`) as Promise<{
poolAccount: string;
settlementAccount: string;
}>,
});

const parseCommandLine = () => {
const { values, positionals } = parseArgs({
options: {
eud: {
type: 'string',
},
oracle: {
type: 'string',
},
mnemonic: {
type: 'string',
},
help: {
type: 'boolean',
},
},
allowPositionals: true,
});

if (values.help) {
console.log(USAGE);
return undefined;
}

const command = positionals[0];
const mnemonic = values.mnemonic;
const suppliedOracles = values.oracle?.split(',');
const oracles = suppliedOracles || [
'oracle1:agoric1yupasge4528pgkszg9v328x4faxtkldsnygwjl',
'oracle2:agoric1dh04lnl7epr7l4cpvqqprxvam7ewdswj7yv6ep',
'oracle3:agoric1ujmk0492mauq2f2vrcn7ylq3w3x55k0ap9mt2p',
];
const eud = values.eud;

return { command, eud, mnemonic, oracles, provisionOracles: true };
};

const main = async () => {
const job = parseCommandLine();
if (!job) return undefined;
const { command, eud, mnemonic, oracles, provisionOracles } = job;
const {
chainInfo,
commonBuilderOpts,
deleteTestKeys,
faucetTools,
nobleTools,
provisionSmartWallet,
setupTestKeys,
startContract,
vstorageClient,
} = await commonSetup(runT, { config: '../config.fusdc.yaml' });

const assertProvisioned = async address => {
try {
await vstorageClient.queryData(`published.wallet.${address}.current`);
} catch {
throw new Error(`${address} is not provisioned`);
}
};

const provisionWallet = async (mnemonic: string) => {
// provision-one must be called by the owner, so we need to add the key to the test keyring
const keyname = 'temp';
const address = (await setupTestKeys([keyname], [mnemonic]))[keyname];
try {
await provisionSmartWallet(address, {
BLD: 100n,
IST: 100n,
});
} finally {
await deleteTestKeys([keyname]);
}
};

const start = async () => {
const { getTransferChannelId, toDenomHash } = makeDenomTools(chainInfo);
const usdcDenom = toDenomHash('uusdc', 'noblelocal', 'agoric');
const nobleAgoricChannelId = getTransferChannelId('agoriclocal', 'noble');
if (!nobleAgoricChannelId)
throw new Error('nobleAgoricChannelId not found');
console.log('nobleAgoricChannelId', nobleAgoricChannelId);
console.log('usdcDenom', usdcDenom);

for (const oracle of oracles) {
if (provisionOracles) {
await provisionWallet(oracleMnemonics[oracle.split(':')[0]]);
} else {
console.log(`Confirming ${oracle} smart wallet provisioned...`);
// oracles must be provisioned before the contract starts
await assertProvisioned(oracle.split(':')[1]);
}
}

await startContract(contractName, contractBuilder, {
oracle: oracles,
usdcDenom,
feedPolicy: JSON.stringify(makeFeedPolicyPartial(nobleAgoricChannelId)),
...commonBuilderOpts,
});
};

const fundFaucet = async () => faucetTools.fundFaucet([['noble', 'uusdc']]);

const fundLiquidityPool = async () => {
await fundFaucet();
const accounts = ['lp'];
await deleteTestKeys(accounts).catch();
const wallets = await setupTestKeys(accounts);
const lpUser = await provisionSmartWallet(wallets['lp'], {
USDC: 8_000n,
BLD: 100n,
});
const lpDoOffer = makeDoOffer(lpUser);
const { USDC } = await agoricNamesQ(vstorageClient).brands('nat');
const { shareWorth } = await fastLPQ(vstorageClient).metrics();

const LP_DEPOSIT_AMOUNT = 8_000n * 10n ** 6n;
const give = { USDC: AmountMath.make(USDC as Brand, LP_DEPOSIT_AMOUNT) };
const want = { PoolShare: divideBy(give.USDC, shareWorth) };
const proposal: USDCProposalShapes['deposit'] = harden({ give, want });

await lpDoOffer({
id: `lp-deposit-${Date.now()}`,
invitationSpec: {
source: 'agoricContract',
instancePath: [contractName],
callPipe: [['makeDepositInvitation']],
},
// @ts-expect-error 'NatAmount' vs 'AnyAmount'
proposal,
});
};

const registerForwardingAccount = async (EUD: string) => {
console.log('eud', EUD);
const { settlementAccount } = await vstorageClient.queryData(
`published.${contractName}`,
);
console.log('settlementAccount:', settlementAccount);

const recipientAddress = encodeAddressHook(settlementAccount, {
EUD,
});
console.log('recipientAddress:', recipientAddress);

const { getTransferChannelId } = makeDenomTools(chainInfo);
const nobleAgoricChannelId = getTransferChannelId('agoriclocal', 'noble');
if (!nobleAgoricChannelId)
throw new Error('nobleAgoricChannelId not found');

const txRes = nobleTools.registerForwardingAcct(
nobleAgoricChannelId,
recipientAddress,
);
runT.is(txRes?.code, 0, 'registered forwarding account');

const { address } = nobleTools.queryForwardingAddress(
nobleAgoricChannelId,
recipientAddress,
);
console.log('forwardingAddress:', address);
return address;
};

// Execute commands based on input
switch (command) {
case 'start':
await start();
break;
case 'fund-pool':
await fundLiquidityPool();
break;
case 'fund-faucet':
await fundFaucet();
break;
case 'provision-wallet':
if (!mnemonic) {
throw new Error('--mnemonic is required for provision-wallet command');
}
await provisionWallet(mnemonic);
break;
case 'register-forwarding':
if (!eud) {
throw new Error('--eud is required for register-forwarding command');
}
await registerForwardingAccount(eud);
break;
default:
// No command provided - run both start and fundLiquidityPool
await start();
await fundLiquidityPool();
}
};

main().catch(error => {
console.error('An error occurred:', error);
process.exit(1);
});
17 changes: 6 additions & 11 deletions multichain-testing/test/fast-usdc/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IBCChannelID } from '@agoric/vats';
import type { FeedPolicy } from '@agoric/fast-usdc/src/types.js';

export const oracleMnemonics = {
oracle1:
Expand All @@ -10,19 +11,13 @@ export const oracleMnemonics = {
};
harden(oracleMnemonics);

export const makeFeedPolicy = (nobleAgoricChannelId: IBCChannelID) => {
export const makeFeedPolicyPartial = (
nobleAgoricChannelId: IBCChannelID,
): Omit<FeedPolicy, 'chainPolicies'> => {
// XXX consider using toExternalConfig to marshal bigints and send ChainPolicies
return {
nobleAgoricChannelId,
nobleDomainId: 4,
chainPolicies: {
Arbitrum: {
attenuatedCttpBridgeAddress:
'0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
chainId: 42161,
confirmations: 2,
},
},
};
};
harden(makeFeedPolicy);
harden(makeFeedPolicyPartial);
8 changes: 5 additions & 3 deletions multichain-testing/test/fast-usdc/fast-usdc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { makeDenomTools } from '../../tools/asset-info.js';
import { createWallet } from '../../tools/wallet.js';
import { makeQueryClient } from '../../tools/query.js';
import { commonSetup, type SetupContextWithWallets } from '../support.js';
import { makeFeedPolicy, oracleMnemonics } from './config.js';
import { makeFeedPolicyPartial, oracleMnemonics } from './config.js';
import { makeRandomDigits } from '../../tools/random.js';
import { makeTracer } from '@agoric/internal';
import type {
Expand Down Expand Up @@ -48,7 +48,9 @@ const contractBuilder =
const LP_DEPOSIT_AMOUNT = 8_000n * 10n ** 6n;

test.before(async t => {
const { setupTestKeys, ...common } = await commonSetup(t);
const { setupTestKeys, ...common } = await commonSetup(t, {
config: '../config.fusdc.yaml',
});
const {
chainInfo,
commonBuilderOpts,
Expand Down Expand Up @@ -81,7 +83,7 @@ test.before(async t => {
await startContract(contractName, contractBuilder, {
oracle: keys(oracleMnemonics).map(n => `${n}:${wallets[n]}`),
usdcDenom,
feedPolicy: JSON.stringify(makeFeedPolicy(nobleAgoricChannelId)),
feedPolicy: JSON.stringify(makeFeedPolicyPartial(nobleAgoricChannelId)),
...commonBuilderOpts,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const test = anyTest as TestFn<SetupContext>;

test('noble forwarding', async t => {
const { nobleTools, retryUntilCondition, useChain, vstorageClient } =
await commonSetup(t);
await commonSetup(t, { config: '../config.fusdc.yaml' });

const agoricWallet = await createWallet('agoric');
const agoricAddr = (await agoricWallet.getAccounts())[0].address;
Expand Down
7 changes: 5 additions & 2 deletions multichain-testing/test/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@ const makeKeyring = async (
return { setupTestKeys, deleteTestKeys };
};

export const commonSetup = async (t: ExecutionContext) => {
export const commonSetup = async (
t: ExecutionContext,
{ config = '../config.yaml' } = {},
) => {
let useChain: MultichainRegistry['useChain'];
try {
const registry = await setupRegistry({
config: `../${process.env.FILE || 'config.yaml'}`,
config,
});
useChain = registry.useChain;
} catch (e) {
Expand Down
Loading
Loading