From fd9cb7d4863d550b1f32166ea7172031d3b96c08 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 30 Nov 2024 04:37:42 -0500 Subject: [PATCH] test: `faucetTools.fundFaucet` - adds helper to fund agoric faucet with interchain tokens - allows callers to request `OSMO`, `ATOM`, etc, via `provisionSmartWallet` --- multichain-testing/test/auto-stake-it.test.ts | 60 +++---------------- multichain-testing/test/support.ts | 8 +++ multichain-testing/tools/agd-lib.js | 15 ++++- multichain-testing/tools/e2e-tools.js | 7 ++- multichain-testing/tools/faucet-tools.ts | 44 ++++++++++++++ multichain-testing/tools/ibc-transfer.ts | 59 +++++++++++++++++- multichain-testing/tools/sleep.ts | 2 + 7 files changed, 138 insertions(+), 57 deletions(-) create mode 100644 multichain-testing/tools/faucet-tools.ts diff --git a/multichain-testing/test/auto-stake-it.test.ts b/multichain-testing/test/auto-stake-it.test.ts index e19c38f8479..7d33668a34a 100644 --- a/multichain-testing/test/auto-stake-it.test.ts +++ b/multichain-testing/test/auto-stake-it.test.ts @@ -1,12 +1,9 @@ import { type CosmosChainInfo } from '@agoric/orchestration'; import anyTest from '@endo/ses-ava/prepare-endo.js'; -import type { ExecutionContext, TestFn } from 'ava'; +import type { TestFn } from 'ava'; import starshipChainInfo from '../starship-chain-info.js'; import { makeDoOffer } from '../tools/e2e-tools.js'; -import { - createFundedWalletAndClient, - makeIBCTransferMsg, -} from '../tools/ibc-transfer.js'; +import { makeFundAndTransfer } from '../tools/ibc-transfer.js'; import { makeQueryClient } from '../tools/query.js'; import type { SetupContextWithWallets } from './support.js'; import { chainConfig, commonSetup } from './support.js'; @@ -38,53 +35,6 @@ test.after(async t => { deleteTestKeys(accounts); }); -const makeFundAndTransfer = (t: ExecutionContext) => { - const { retryUntilCondition, useChain } = t.context; - return async (chainName: string, agoricAddr: string, amount = 100n) => { - const { staking } = useChain(chainName).chainInfo.chain; - const denom = staking?.staking_tokens?.[0].denom; - if (!denom) throw Error(`no denom for ${chainName}`); - - const { client, address, wallet } = await createFundedWalletAndClient( - t, - chainName, - useChain, - ); - const balancesResult = await retryUntilCondition( - () => client.getAllBalances(address), - coins => !!coins?.length, - `Faucet balances found for ${address}`, - ); - - console.log('Balances:', balancesResult); - - const transferArgs = makeIBCTransferMsg( - { denom, value: amount }, - { address: agoricAddr, chainName: 'agoric' }, - { address: address, chainName }, - Date.now(), - useChain, - ); - console.log('Transfer Args:', transferArgs); - // TODO #9200 `sendIbcTokens` does not support `memo` - // @ts-expect-error spread argument for concise code - const txRes = await client.sendIbcTokens(...transferArgs); - if (txRes && txRes.code !== 0) { - console.error(txRes); - throw Error(`failed to ibc transfer funds to ${chainName}`); - } - const { events: _events, ...txRest } = txRes; - console.log(txRest); - t.is(txRes.code, 0, `Transaction succeeded`); - t.log(`Funds transferred to ${agoricAddr}`); - return { - client, - address, - wallet, - }; - }; -}; - const autoStakeItScenario = test.macro({ title: (_, chainName: string) => `auto-stake-it on ${chainName}`, exec: async (t, chainName: string) => { @@ -97,7 +47,11 @@ const autoStakeItScenario = test.macro({ useChain, } = t.context; - const fundAndTransfer = makeFundAndTransfer(t); + const fundAndTransfer = makeFundAndTransfer( + t, + retryUntilCondition, + useChain, + ); // 2. Find 'stakingDenom' denom on agoric const remoteChainInfo = ( diff --git a/multichain-testing/test/support.ts b/multichain-testing/test/support.ts index aef2bfab173..c9fa9175a99 100644 --- a/multichain-testing/test/support.ts +++ b/multichain-testing/test/support.ts @@ -18,6 +18,7 @@ import { makeHermes } from '../tools/hermes-tools.js'; import { makeNobleTools } from '../tools/noble-tools.js'; import { makeAssetInfo } from '../tools/asset-info.js'; import starshipChainInfo from '../starship-chain-info.js'; +import { makeFaucetTools } from '../tools/faucet-tools.js'; export const FAUCET_POUR = 10_000n * 1_000_000n; @@ -83,6 +84,12 @@ export const commonSetup = async (t: ExecutionContext) => { const nobleTools = makeNobleTools(childProcess); const assetInfo = makeAssetInfo(starshipChainInfo); const chainInfo = withChainCapabilities(starshipChainInfo); + const faucetTools = makeFaucetTools( + t, + tools.agd, + retryUntilCondition, + useChain, + ); /** * Starts a contract if instance not found. Takes care of installing @@ -123,6 +130,7 @@ export const commonSetup = async (t: ExecutionContext) => { startContract, assetInfo, chainInfo, + faucetTools, }; }; diff --git a/multichain-testing/tools/agd-lib.js b/multichain-testing/tools/agd-lib.js index a301945f148..a28d408493b 100644 --- a/multichain-testing/tools/agd-lib.js +++ b/multichain-testing/tools/agd-lib.js @@ -164,6 +164,19 @@ export const makeAgd = ({ execFileSync }) => { }, ).toString(); }, + /** @param {string} name key name in keyring */ + showAddress: name => { + return execFileSync( + kubectlBinary, + [...binaryArgs, 'keys', 'show', name, '-a', ...keyringArgs], + { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'ignore'], + }, + ) + .toString() + .trim(); + }, /** @param {string} name */ delete: name => { return exec([...keyringArgs, 'keys', 'delete', name, '-y'], { @@ -181,7 +194,7 @@ export const makeAgd = ({ execFileSync }) => { return make(); }; -/** @typedef {ReturnType} Agd */ +/** @typedef {ReturnType} Agd */ /** @param {{ execFileSync: typeof import('child_process').execFileSync, log: typeof console.log }} powers */ export const makeCopyFiles = ( diff --git a/multichain-testing/tools/e2e-tools.js b/multichain-testing/tools/e2e-tools.js index ec0759d9232..4409c01651a 100644 --- a/multichain-testing/tools/e2e-tools.js +++ b/multichain-testing/tools/e2e-tools.js @@ -142,7 +142,11 @@ export const provisionSmartWallet = async ( // TODO: skip this query if balances is {} const vbankEntries = await q.queryData('published.agoricNames.vbankAsset'); const byName = Object.fromEntries( - vbankEntries.map(([_denom, info]) => [info.issuerName, info]), + vbankEntries.map(([denom, info]) => { + /// XXX better way to filter out old ATOM denom? + if (denom === 'ibc/toyatom') return [undefined, undefined]; + return [info.issuerName, info]; + }), ); progress({ send: balances, to: address }); @@ -540,6 +544,7 @@ export const makeE2ETools = async ( /** @param {string} name */ deleteKey: async name => agd.keys.delete(name), copyFiles, + agd, }; }; diff --git a/multichain-testing/tools/faucet-tools.ts b/multichain-testing/tools/faucet-tools.ts new file mode 100644 index 00000000000..d7161cd8b0f --- /dev/null +++ b/multichain-testing/tools/faucet-tools.ts @@ -0,0 +1,44 @@ +import type { ExecutionContext } from 'ava'; +import type { Denom } from '@agoric/orchestration'; +import { makeFundAndTransfer } from './ibc-transfer.js'; +import type { MultichainRegistry } from './registry.js'; +import type { RetryUntilCondition } from './sleep.js'; +import type { AgdTools } from './agd-tools.js'; + +type ChainName = string; + +// 90% of default faucet pour +const DEFAULT_QTY = (10_000_000_000n * 9n) / 10n; + +/** + * Determines the agoric `faucet` address and sends funds to it. + * + * Allows use of brands like OSMO, ATOM, etc. with `provisionSmartWallet`. + */ +export const makeFaucetTools = ( + t: ExecutionContext, + agd: AgdTools['agd'], + retryUntilCondition: RetryUntilCondition, + useChain: MultichainRegistry['useChain'], +) => { + return { + /** + * @param assets denom on the issuing chain + * @param [qty] number of tokens + */ + fundFaucet: async (assets: [ChainName, Denom][], qty = DEFAULT_QTY) => { + const faucetAddr = agd.keys.showAddress('faucet'); + console.log(`Faucet address: ${faucetAddr}`); + + const fundAndTransfer = makeFundAndTransfer( + t, + retryUntilCondition, + useChain, + ); + + for (const [chainName, denom] of assets) { + await fundAndTransfer(chainName, faucetAddr, qty, denom); + } + }, + }; +}; diff --git a/multichain-testing/tools/ibc-transfer.ts b/multichain-testing/tools/ibc-transfer.ts index 41db593cff2..b140dcb40b3 100644 --- a/multichain-testing/tools/ibc-transfer.ts +++ b/multichain-testing/tools/ibc-transfer.ts @@ -16,6 +16,7 @@ import { MsgTransfer } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/t import { createWallet } from './wallet.js'; import chainInfo from '../starship-chain-info.js'; import type { MultichainRegistry } from './registry.js'; +import type { RetryUntilCondition } from './sleep.js'; interface MakeFeeObjectArgs { denom?: string; @@ -118,14 +119,14 @@ export const makeIBCTransferMsg = ( }; export const createFundedWalletAndClient = async ( - t: ExecutionContext, + log: (...args: unknown[]) => void, chainName: string, useChain: MultichainRegistry['useChain'], ) => { const { chain, creditFromFaucet, getRpcEndpoint } = useChain(chainName); const wallet = await createWallet(chain.bech32_prefix); const address = (await wallet.getAccounts())[0].address; - t.log(`Requesting faucet funds for ${address}`); + log(`Requesting faucet funds for ${address}`); await creditFromFaucet(address); // TODO use telescope generated rpc client from @agoric/cosmic-proto // https://github.com/Agoric/agoric-sdk/issues/9200 @@ -135,3 +136,57 @@ export const createFundedWalletAndClient = async ( ); return { client, wallet, address }; }; + +export const makeFundAndTransfer = ( + t: ExecutionContext, + retryUntilCondition: RetryUntilCondition, + useChain: MultichainRegistry['useChain'], +) => { + return async ( + chainName: string, + agoricAddr: string, + amount = 100n, + denom?: string, + ) => { + const { staking } = useChain(chainName).chainInfo.chain; + const denomToTransfer = denom || staking?.staking_tokens?.[0].denom; + if (!denomToTransfer) throw Error(`no denom for ${chainName}`); + + const { client, address, wallet } = await createFundedWalletAndClient( + t.log, + chainName, + useChain, + ); + const balancesResult = await retryUntilCondition( + () => client.getAllBalances(address), + coins => !!coins?.length, + `Faucet balances found for ${address}`, + ); + console.log('Balances:', balancesResult); + + const transferArgs = makeIBCTransferMsg( + { denom: denomToTransfer, value: amount }, + { address: agoricAddr, chainName: 'agoric' }, + { address: address, chainName }, + Date.now(), + useChain, + ); + console.log('Transfer Args:', transferArgs); + // TODO #9200 `sendIbcTokens` does not support `memo` + // @ts-expect-error spread argument for concise code + const txRes = await client.sendIbcTokens(...transferArgs); + if (txRes && txRes.code !== 0) { + console.error(txRes); + throw Error(`failed to ibc transfer funds to ${chainName}`); + } + const { events: _events, ...txRest } = txRes; + console.log(txRest); + t.is(txRes.code, 0, `Transaction succeeded`); + t.log(`Funds transferred to ${agoricAddr}`); + return { + client, + address, + wallet, + }; + }; +}; diff --git a/multichain-testing/tools/sleep.ts b/multichain-testing/tools/sleep.ts index 66251a87dca..1b0d1a58807 100644 --- a/multichain-testing/tools/sleep.ts +++ b/multichain-testing/tools/sleep.ts @@ -75,3 +75,5 @@ export const makeRetryUntilCondition = (defaultOptions: RetryOptions = {}) => { ...options, }); }; + +export type RetryUntilCondition = ReturnType;