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

9211 lookup denom #9983

Merged
merged 14 commits into from
Aug 30, 2024
40 changes: 9 additions & 31 deletions multichain-testing/test/auto-stake-it.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { CosmosChainInfo } from '@agoric/orchestration';
import anyTest from '@endo/ses-ava/prepare-endo.js';
import type { ExecutionContext, TestFn } from 'ava';
import { useChain } from 'starshipjs';
import type { CosmosChainInfo, IBCConnectionInfo } from '@agoric/orchestration';
import type { SetupContextWithWallets } from './support.js';
import { chainConfig, commonSetup } from './support.js';
import { makeQueryClient } from '../tools/query.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import chainInfo from '../starship-chain-info.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import {
createFundedWalletAndClient,
makeIBCTransferMsg,
} from '../tools/ibc-transfer.js';
import { makeQueryClient } from '../tools/query.js';
import type { SetupContextWithWallets } from './support.js';
import { chainConfig, commonSetup } from './support.js';
import { AUTO_STAKE_IT_DELEGATIONS_TIMEOUT } from './config.js';

const test = anyTest as TestFn<SetupContextWithWallets>;

Expand Down Expand Up @@ -82,6 +83,7 @@ const makeFundAndTransfer = (t: ExecutionContext<SetupContextWithWallets>) => {
const autoStakeItScenario = test.macro({
title: (_, chainName: string) => `auto-stake-it on ${chainName}`,
exec: async (t, chainName: string) => {
// 1. setup
const {
wallets,
vstorageClient,
Expand All @@ -91,36 +93,12 @@ const autoStakeItScenario = test.macro({

const fundAndTransfer = makeFundAndTransfer(t);

// 1. Send initial tokens so denom is available (debatably necessary, but
// allows us to trace the denom until we have ibc denoms in chainInfo)
const agAdminAddr = wallets['agoricAdmin'];
console.log('Sending tokens to', agAdminAddr, `from ${chainName}`);
await fundAndTransfer(chainName, agAdminAddr);

// 2. Find 'stakingDenom' denom on agoric
const agoricConns = chainInfo['agoric'].connections as Record<
string,
IBCConnectionInfo
>;
const remoteChainInfo = (chainInfo as Record<string, CosmosChainInfo>)[
chainName
];
// const remoteChainId = remoteChainInfo.chain.chain_id;
// const agoricToRemoteConn = agoricConns[remoteChainId];
const { portId, channelId } =
agoricConns[remoteChainInfo.chainId].transferChannel;
const agoricQueryClient = makeQueryClient(
await useChain('agoric').getRestEndpoint(),
);
const stakingDenom = remoteChainInfo?.stakingTokens?.[0].denom;
if (!stakingDenom) throw Error(`staking denom found for ${chainName}`);
const { hash } = await retryUntilCondition(
() =>
agoricQueryClient.queryDenom(`/${portId}/${channelId}`, stakingDenom),
denomTrace => !!denomTrace.hash,
`local denom hash for ${stakingDenom} found`,
);
t.log(`found ibc denom hash for ${stakingDenom}:`, hash);

// 3. Find a remoteChain validator to delegate to
const remoteQueryClient = makeQueryClient(
Expand Down Expand Up @@ -161,7 +139,6 @@ const autoStakeItScenario = test.macro({
encoding: 'bech32',
chainId: remoteChainInfo.chainId,
},
localDenom: `ibc/${hash}`,
},
proposal: {},
});
Expand Down Expand Up @@ -203,7 +180,8 @@ const autoStakeItScenario = test.macro({
const { delegation_responses } = await retryUntilCondition(
() => remoteQueryClient.queryDelegations(icaAddress),
({ delegation_responses }) => !!delegation_responses.length,
`delegations visible on ${chainName}`,
`auto-stake-it delegations visible on ${chainName}`,
AUTO_STAKE_IT_DELEGATIONS_TIMEOUT,
);
t.log('delegation balance', delegation_responses[0]?.balance);
t.like(
Expand Down
32 changes: 32 additions & 0 deletions multichain-testing/test/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { RetryOptions } from '../tools/sleep.js';

/**
* Wait 90 seconds to ensure staking rewards are available.
*
* While we expect staking rewards to be available after a
* single block (~5-12 seconds for most chains), this provides additional
* padding after observed failures in CI
* (https://github.com/Agoric/agoric-sdk/issues/9934).
*
* A more robust approach might consider Distribution params and the
* {@link FAUCET_POUR} constant to determine how many blocks it should take for
* rewards to be available.
*/
export const STAKING_REWARDS_TIMEOUT: RetryOptions = {
retryIntervalMs: 5000,
maxRetries: 18,
};

/**
* Wait 2 minutes to ensure:
* - IBC Transfer from LocalAccount -> ICA Account Completes
* - Delegation from ICA Account (initiated from SwingSet) Completes
* - Delegations are visible via LCD (API Endpoint)
*
* Most of the time this finishes in <7 seconds, but other times it
* appears to take much longer.
*/
export const AUTO_STAKE_IT_DELEGATIONS_TIMEOUT: RetryOptions = {
retryIntervalMs: 5000,
maxRetries: 24,
};
20 changes: 2 additions & 18 deletions multichain-testing/test/stake-ica.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,13 @@ import {
} from './support.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import { makeQueryClient } from '../tools/query.js';
import { sleep, type RetryOptions } from '../tools/sleep.js';
import { sleep } from '../tools/sleep.js';
import { STAKING_REWARDS_TIMEOUT } from './config.js';

const test = anyTest as TestFn<SetupContextWithWallets>;

const accounts = ['user1', 'user2'];

/**
* Wait 90 seconds to ensure staking rewards are available.
*
* While we expect staking rewards to be available after a
* single block (~5-12 seconds for most chains), this provide additional
* padding after observed failures in CI
* (https://github.com/Agoric/agoric-sdk/issues/9934).
*
* A more robust approach might consider Distribution params and the
* {@link FAUCET_POUR} constant to determine how many blocks it should take for
* rewards to be available.
*/
export const STAKING_REWARDS_TIMEOUT: RetryOptions = {
retryIntervalMs: 5000,
maxRetries: 18,
};

test.before(async t => {
const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t);
// XXX not necessary for CI, but helpful for unexpected failures in
Expand Down
6 changes: 4 additions & 2 deletions multichain-testing/tools/ibc-transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ type SimpleChainAddress = {
chainName: string;
};

export const DEFAULT_TIMEOUT_NS = 1893456000000000000n;
// 2030-01-01T00:00:00Z
export const DEFAULT_TIMEOUT_NS =
1893456000n * NANOSECONDS_PER_MILLISECOND * MILLISECONDS_PER_SECOND;

/**
* @param {number} [ms] current time in ms (e.g. Date.now())
* @param {bigint} [minutes=5n] number of minutes in the future
* @returns {bigint} nanosecond timestamp 5 mins in the future */
* @returns {bigint} nanosecond timestamp absolute since Unix epoch */
export const getTimeout = (ms: number = 0, minutes = 5n) => {
// UNTIL #9200. timestamps are getting clobbered somewhere along the way
// and we are observing failed transfers with timeouts years in the past.
Expand Down
3 changes: 1 addition & 2 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,8 @@ test.serial('stakeAtom - smart wallet', async t => {
proposal: {},
}),
{
message: 'Brands not currently supported.',
message: 'No denomination for brand [object Alleged: ATOM brand]',
},
'brands not currently supported',
);
});

Expand Down
11 changes: 4 additions & 7 deletions packages/orchestration/src/examples/auto-stake-it.flows.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Fail } from '@endo/errors';
import { denomHash } from '../utils/denomHash.js';

/**
* @import {ResolvedPublicTopic} from '@agoric/zoe/src/contractSupport/topics.js';
Expand All @@ -21,19 +22,13 @@ import { Fail } from '@endo/errors';
* @param {{
* chainName: string;
* validator: CosmosValidatorAddress;
* localDenom: Denom;
* }} offerArgs
*/
export const makeAccounts = async (
orch,
{ makeStakingTap, makePortfolioHolder, chainHub },
seat,
{
chainName,
validator,
// TODO localDenom is user supplied, until #9211
localDenom,
},
{ chainName, validator },
) => {
seat.exit(); // no funds exchanged
const [agoric, remoteChain] = await Promise.all([
Expand Down Expand Up @@ -65,6 +60,8 @@ export const makeAccounts = async (
);
assert(transferChannel.counterPartyChannelId, 'unable to find sourceChannel');

const localDenom = `ibc/${denomHash({ denom: remoteDenom, channelId: transferChannel.channelId })}`;

// Every time the `localAccount` receives `remoteDenom` over IBC, delegate it.
const tap = makeStakingTap({
localAccount,
Expand Down
18 changes: 15 additions & 3 deletions packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
* @file Stake BLD contract
*/
import { makeTracer } from '@agoric/internal';
import { heapVowE as E, prepareVowTools } from '@agoric/vow/vat.js';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { prepareVowTools, heapVowE as E } from '@agoric/vow/vat.js';
import { deeplyFulfilled } from '@endo/marshal';
import { M } from '@endo/patterns';
import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js';
import { makeChainHub } from '../exos/chain-hub.js';
import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js';
import fetchedChainInfo from '../fetched-chain-info.js';

/**
* @import {NameHub} from '@agoric/vats';
Expand Down Expand Up @@ -41,13 +42,15 @@ export const start = async (zcf, privateArgs, baggage) => {
);
const vowTools = prepareVowTools(zone.subZone('vows'));

const chainHub = makeChainHub(privateArgs.agoricNames, vowTools);

const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit(
zone,
makeRecorderKit,
zcf,
privateArgs.timerService,
vowTools,
makeChainHub(privateArgs.agoricNames, vowTools),
chainHub,
);

// ----------------
Expand All @@ -56,6 +59,15 @@ export const start = async (zcf, privateArgs, baggage) => {
const BLD = zcf.getTerms().brands.In;
const bldAmountShape = await E(BLD).getAmountShape();

// XXX big dependency (59KB) but in production will probably already be registered in agoricNames
chainHub.registerChain('agoric', fetchedChainInfo.agoric);
chainHub.registerAsset('ubld', {
baseName: 'agoric',
baseDenom: 'ubld',
brand: BLD,
chainName: 'agoric',
});

async function makeLocalAccountKit() {
const account = await E(privateArgs.localchain).makeAccount();
const address = await E(account).getAddress();
Expand Down
21 changes: 18 additions & 3 deletions packages/orchestration/src/exos/chain-hub-admin.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* we expect promises to resolved promptly, */
/* eslint-disable no-restricted-syntax */
import { M } from '@endo/patterns';
import { heapVowE } from '@agoric/vow/vat.js';
import { M } from '@endo/patterns';
import { CosmosChainInfoShape } from '../typeGuards.js';
import { DenomDetailShape } from './chain-hub.js';

/**
* @import {Zone} from '@agoric/zone';
* @import {CosmosChainInfo, IBCConnectionInfo} from '@agoric/orchestration';
* @import {ChainHub} from './chain-hub.js';
* @import {CosmosChainInfo, Denom, IBCConnectionInfo} from '@agoric/orchestration';
* @import {ChainHub, DenomDetail} from './chain-hub.js';
*/

/**
Expand All @@ -28,6 +29,7 @@ export const prepareChainHubAdmin = (zone, chainHub) => {
CosmosChainInfoShape,
ConnectionInfoShape,
).returns(M.undefined()),
registerAsset: M.call(M.string(), DenomDetailShape).returns(M.promise()),
}),
{
/**
Expand All @@ -48,6 +50,19 @@ export const prepareChainHubAdmin = (zone, chainHub) => {
connectionInfo,
);
},
/**
* Register an asset that may be held on a chain other than the issuing
* chain.
*
* @param {Denom} denom - on the holding chain, whose name is given in
* `detail.chainName`
* @param {DenomDetail} detail - chainName and baseName must be registered
*/
async registerAsset(denom, detail) {
// XXX async work necessary before the synchronous call
await heapVowE.when(chainHub.getChainInfo('agoric'));
chainHub.registerAsset(denom, detail);
},
},
);
return makeCreatorFacet;
Expand Down
24 changes: 23 additions & 1 deletion packages/orchestration/src/exos/chain-hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { CosmosChainInfoShape, IBCConnectionInfoShape } from '../typeGuards.js';
* @property {string} baseName - name of issuing chain; e.g. cosmoshub
* @property {Denom} baseDenom - e.g. uatom
* @property {string} chainName - name of holding chain; e.g. agoric
* @property {Brand} [brand] - vbank brand, if registered
* @property {Brand<'nat'>} [brand] - vbank brand, if registered
* @see {ChainHub} `registerAsset` method
*/
/** @type {TypedPattern<DenomDetail>} */
Expand Down Expand Up @@ -168,6 +168,7 @@ const ChainHubI = M.interface('ChainHub', {
getChainsAndConnection: M.call(M.string(), M.string()).returns(VowShape),
registerAsset: M.call(M.string(), DenomDetailShape).returns(),
lookupAsset: M.call(M.string()).returns(DenomDetailShape),
lookupDenom: M.call(BrandShape).returns(M.or(M.string(), M.undefined())),
});

/**
Expand Down Expand Up @@ -199,6 +200,11 @@ export const makeChainHub = (agoricNames, vowTools) => {
keyShape: M.string(),
valueShape: DenomDetailShape,
});
/** @type {MapStore<Brand, string>} */
const brandDenoms = zone.mapStore('brandDenom', {
keyShape: BrandShape,
valueShape: M.string(),
});

const lookupChainInfo = vowTools.retriable(
zone,
Expand Down Expand Up @@ -380,15 +386,31 @@ export const makeChainHub = (agoricNames, vowTools) => {
chainInfos.has(baseName) ||
Fail`must register chain ${q(baseName)} first`;
denomDetails.init(denom, detail);
if (detail.brand) {
brandDenoms.init(detail.brand, denom);
}
},
/**
* Retrieve holding, issuing chain names etc. for a denom.
*
* @param {Denom} denom
* @returns {DenomDetail}
*/
lookupAsset(denom) {
return denomDetails.get(denom);
},
/**
* Retrieve holding, issuing chain names etc. for a denom.
*
* @param {Brand} brand
* @returns {string | undefined}
*/
lookupDenom(brand) {
if (brandDenoms.has(brand)) {
return brandDenoms.get(brand);
}
return undefined;
},
});

return chainHub;
Expand Down
Loading
Loading