-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:
cosmosOrchAccount.getBalances()
(#10004)
refs: #9610 ## Description - adds `.getBalances()` method to `cosmos-orchestration-accounts.js` - adds e2e tests of `CosmosOrchAccount` `getBalance()` and `getBalances()` in `multichain-testing` - moves `*query` invitations used for testing out of `src/examples/basic-flows` contract into its own contract stored in `src/fixtures/query-flows.contract.js` ### Security Considerations n/a, using existing powers ### Scaling Considerations n/a ### Documentation Considerations n/a ### Testing Considerations Includes unit tests with high fidelity mocks and e2e tests. ### Upgrade Considerations n/a, unreleased code
- Loading branch information
Showing
18 changed files
with
963 additions
and
350 deletions.
There are no files selected for viewing
190 changes: 190 additions & 0 deletions
190
multichain-testing/test/account-balance-queries.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import anyTest from '@endo/ses-ava/prepare-endo.js'; | ||
import type { TestFn } from 'ava'; | ||
import type { CosmosChainInfo } from '@agoric/orchestration'; | ||
import { | ||
commonSetup, | ||
SetupContextWithWallets, | ||
chainConfig, | ||
} from './support.js'; | ||
import { makeDoOffer } from '../tools/e2e-tools.js'; | ||
import chainInfo from '../starship-chain-info.js'; | ||
import { MAKE_ACCOUNT_AND_QUERY_BALANCE_TIMEOUT } from './config.js'; | ||
|
||
const test = anyTest as TestFn<SetupContextWithWallets>; | ||
|
||
const accounts = ['osmosis', 'cosmoshub', 'agoric']; | ||
|
||
const contractName = 'queryFlows'; | ||
const contractBuilder = | ||
'../packages/builders/scripts/testing/start-query-flows.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); | ||
}); | ||
|
||
test.after(async t => { | ||
const { deleteTestKeys } = t.context; | ||
deleteTestKeys(accounts); | ||
}); | ||
|
||
const queryAccountBalances = test.macro({ | ||
title: (_, chainName: string) => `Query Account Balances on ${chainName}`, | ||
exec: async (t, chainName: string) => { | ||
const config = chainConfig[chainName]; | ||
if (!config) return t.fail(`Unknown chain: ${chainName}`); | ||
const { | ||
wallets, | ||
provisionSmartWallet, | ||
vstorageClient, | ||
retryUntilCondition, | ||
} = t.context; | ||
|
||
const agoricAddr = wallets[chainName]; | ||
const wdUser1 = await provisionSmartWallet(agoricAddr, { | ||
BLD: 100n, | ||
IST: 100n, | ||
}); | ||
t.log(`provisioning agoric smart wallet for ${agoricAddr}`); | ||
|
||
const doOffer = makeDoOffer(wdUser1); | ||
t.log(`${chainName} makeAccountAndGetBalancesQuery offer`); | ||
const offerId = `${chainName}-makeAccountAndGetBalancesQuery-${Date.now()}`; | ||
|
||
await doOffer({ | ||
id: offerId, | ||
invitationSpec: { | ||
source: 'agoricContract', | ||
instancePath: [contractName], | ||
callPipe: [['makeAccountAndGetBalancesQueryInvitation']], | ||
}, | ||
offerArgs: { chainName }, | ||
proposal: {}, | ||
}); | ||
|
||
const offerResult = await retryUntilCondition( | ||
() => vstorageClient.queryData(`published.wallet.${agoricAddr}`), | ||
({ status }) => status.id === offerId && (status.result || status.error), | ||
`${offerId} offer result is in vstorage`, | ||
MAKE_ACCOUNT_AND_QUERY_BALANCE_TIMEOUT, | ||
); | ||
t.log('Account Balances Query Offer Result', offerResult); | ||
|
||
const { icqEnabled } = (chainInfo as Record<string, CosmosChainInfo>)[ | ||
chainName | ||
]; | ||
t.log( | ||
icqEnabled | ||
? 'ICQ Enabled expecting offer result.' | ||
: 'ICQ Disabled expecting offer error', | ||
); | ||
|
||
const { | ||
status: { result, error }, | ||
} = offerResult; | ||
if (icqEnabled) { | ||
t.is(error, undefined, 'No error observed for supported chain'); | ||
const balances = JSON.parse(result); | ||
t.truthy(balances, 'Result is parsed successfully'); | ||
t.true(Array.isArray(balances), 'Balances is an array'); | ||
t.is(balances.length, 0, 'Balances are empty'); | ||
} else { | ||
t.truthy(error, 'Error observed for unsupported chain'); | ||
t.regex( | ||
error, | ||
/Queries not available for chain/i, | ||
'Correct error message for unsupported chain', | ||
); | ||
} | ||
}, | ||
}); | ||
|
||
const queryAccountBalance = test.macro({ | ||
title: (_, chainName: string) => `Query Account Balance on ${chainName}`, | ||
exec: async (t, chainName: string) => { | ||
const config = chainConfig[chainName]; | ||
if (!config) return t.fail(`Unknown chain: ${chainName}`); | ||
const { | ||
wallets, | ||
provisionSmartWallet, | ||
vstorageClient, | ||
retryUntilCondition, | ||
useChain, | ||
} = t.context; | ||
|
||
const { | ||
chainInfo: { | ||
chain: { staking }, | ||
}, | ||
} = useChain(chainName); | ||
const denom = staking?.staking_tokens?.[0].denom; | ||
if (!denom) throw Error(`no denom for ${chainName}`); | ||
|
||
const agoricAddr = wallets[chainName]; | ||
const wdUser1 = await provisionSmartWallet(agoricAddr, { | ||
BLD: 100n, | ||
IST: 100n, | ||
}); | ||
t.log(`provisioning agoric smart wallet for ${agoricAddr}`); | ||
|
||
const doOffer = makeDoOffer(wdUser1); | ||
t.log(`${chainName} makeAccountAndGetBalanceQuery offer`); | ||
const offerId = `${chainName}-makeAccountAndGetBalanceQuery-${Date.now()}`; | ||
|
||
await doOffer({ | ||
id: offerId, | ||
invitationSpec: { | ||
source: 'agoricContract', | ||
instancePath: [contractName], | ||
callPipe: [['makeAccountAndGetBalanceQueryInvitation']], | ||
}, | ||
offerArgs: { chainName, denom }, | ||
proposal: {}, | ||
}); | ||
|
||
const offerResult = await retryUntilCondition( | ||
() => vstorageClient.queryData(`published.wallet.${agoricAddr}`), | ||
({ status }) => status.id === offerId && (status.result || status.error), | ||
`${offerId} offer result is in vstorage`, | ||
MAKE_ACCOUNT_AND_QUERY_BALANCE_TIMEOUT, | ||
); | ||
t.log('Account Balance Query Offer Result', offerResult); | ||
const { icqEnabled } = (chainInfo as Record<string, CosmosChainInfo>)[ | ||
chainName | ||
]; | ||
t.log( | ||
icqEnabled | ||
? 'ICQ Enabled, expecting offer result.' | ||
: 'ICQ Disabled, expecting offer error', | ||
); | ||
|
||
const { | ||
status: { result, error }, | ||
} = offerResult; | ||
if (icqEnabled) { | ||
t.is(error, undefined, 'No error observed for supported chain'); | ||
const parsedBalance = JSON.parse(result); | ||
t.truthy(parsedBalance, 'Result is parsed successfully'); | ||
|
||
t.truthy(parsedBalance, 'Balance object exists'); | ||
t.is(parsedBalance.denom, denom, 'Correct denom in balance'); | ||
t.is(parsedBalance.value, '[0n]', 'Balance amount is 0n'); | ||
} else { | ||
t.truthy(error, 'Error observed for unsupported chain'); | ||
t.regex( | ||
error, | ||
/Queries not available for chain/i, | ||
'Correct error message for unsupported chain', | ||
); | ||
} | ||
}, | ||
}); | ||
|
||
test.serial(queryAccountBalances, 'osmosis'); | ||
test.serial(queryAccountBalances, 'cosmoshub'); | ||
test.serial(queryAccountBalance, 'osmosis'); | ||
test.serial(queryAccountBalance, 'cosmoshub'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/** | ||
* @file A proposal to start the query-flows contract. | ||
* | ||
* QueryFlows is a testing fixture that publishes query results to vstorage. | ||
* It's purpose is to support E2E testing. | ||
*/ | ||
import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; | ||
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; | ||
import { E } from '@endo/far'; | ||
|
||
/** | ||
* @import {QueryFlowsSF as StartFn} from '@agoric/orchestration/src/fixtures/query-flows.contract.js'; | ||
*/ | ||
|
||
const contractName = 'queryFlows'; | ||
const trace = makeTracer(contractName, true); | ||
|
||
/** | ||
* See `@agoric/builders/builders/scripts/orchestration/init-query-flows.js` for | ||
* the accompanying proposal builder. Run `agoric run | ||
* packages/builders/scripts/orchestration/init-query-flows.js` to build the | ||
* contract and proposal files. | ||
* | ||
* @param {BootstrapPowers & { | ||
* installation: { | ||
* consume: { | ||
* queryFlows: Installation<StartFn>; | ||
* }; | ||
* }; | ||
* }} powers | ||
*/ | ||
export const startQueryFlows = async ({ | ||
consume: { | ||
agoricNames, | ||
board, | ||
chainStorage, | ||
chainTimerService, | ||
cosmosInterchainService, | ||
localchain, | ||
startUpgradable, | ||
}, | ||
installation: { | ||
consume: { [contractName]: installation }, | ||
}, | ||
instance: { | ||
// @ts-expect-error unknown instance | ||
produce: { [contractName]: produceInstance }, | ||
}, | ||
}) => { | ||
trace(`start ${contractName}`); | ||
|
||
const storageNode = await makeStorageNodeChild(chainStorage, contractName); | ||
const marshaller = await E(board).getPublishingMarshaller(); | ||
|
||
/** @type {StartUpgradableOpts<StartFn>} */ | ||
const startOpts = { | ||
label: 'queryFlows', | ||
installation, | ||
terms: undefined, | ||
privateArgs: await deeplyFulfilledObject( | ||
harden({ | ||
agoricNames, | ||
orchestrationService: cosmosInterchainService, | ||
localchain, | ||
storageNode, | ||
marshaller, | ||
timerService: chainTimerService, | ||
}), | ||
), | ||
}; | ||
|
||
const { instance } = await E(startUpgradable)(startOpts); | ||
produceInstance.resolve(instance); | ||
}; | ||
harden(startQueryFlows); | ||
|
||
export const getManifestForContract = ( | ||
{ restoreRef }, | ||
{ installKeys, ...options }, | ||
) => { | ||
return { | ||
manifest: { | ||
[startQueryFlows.name]: { | ||
consume: { | ||
agoricNames: true, | ||
board: true, | ||
chainStorage: true, | ||
chainTimerService: true, | ||
cosmosInterchainService: true, | ||
localchain: true, | ||
startUpgradable: true, | ||
}, | ||
installation: { | ||
consume: { [contractName]: true }, | ||
}, | ||
instance: { | ||
produce: { [contractName]: true }, | ||
}, | ||
}, | ||
}, | ||
installations: { | ||
[contractName]: restoreRef(installKeys[contractName]), | ||
}, | ||
options, | ||
}; | ||
}; | ||
|
||
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ | ||
export const defaultProposalBuilder = async ({ publishRef, install }) => { | ||
return harden({ | ||
// Somewhat unorthodox, source the exports from this builder module | ||
sourceSpec: '@agoric/builders/scripts/testing/start-query-flows.js', | ||
getManifestCall: [ | ||
'getManifestForContract', | ||
{ | ||
installKeys: { | ||
queryFlows: publishRef( | ||
install( | ||
'@agoric/orchestration/src/fixtures/query-flows.contract.js', | ||
), | ||
), | ||
}, | ||
}, | ||
], | ||
}); | ||
}; | ||
|
||
export default async (homeP, endowments) => { | ||
// import dynamically so the module can work in CoreEval environment | ||
const dspModule = await import('@agoric/deploy-script-support'); | ||
const { makeHelpers } = dspModule; | ||
const { writeCoreEval } = await makeHelpers(homeP, endowments); | ||
await writeCoreEval(startQueryFlows.name, defaultProposalBuilder); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.