-
Notifications
You must be signed in to change notification settings - Fork 214
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
test(multichain): fusdc "advance and settle" as a macro #10692
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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); | ||||||||||||||
|
@@ -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, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
|
@@ -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)) { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a lot going on in this loop. If we reify the oracle operators as objects somewhat like makeOracleOperator or makeAgoricWatcher, we can make this read much more straightforwardly:
Suggested change
|
||||||||||||||
const { offerToUsedInvitation, purses } = await vstorageClient.queryData( | ||||||||||||||
`published.wallet.${wallets[name]}.current`, | ||||||||||||||
); | ||||||||||||||
const { value: invitations } = balancesFromPurses(purses)[Invitation]; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that work?
|
||||||||||||||
const hasInvitation = invitations.some(x => x.description === description); | ||||||||||||||
const usedInvitation = offerToUsedInvitation?.[0]?.[0] === `${name}-accept`; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
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 | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
right? |
||||||||||||||
// 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: {}, | ||||||||||||||
}), | ||||||||||||||
|
@@ -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) => | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
`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( | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. likewise...
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather avoid using the ambient random source inside I can imagine this isn't worthwhile just now, so... adding it to the list: |
||||||||||||||
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( | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, lots going on; my tiny brain wants the roles (user, noble chain, ...) reified as objects. |
||||||||||||||
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) => | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would love to see...
Suggested change
|
||||||||||||||
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, | ||||||||||||||
|
@@ -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)'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have the 10^6 as a constant somewhere but this is clear enough. better than the literal