Skip to content

Commit

Permalink
test(multichain): fusdc "advance and settle" as a macro (#10692)
Browse files Browse the repository at this point in the history
refs: #10597

## Description
- tests additional FUSDC happy paths with agoric and noble EUDs
- "oracles accept" test conditionally accepts invitations

### Security Considerations
None, only tests

### Scaling Considerations
None, ~60s hit to CI (each advance + settle is about 30s)

### Documentation Considerations
None

### Testing Considerations
PR is only tests

### Upgrade Considerations
None
  • Loading branch information
mergify[bot] authored Dec 17, 2024
2 parents 5939b2a + 4ce1540 commit 9e124c3
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 115 deletions.
5 changes: 4 additions & 1 deletion a3p-integration/proposals/z:acceptance/governance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ const governanceAddresses = [GOV4ADDR, GOV2ADDR, GOV1ADDR];
const delay = ms =>
new Promise(resolve => setTimeout(() => resolve(undefined), ms));

test.serial(
const testSkipXXX = test.skip; // same lenth as test.serial to avoid reformatting all lines

// z:acceptance governance fails/flakes: No quorum #10708
testSkipXXX(
'economic committee can make governance proposal and vote on it',
async t => {
const { waitUntil } = makeTimerUtils({ setTimeout });
Expand Down
259 changes: 145 additions & 114 deletions multichain-testing/test/fast-usdc/fast-usdc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const accounts = [...keys(oracleMnemonics), 'lp'];
const contractName = 'fastUsdc';
const contractBuilder =
'../packages/builders/scripts/fast-usdc/init-fast-usdc.js';
const LP_DEPOSIT_AMOUNT = 10_000_000n;
const LP_DEPOSIT_AMOUNT = 8_000n * 10n ** 6n;

test.before(async t => {
const { setupTestKeys, ...common } = await commonSetup(t);
Expand Down Expand Up @@ -79,7 +79,7 @@ test.before(async t => {

// save an LP in test context
const lpUser = await provisionSmartWallet(wallets['lp'], {
USDC: 100n,
USDC: 8_000n,
BLD: 100n,
});

Expand All @@ -103,21 +103,41 @@ const toOracleOfferId = (idx: number) => `oracle${idx + 1}-accept`;

test.serial('oracles accept', async t => {
const { oracleWds, retryUntilCondition, vstorageClient, wallets } = t.context;
const brands = await vstorageClient.queryData('published.agoricNames.brand');
const { Invitation } = Object.fromEntries(brands);

const instances = await vstorageClient.queryData(
'published.agoricNames.instance',
);
const instance = fromEntries(instances)[contractName];
const description = 'oracle operator invitation';

// ensure we have an unused (or used) oracle invitation in each purse
let hasAccepted = false;
for (const name of keys(oracleMnemonics)) {
const { offerToUsedInvitation, purses } = await vstorageClient.queryData(
`published.wallet.${wallets[name]}.current`,
);
const { value: invitations } = balancesFromPurses(purses)[Invitation];
const hasInvitation = invitations.some(x => x.description === description);
const usedInvitation = offerToUsedInvitation?.[0]?.[0] === `${name}-accept`;
t.log({ name, hasInvitation, usedInvitation });
t.true(hasInvitation || usedInvitation, 'has or accepted invitation');
if (usedInvitation) hasAccepted = true;
}
// if the oracles have already accepted, skip the rest of the test this is
// primarily to facilitate active development but could support testing on
// images where operator invs are already accepted
if (hasAccepted) return t.pass();

// accept oracle operator invitations
const instance = fromEntries(
await vstorageClient.queryData('published.agoricNames.instance'),
)[contractName];
await Promise.all(
oracleWds.map(makeDoOffer).map((doOffer, i) =>
doOffer({
id: toOracleOfferId(i),
invitationSpec: {
source: 'purse',
instance,
description: 'oracle operator invitation', // TODO export/import INVITATION_MAKERS_DESC
description,
},
proposal: {},
}),
Expand Down Expand Up @@ -187,129 +207,136 @@ test.serial('lp deposits', async t => {
);
});

test.serial('advance and settlement', async t => {
const {
nobleTools,
nobleAgoricChannelId,
oracleWds,
retryUntilCondition,
useChain,
usdcOnOsmosis,
vstorageClient,
} = t.context;

// EUD wallet on osmosis
const eudWallet = await createWallet(useChain('osmosis').chain.bech32_prefix);
const EUD = (await eudWallet.getAccounts())[0].address;

// parameterize agoric address
const { settlementAccount } = await vstorageClient.queryData(
`published.${contractName}`,
);
t.log('settlementAccount address', settlementAccount);
const advanceAndSettleScenario = test.macro({
title: (_, mintAmt: bigint, eudChain: string) =>
`advance ${mintAmt} uusdc to ${eudChain} and settle`,
exec: async (t, mintAmt: bigint, eudChain: string) => {
const {
nobleTools,
nobleAgoricChannelId,
oracleWds,
retryUntilCondition,
useChain,
usdcOnOsmosis,
vstorageClient,
} = t.context;

// EUD wallet on the specified chain
const eudWallet = await createWallet(
useChain(eudChain).chain.bech32_prefix,
);
const EUD = (await eudWallet.getAccounts())[0].address;
t.log(`EUD wallet created: ${EUD}`);

const recipientAddress = encodeAddressHook(settlementAccount, { EUD });
t.log('recipientAddress', recipientAddress);
// parameterize agoric address
const { settlementAccount } = await vstorageClient.queryData(
`published.${contractName}`,
);
t.log('settlementAccount address', settlementAccount);

// register forwarding address on noble
const txRes = nobleTools.registerForwardingAcct(
nobleAgoricChannelId,
recipientAddress,
);
t.is(txRes?.code, 0, 'registered forwarding account');
const recipientAddress = encodeAddressHook(settlementAccount, { EUD });
t.log('recipientAddress', recipientAddress);

const { address: userForwardingAddr } = nobleTools.queryForwardingAddress(
nobleAgoricChannelId,
recipientAddress,
);
t.log('got forwardingAddress', userForwardingAddr);

const mintAmount = 800_000n;

// TODO export CctpTxEvidence type
const evidence = harden({
blockHash:
'0x90d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee665',
blockNumber: 21037663n,
txHash: `0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617${makeRandomDigits(makeRandomNumber(), 2n)}`,
tx: {
amount: mintAmount,
forwardingAddress: userForwardingAddr,
},
aux: {
forwardingChannel: nobleAgoricChannelId,
// register forwarding address on noble
const txRes = nobleTools.registerForwardingAcct(
nobleAgoricChannelId,
recipientAddress,
},
chainId: 42161,
});

console.log('User initiates evm mint', evidence.txHash);

// submit evidences
await Promise.all(
oracleWds.map(makeDoOffer).map((doOffer, i) =>
doOffer({
id: `${Date.now()}-evm-evidence`,
invitationSpec: {
source: 'continuing',
previousOffer: toOracleOfferId(i),
invitationMakerName: 'SubmitEvidence',
invitationArgs: [evidence],
},
proposal: {},
}),
),
);

const queryClient = makeQueryClient(
await useChain('osmosis').getRestEndpoint(),
);
);
t.is(txRes?.code, 0, 'registered forwarding account');

await t.notThrowsAsync(() =>
retryUntilCondition(
() => queryClient.queryBalance(EUD, usdcOnOsmosis),
({ balance }) => !!balance?.amount && BigInt(balance.amount) < mintAmount,
`${EUD} advance available from fast-usdc`,
{
// this resolves quickly, so _decrease_ the interval so the timing is more apparent
retryIntervalMs: 500,
const { address: userForwardingAddr } = nobleTools.queryForwardingAddress(
nobleAgoricChannelId,
recipientAddress,
);
t.log('got forwardingAddress', userForwardingAddr);

// TODO export CctpTxEvidence type
const evidence = harden({
blockHash:
'0x90d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee665',
blockNumber: 21037663n,
txHash: `0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617${makeRandomDigits(makeRandomNumber(), 2n)}`,
tx: {
amount: mintAmt,
forwardingAddress: userForwardingAddr,
},
),
);
aux: {
forwardingChannel: nobleAgoricChannelId,
recipientAddress,
},
chainId: 42161,
});

console.log('User initiates evm mint:', evidence.txHash);

// submit evidences
await Promise.all(
oracleWds.map(makeDoOffer).map((doOffer, i) =>
doOffer({
id: `${Date.now()}-evm-evidence`,
invitationSpec: {
source: 'continuing',
previousOffer: toOracleOfferId(i),
invitationMakerName: 'SubmitEvidence',
invitationArgs: [evidence],
},
proposal: {},
}),
),
);

const queryTxStatus = async () =>
vstorageClient.queryData(
`published.${contractName}.status.${evidence.txHash}`,
const queryClient = makeQueryClient(
await useChain(eudChain).getRestEndpoint(),
);

const assertTxStatus = async (status: string) =>
t.notThrowsAsync(() =>
await t.notThrowsAsync(() =>
retryUntilCondition(
() => queryTxStatus(),
txStatus => {
console.log('tx status', txStatus);
return txStatus === status;
},
`${evidence.txHash} is ${status}`,
() => queryClient.queryBalance(EUD, usdcOnOsmosis),
({ balance }) => !!balance?.amount && BigInt(balance.amount) < mintAmt,
`${EUD} advance available from fast-usdc`,
// this resolves quickly, so _decrease_ the interval so the timing is more apparent
{ retryIntervalMs: 500 },
),
);

await assertTxStatus('ADVANCED');
console.log('Advance completed, waiting for mint...');

nobleTools.mockCctpMint(mintAmount, userForwardingAddr);
await t.notThrowsAsync(() =>
retryUntilCondition(
() => vstorageClient.queryData(`published.${contractName}.poolMetrics`),
({ encumberedBalance }) =>
encumberedBalance && isEmpty(encumberedBalance),
'encumberedBalance returns to 0',
),
);
const queryTxStatus = async () =>
vstorageClient.queryData(
`published.${contractName}.status.${evidence.txHash}`,
);

const assertTxStatus = async (status: string) =>
t.notThrowsAsync(() =>
retryUntilCondition(
() => queryTxStatus(),
txStatus => {
console.log('tx status', txStatus);
return txStatus === status;
},
`${evidence.txHash} is ${status}`,
),
);

await assertTxStatus('ADVANCED');
console.log('Advance completed, waiting for mint...');

nobleTools.mockCctpMint(mintAmt, userForwardingAddr);
await t.notThrowsAsync(() =>
retryUntilCondition(
() => vstorageClient.queryData(`published.${contractName}.poolMetrics`),
({ encumberedBalance }) =>
encumberedBalance && isEmpty(encumberedBalance),
'encumberedBalance returns to 0',
),
);

await assertTxStatus('DISBURSED');
await assertTxStatus('DISBURSED');
},
});

test.serial(advanceAndSettleScenario, LP_DEPOSIT_AMOUNT / 4n, 'osmosis');
test.serial(advanceAndSettleScenario, LP_DEPOSIT_AMOUNT / 8n, 'noble');
test.serial(advanceAndSettleScenario, LP_DEPOSIT_AMOUNT / 5n, 'agoric');

test.serial('lp withdraws', async t => {
const {
lpUser,
Expand Down Expand Up @@ -380,3 +407,7 @@ test.serial('lp withdraws', async t => {
),
);
});

test.todo('insufficient LP funds; forward path');
test.todo('mint while Advancing; still Disbursed');
test.todo('transfer failed (e.g. to cosmos, not in env)');

0 comments on commit 9e124c3

Please sign in to comment.