From d4aa0eae49e697b9a490d982f8057e6e23f16af3 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 13:24:24 -0400 Subject: [PATCH 1/9] feat: narrow AmountArg to NatAmountShape --- packages/orchestration/src/typeGuards.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index 6088eb07fe9..a0174fc681a 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -1,4 +1,3 @@ -import { AmountShape } from '@agoric/ertp'; import { VowShape } from '@agoric/vow'; import { M } from '@endo/patterns'; @@ -39,7 +38,6 @@ export const Proto3Shape = { value: M.string(), }; -// FIXME missing `delegatorAddress` from the type /** @type {TypedPattern} */ export const DelegationShape = harden({ validatorAddress: M.string(), @@ -130,8 +128,14 @@ export const DenomInfoShape = { /** @type {TypedPattern} */ export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; +/** @type {TypedPattern>} */ +export const AnyNatAmountShape = harden({ + brand: M.remotable('Brand'), + value: M.nat(), +}); + /** @type {TypedPattern} */ -export const AmountArgShape = M.or(AmountShape, DenomAmountShape); +export const AmountArgShape = M.or(AnyNatAmountShape, DenomAmountShape); /** Approximately @see RequestQuery */ export const ICQMsgShape = M.splitRecord( From 642053722a58625eec8c1a7cabe714f0f056056e Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 13:26:18 -0400 Subject: [PATCH 2/9] feat!: `undelegate` takes AmountArg and ChainAddress --- multichain-testing/test/stake-ica.test.ts | 24 +++++++++---------- packages/orchestration/src/cosmos-api.ts | 2 +- .../examples/staking-combinations.contract.js | 4 ++-- .../examples/staking-combinations.flows.js | 5 ++-- .../src/exos/cosmos-orchestration-account.js | 17 +++++++------ packages/orchestration/src/typeGuards.js | 19 +++++++++------ .../test/examples/stake-ica.contract.test.ts | 11 +++++---- .../examples/staking-combinations.test.ts | 11 +++++++-- .../orchestration/test/staking-ops.test.ts | 17 +++++++------ 9 files changed, 63 insertions(+), 47 deletions(-) diff --git a/multichain-testing/test/stake-ica.test.ts b/multichain-testing/test/stake-ica.test.ts index d3007496450..f5fd41fd020 100644 --- a/multichain-testing/test/stake-ica.test.ts +++ b/multichain-testing/test/stake-ica.test.ts @@ -46,16 +46,10 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => { vstorageClient, retryUntilCondition, useChain, - deployBuilder, + startContract, } = t.context; - t.log('bundle and install contract', scenario); - await deployBuilder(scenario.builder); - await retryUntilCondition( - () => vstorageClient.queryData(`published.agoricNames.instance`), - res => scenario.contractName in Object.fromEntries(res), - `${scenario.contractName} instance is available`, - ); + await startContract(scenario.contractName, scenario.builder); const wdUser1 = await provisionSmartWallet(wallets[scenario.wallet], { BLD: 100n, IST: 100n, @@ -206,7 +200,7 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => { ); t.log('Balance after claiming rewards:', rewards); - const SHARES = 50; + const TOKENS_TO_UNDELEGATE = 50n; t.log('Undelegate offer from continuing inv'); const undelegateOfferId = `undelegate-${Date.now()}`; await doOffer({ @@ -218,8 +212,11 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => { invitationArgs: [ [ { - validatorAddress, - shares: String(SHARES), + validator: validatorChainAddress, + amount: { + denom: scenario.denom, + value: TOKENS_TO_UNDELEGATE, + }, }, ], ], @@ -235,7 +232,7 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => { t.log('unbonding_responses:', unbonding_responses[0].entries); t.is( unbonding_responses[0].entries[0].balance, - String(SHARES), + String(TOKENS_TO_UNDELEGATE), 'undelegating 50 shares in progress', ); @@ -250,7 +247,8 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => { const { balances: rewardsWithUndelegations } = await retryUntilCondition( () => queryClient.queryBalances(address), ({ balances }) => { - const expectedBalance = Number(currentBalances[0].amount) + SHARES; + const expectedBalance = + Number(currentBalances[0].amount) + Number(TOKENS_TO_UNDELEGATE); return Number(balances?.[0]?.amount) >= expectedBalance; }, 'claimed rewards available', diff --git a/packages/orchestration/src/cosmos-api.ts b/packages/orchestration/src/cosmos-api.ts index d44bd8c4307..9fa8076df8c 100644 --- a/packages/orchestration/src/cosmos-api.ts +++ b/packages/orchestration/src/cosmos-api.ts @@ -187,7 +187,7 @@ export interface StakingAccountActions { * @param delegations - the delegation to undelegate */ undelegate: ( - delegations: Omit[], + delegations: { amount: AmountArg; validator: CosmosValidatorAddress }[], ) => Promise; /** diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js index 62d15438364..d7229c95a99 100644 --- a/packages/orchestration/src/examples/staking-combinations.contract.js +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -14,7 +14,6 @@ import * as flows from './staking-combinations.flows.js'; /** * @import {GuestInterface} from '@agoric/async-flow'; - * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {ContinuingOfferResult} from '@agoric/smart-wallet/src/types.js'; * @import {TimerService} from '@agoric/time'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; @@ -25,6 +24,7 @@ import * as flows from './staking-combinations.flows.js'; * @import {CosmosInterchainService} from '../exos/exo-interfaces.js'; * @import {OrchestrationTools} from '../utils/start-helper.js'; * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; + * @import {AmountArg, CosmosValidatorAddress} from '../types.js'; */ const emptyOfferShape = harden({ @@ -80,7 +80,7 @@ const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { ); }, /** - * @param {Omit[]} delegations + * @param {{ amount: AmountArg; validator: CosmosValidatorAddress }[]} delegations */ UndelegateAndTransfer(delegations) { const { account } = this.state; diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js index 9361bbf5176..082446bef40 100644 --- a/packages/orchestration/src/examples/staking-combinations.flows.js +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -1,9 +1,8 @@ /** * @import {GuestInterface} from '@agoric/async-flow'; - * @import {Orchestrator, OrchestrationFlow, OrchestrationAccount, OrchestrationAccountI, StakingAccountActions, AmountArg, CosmosValidatorAddress} from '../types.js' + * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress} from '../types.js' * @import {ContinuingOfferResult, InvitationMakers} from '@agoric/smart-wallet/src/types.js'; * @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js'; - * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; */ @@ -67,7 +66,7 @@ harden(depositAndDelegate); * @param {Orchestrator} orch * @param {object} ctx * @param {GuestInterface} account - * @param {Omit[]} delegations + * @param {{ amount: AmountArg; validator: CosmosValidatorAddress }[]} delegations * @returns {Promise} */ export const undelegateAndTransfer = async ( diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 59dcee5b58c..84b338a2943 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -407,16 +407,19 @@ export const prepareCosmosOrchestrationAccountKit = ( /** @param {CosmosValidatorAddress} validator */ WithdrawReward(validator) { trace('WithdrawReward', validator); - return zcf.makeInvitation(seat => { seat.exit(); return watch(this.facets.holder.withdrawReward(validator)); }, 'WithdrawReward'); }, - /** @param {Omit[]} delegations */ + /** + * @param {{ + * amount: AmountArg; + * validator: CosmosValidatorAddress; + * }[]} delegations + */ Undelegate(delegations) { trace('Undelegate', delegations); - return zcf.makeInvitation(seat => { seat.exit(); return watch(this.facets.holder.undelegate(delegations)); @@ -725,16 +728,16 @@ export const prepareCosmosOrchestrationAccountKit = ( return asVow(() => { trace('undelegate', delegations); const { helper } = this.facets; - const { chainAddress, bondDenom } = this.state; + const { chainAddress } = this.state; const undelegateV = watch( E(helper.owned()).executeEncodedTx( - delegations.map(d => + delegations.map(({ validator, amount }) => Any.toJSON( MsgUndelegate.toProtoMsg({ delegatorAddress: chainAddress.value, - validatorAddress: d.validatorAddress, - amount: { denom: bondDenom, amount: d.shares }, + validatorAddress: validator.value, + amount: coerceCoin(chainHub, amount), }), ), ), diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index a0174fc681a..4c3255dbbdf 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -3,7 +3,7 @@ import { M } from '@endo/patterns'; /** * @import {TypedPattern} from '@agoric/internal'; - * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomDetail, DenomInfo, AmountArg} from './types.js'; + * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomDetail, DenomInfo, AmountArg, CosmosValidatorAddress} from './types.js'; * @import {Any as Proto3Msg} from '@agoric/cosmic-proto/google/protobuf/any.js'; * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; @@ -38,12 +38,6 @@ export const Proto3Shape = { value: M.string(), }; -/** @type {TypedPattern} */ -export const DelegationShape = harden({ - validatorAddress: M.string(), - shares: M.string(), // TODO: bigint? -}); - /** @internal */ export const IBCTransferOptionsShape = M.splitRecord( {}, @@ -137,6 +131,17 @@ export const AnyNatAmountShape = harden({ /** @type {TypedPattern} */ export const AmountArgShape = M.or(AnyNatAmountShape, DenomAmountShape); +/** + * @type {TypedPattern<{ + * validator: CosmosValidatorAddress; + * amount: AmountArg; + * }>} + */ +export const DelegationShape = harden({ + validator: ChainAddressShape, + amount: AmountArgShape, +}); + /** Approximately @see RequestQuery */ export const ICQMsgShape = M.splitRecord( { path: M.string(), data: M.string() }, diff --git a/packages/orchestration/test/examples/stake-ica.contract.test.ts b/packages/orchestration/test/examples/stake-ica.contract.test.ts index fcbe90cc0ab..6845546c107 100644 --- a/packages/orchestration/test/examples/stake-ica.contract.test.ts +++ b/packages/orchestration/test/examples/stake-ica.contract.test.ts @@ -126,16 +126,17 @@ test('delegate, undelegate, redelegate, withdrawReward', async t => { chainId: 'cosmoshub-4', encoding: 'bech32' as const, }; - const delegation = await E(account).delegate(validatorAddr, { + const delegation = { denom: 'uatom', value: 10n, - }); - t.is(delegation, undefined, 'delegation returns void'); + }; + const delegationResult = await E(account).delegate(validatorAddr, delegation); + t.is(delegationResult, undefined, 'delegation returns void'); const undelegatationP = E(account).undelegate([ { - shares: '10', - validatorAddress: validatorAddr.value, + amount: delegation, + validator: validatorAddr, }, ]); const completionTime = UNBOND_PERIOD_SECONDS + maxClockSkew; diff --git a/packages/orchestration/test/examples/staking-combinations.test.ts b/packages/orchestration/test/examples/staking-combinations.test.ts index 58505844d28..5a7df3d8cdf 100644 --- a/packages/orchestration/test/examples/staking-combinations.test.ts +++ b/packages/orchestration/test/examples/staking-combinations.test.ts @@ -81,13 +81,20 @@ test('start', async t => { // Undelegate the funds using the guest flow ibcBridge.addMockAck( // observed in console - 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xnS0pTOWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxeloxVnVaR1ZzWldkaGRHVVNMd29MWTI5emJXOXpNWFJsYzNRU0VtTnZjMjF2YzNaaGJHOXdaWEl4ZEdWemRCb01DZ1YxYjNOdGJ4SURNVEF3IiwibWVtbyI6IiJ9', + 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xZS0pTOWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxeloxVnVaR1ZzWldkaGRHVVNMUW9MWTI5emJXOXpNWFJsYzNRU0VHOXpiVzkyWVd4dmNHVnlNWFJsYzNRYURBb0ZkVzl6Ylc4U0F6RXdNQT09IiwibWVtbyI6IiJ9', protoMsgMocks.undelegate.ack, ); const undelegateInvVow = await E( result.invitationMakers, ).UndelegateAndTransfer([ - { validatorAddress: 'cosmosvaloper1test', shares: '100' }, + { + validator: { + value: 'osmovaloper1test', + encoding: 'bech32', + chainId: 'osmosis', + }, + amount: { denom: 'uosmo', value: 100n }, + }, ]); const undelegateInv = await vt.when(undelegateInvVow); t.like(await E(zoe).getInvitationDetails(undelegateInv), { diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index 3e754bab786..234e24ef6e1 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -26,7 +26,12 @@ import { Far } from '@endo/far'; import { Timestamp } from '@agoric/cosmic-proto/google/protobuf/timestamp.js'; import { makeNameHubKit } from '@agoric/vats'; import { prepareCosmosOrchestrationAccountKit } from '../src/exos/cosmos-orchestration-account.js'; -import type { ChainAddress, IcaAccount, ICQConnection } from '../src/types.js'; +import type { + ChainAddress, + DenomAmount, + IcaAccount, + ICQConnection, +} from '../src/types.js'; import { MILLISECONDS_PER_SECOND } from '../src/utils/time.js'; import { makeChainHub } from '../src/exos/chain-hub.js'; @@ -345,7 +350,6 @@ test(`delegate; redelegate using invitationMakers`, async t => { vowTools, zone, } = s; - const aBrand = Far('Token') as Brand<'nat'>; const makeAccountKit = prepareCosmosOrchestrationAccountKit(zone, { chainHub: makeChainHub(agoricNames, vowTools), makeRecorderKit, @@ -511,15 +515,14 @@ test(`undelegate waits for unbonding period`, async t => { const { validator, delegations } = configStaking; - const value = BigInt(Object.values(delegations)[0].amount); - const anAmount = { brand: Far('Token'), value } as Amount<'nat'>; + const { denom, amount } = Object.values(delegations)[0]; const delegation = { - shares: `${anAmount.value}`, - validatorAddress: validator.value, + amount: { denom, value: BigInt(amount) } as DenomAmount, + validator, }; const toUndelegate = await E(invitationMakers).Undelegate([delegation]); const current = () => E(timer).getCurrentTimestamp().then(time.format); - t.log(await current(), 'undelegate', delegation.shares); + t.log(await current(), 'undelegate', delegation.amount.value); const seat = E(zoe).offer(toUndelegate); const beforeDone = E(timer) From 73404f2ae7738fc63a7ead2ec1f1c6a60629fc3a Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 14:03:39 -0400 Subject: [PATCH 3/9] feat: complete undelegateAndTransfer flow --- .../examples/staking-combinations.contract.js | 19 ++++-- .../examples/staking-combinations.flows.js | 29 +++++---- .../examples/staking-combinations.test.ts | 60 ++++++++++++++++--- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js index d7229c95a99..97275cdd111 100644 --- a/packages/orchestration/src/examples/staking-combinations.contract.js +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -9,6 +9,7 @@ import { AmountShape } from '@agoric/ertp'; import { M } from '@endo/patterns'; import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js'; import { CosmosOrchestrationInvitationMakersInterface } from '../exos/cosmos-orchestration-account.js'; +import { ChainAddressShape, DelegationShape } from '../typeGuards.js'; import { withOrchestration } from '../utils/start-helper.js'; import * as flows from './staking-combinations.flows.js'; @@ -24,7 +25,7 @@ import * as flows from './staking-combinations.flows.js'; * @import {CosmosInterchainService} from '../exos/exo-interfaces.js'; * @import {OrchestrationTools} from '../utils/start-helper.js'; * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; - * @import {AmountArg, CosmosValidatorAddress} from '../types.js'; + * @import {AmountArg, ChainAddress, CosmosValidatorAddress} from '../types.js'; */ const emptyOfferShape = harden({ @@ -52,7 +53,10 @@ const emptyOfferShape = harden({ const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { const ExtraInvitationMakerInterface = M.interface('', { DepositAndDelegate: M.call(M.array()).returns(M.promise()), - UndelegateAndTransfer: M.call(M.array()).returns(M.promise()), + UndelegateAndTransfer: M.call( + M.arrayOf(DelegationShape), + ChainAddressShape, + ).returns(M.promise()), }); /** @type {any} XXX async membrane */ const makeExtraInvitationMaker = zone.exoClass( @@ -81,13 +85,18 @@ const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { }, /** * @param {{ amount: AmountArg; validator: CosmosValidatorAddress }[]} delegations + * @param {ChainAddress} destination */ - UndelegateAndTransfer(delegations) { + UndelegateAndTransfer(delegations, destination) { const { account } = this.state; return zcf.makeInvitation( - // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this - () => orchFns.undelegateAndTransfer(account, delegations), + () => + // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this + orchFns.undelegateAndTransfer(account, { + delegations, + destination, + }), 'Undelegate and transfer', undefined, emptyOfferShape, diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js index 082446bef40..526f4ea7599 100644 --- a/packages/orchestration/src/examples/staking-combinations.flows.js +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -1,6 +1,6 @@ /** * @import {GuestInterface} from '@agoric/async-flow'; - * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress} from '../types.js' + * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress, ChainAddress} from '../types.js' * @import {ContinuingOfferResult, InvitationMakers} from '@agoric/smart-wallet/src/types.js'; * @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js'; * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; @@ -38,8 +38,8 @@ harden(makeAccount); /** * @satisfies {OrchestrationFlow} - * @param {Orchestrator} orch - * @param {object} ctx + * @param {Orchestrator} _orch + * @param {object} _ctx * @param {GuestInterface} account * @param {ZCFSeat} seat * @param {CosmosValidatorAddress} validator @@ -47,8 +47,8 @@ harden(makeAccount); * @returns {Promise} */ export const depositAndDelegate = async ( - orch, - ctx, + _orch, + _ctx, account, seat, validator, @@ -63,20 +63,25 @@ harden(depositAndDelegate); /** * @satisfies {OrchestrationFlow} - * @param {Orchestrator} orch - * @param {object} ctx + * @param {Orchestrator} _orch + * @param {object} _ctx * @param {GuestInterface} account - * @param {{ amount: AmountArg; validator: CosmosValidatorAddress }[]} delegations + * @param {{ + * delegations: { amount: AmountArg; validator: CosmosValidatorAddress }[]; + * destination: ChainAddress; + * }} offerArgs * @returns {Promise} */ export const undelegateAndTransfer = async ( - orch, - ctx, + _orch, + _ctx, account, - delegations, + { delegations, destination }, ) => { await account.undelegate(delegations); - // TODO transfer something + for (const { amount } of delegations) { + await account.transfer(amount, destination); + } return 'guest undelegateAndTransfer complete'; }; harden(undelegateAndTransfer); diff --git a/packages/orchestration/test/examples/staking-combinations.test.ts b/packages/orchestration/test/examples/staking-combinations.test.ts index 5a7df3d8cdf..8c19002dda6 100644 --- a/packages/orchestration/test/examples/staking-combinations.test.ts +++ b/packages/orchestration/test/examples/staking-combinations.test.ts @@ -4,8 +4,17 @@ import { inspectMapStore } from '@agoric/internal/src/testing-utils.js'; import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E } from '@endo/far'; import path from 'path'; +import { + MsgTransfer, + MsgTransferResponse, +} from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js'; +import { IBCMethod } from '@agoric/vats'; import { protoMsgMocks, UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js'; import { commonSetup } from '../supports.js'; +import { + buildMsgResponseString, + parseOutgoingTxPacket, +} from '../../tools/ibc-mocks.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -18,6 +27,7 @@ test('start', async t => { bootstrap: { timer, vowTools: vt }, brands: { ist }, mocks: { ibcBridge }, + utils: { inspectDibcBridge }, commonPrivateArgs, } = await commonSetup(t); @@ -84,18 +94,31 @@ test('start', async t => { 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xZS0pTOWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxeloxVnVaR1ZzWldkaGRHVVNMUW9MWTI5emJXOXpNWFJsYzNRU0VHOXpiVzkyWVd4dmNHVnlNWFJsYzNRYURBb0ZkVzl6Ylc4U0F6RXdNQT09IiwibWVtbyI6IiJ9', protoMsgMocks.undelegate.ack, ); + ibcBridge.addMockAck( + // observed in console + 'eyJ0eXBlIjoxLCJkYXRhIjoiQ25nS0tTOXBZbU11WVhCd2JHbGpZWFJwYjI1ekxuUnlZVzV6Wm1WeUxuWXhMazF6WjFSeVlXNXpabVZ5RWtzS0NIUnlZVzV6Wm1WeUVnbGphR0Z1Ym1Wc0xUQWFEQW9GZFc5emJXOFNBekV3TUNJTFkyOXpiVzl6TVhSbGMzUXFEMk52YzIxdmN6RnlaV05sYVhabGNqSUFPSUNRK0lTZ21nRT0iLCJtZW1vIjoiIn0=', + buildMsgResponseString(MsgTransferResponse, { sequence: 0n }), + ); + const destination = { + chainId: 'cosmoshub-4', + value: 'cosmos1receiver', + encoding: 'bech32', + }; const undelegateInvVow = await E( result.invitationMakers, - ).UndelegateAndTransfer([ - { - validator: { - value: 'osmovaloper1test', - encoding: 'bech32', - chainId: 'osmosis', + ).UndelegateAndTransfer( + [ + { + validator: { + value: 'osmovaloper1test', + encoding: 'bech32', + chainId: 'osmosis', + }, + amount: { denom: 'uosmo', value: 100n }, }, - amount: { denom: 'uosmo', value: 100n }, - }, - ]); + ], + destination, + ); const undelegateInv = await vt.when(undelegateInvVow); t.like(await E(zoe).getInvitationDetails(undelegateInv), { description: 'Undelegate and transfer', @@ -111,6 +134,25 @@ test('start', async t => { ); t.is(undelegateResult, 'guest undelegateAndTransfer complete'); + const { bridgeDowncalls } = await inspectDibcBridge(); + const { messages } = parseOutgoingTxPacket( + (bridgeDowncalls.at(-1) as IBCMethod<'sendPacket'>).packet.data, + ); + const transferMsg = MsgTransfer.decode(messages[0].value); + t.like( + transferMsg, + { + receiver: 'cosmos1receiver', + sourcePort: 'transfer', + token: { + amount: '100', + denom: 'uosmo', + }, + memo: '', + }, + 'undelegateAndTransfer sent MsgTransfer', + ); + // snapshot the resulting contract baggage const tree = inspectMapStore(contractBaggage); t.snapshot(tree, 'contract baggage after start'); From cfe73bc12941959019e772dcf69848e696707e15 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 16:07:00 -0400 Subject: [PATCH 4/9] feat: delegate allows any bondDenom --- .../orchestration/src/examples/stakeIca.contract.js | 13 +++---------- packages/orchestration/src/exos/README.md | 1 - .../src/exos/cosmos-orchestration-account.js | 8 ++------ .../orchestration/src/exos/remote-chain-facade.js | 5 +---- .../orchestration/src/proposals/start-stakeAtom.js | 1 - .../orchestration/src/proposals/start-stakeOsmo.js | 1 - .../test/examples/stake-ica.contract.test.ts | 1 - .../test/exos/cosmos-orchestration-account.test.ts | 2 -- .../orchestration/test/exos/make-test-coa-kit.ts | 3 +-- packages/orchestration/test/staking-ops.test.ts | 5 ----- 10 files changed, 7 insertions(+), 33 deletions(-) diff --git a/packages/orchestration/src/examples/stakeIca.contract.js b/packages/orchestration/src/examples/stakeIca.contract.js index 13d6421fb6b..d6b89145368 100644 --- a/packages/orchestration/src/examples/stakeIca.contract.js +++ b/packages/orchestration/src/examples/stakeIca.contract.js @@ -29,7 +29,6 @@ export const meta = harden({ chainId: M.string(), hostConnectionId: M.string(), controllerConnectionId: M.string(), - bondDenom: M.string(), icqEnabled: M.boolean(), }, privateArgsShape: { @@ -49,7 +48,6 @@ harden(privateArgsShape); * chainId: string; * hostConnectionId: IBCConnectionID; * controllerConnectionId: IBCConnectionID; - * bondDenom: string; * icqEnabled: boolean; * }} StakeIcaTerms */ @@ -66,13 +64,8 @@ harden(privateArgsShape); * @param {Baggage} baggage */ export const start = async (zcf, privateArgs, baggage) => { - const { - chainId, - hostConnectionId, - controllerConnectionId, - bondDenom, - icqEnabled, - } = zcf.getTerms(); + const { chainId, hostConnectionId, controllerConnectionId, icqEnabled } = + zcf.getTerms(); const { agoricNames, cosmosInterchainService: orchestration, @@ -125,7 +118,7 @@ export const start = async (zcf, privateArgs, baggage) => { chainAddress.value, ); const holder = makeCosmosOrchestrationAccount( - { chainAddress, bondDenom, localAddress, remoteAddress }, + { chainAddress, localAddress, remoteAddress }, { account, storageNode: accountNode, diff --git a/packages/orchestration/src/exos/README.md b/packages/orchestration/src/exos/README.md index 1b5fe5e424a..f147c1bf071 100644 --- a/packages/orchestration/src/exos/README.md +++ b/packages/orchestration/src/exos/README.md @@ -108,7 +108,6 @@ classDiagram class CosmosOrchestrationAccount { account: LocalChainAccount - bondDenom: string chainAddress: ChainAddress icqConnection: ICQConnection | undefined timer: Timer diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 84b338a2943..ba7aa5aa136 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -77,7 +77,6 @@ const { Vow$ } = NetworkShape; // TODO #9611 * localAddress: LocalIbcAddress; * remoteAddress: RemoteIbcAddress; * icqConnection: ICQConnection | undefined; - * bondDenom: string; * timer: Remote; * }} State * Internal to the IcaAccountHolder exo @@ -205,7 +204,6 @@ export const prepareCosmosOrchestrationAccountKit = ( /** * @param {object} info * @param {ChainAddress} info.chainAddress - * @param {string} info.bondDenom e.g. 'uatom' * @param {LocalIbcAddress} info.localAddress * @param {RemoteIbcAddress} info.remoteAddress * @param {object} io @@ -215,7 +213,7 @@ export const prepareCosmosOrchestrationAccountKit = ( * @param {Remote} io.timer * @returns {State} */ - ({ chainAddress, bondDenom, localAddress, remoteAddress }, io) => { + ({ chainAddress, localAddress, remoteAddress }, io) => { const { storageNode } = io; // must be the fully synchronous maker because the kit is held in durable state const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); @@ -233,7 +231,6 @@ export const prepareCosmosOrchestrationAccountKit = ( const { account, icqConnection, timer } = io; return { account, - bondDenom, chainAddress, icqConnection, localAddress, @@ -540,10 +537,9 @@ export const prepareCosmosOrchestrationAccountKit = ( return asVow(() => { trace('delegate', validator, amount); const { helper } = this.facets; - const { chainAddress, bondDenom } = this.state; + const { chainAddress } = this.state; const amountAsCoin = helper.amountToCoin(amount); - assert.equal(amountAsCoin.denom, bondDenom); const results = E(helper.owned()).executeEncodedTx([ Any.toJSON( diff --git a/packages/orchestration/src/exos/remote-chain-facade.js b/packages/orchestration/src/exos/remote-chain-facade.js index 555839e70d0..3efebfff11d 100644 --- a/packages/orchestration/src/exos/remote-chain-facade.js +++ b/packages/orchestration/src/exos/remote-chain-facade.js @@ -233,14 +233,11 @@ const prepareRemoteChainFacadeKit = ( childNode, { account, chainAddress, localAddress, remoteAddress }, ) { - const { remoteChainInfo, icqConnection } = this.state; - const stakingDenom = remoteChainInfo.stakingTokens?.[0]?.denom; - if (!stakingDenom) throw Fail`chain info lacks staking denom`; + const { icqConnection } = this.state; return makeCosmosOrchestrationAccount( { chainAddress, - bondDenom: stakingDenom, localAddress, remoteAddress, }, diff --git a/packages/orchestration/src/proposals/start-stakeAtom.js b/packages/orchestration/src/proposals/start-stakeAtom.js index aac824c946a..838bacfd832 100644 --- a/packages/orchestration/src/proposals/start-stakeAtom.js +++ b/packages/orchestration/src/proposals/start-stakeAtom.js @@ -61,7 +61,6 @@ export const startStakeAtom = async ({ chainId: cosmoshub.chainId, hostConnectionId: connectionInfo.counterparty.connection_id, controllerConnectionId: connectionInfo.id, - bondDenom: cosmoshub.stakingTokens[0].denom, icqEnabled: cosmoshub.icqEnabled, }, privateArgs: await deeplyFulfilledObject( diff --git a/packages/orchestration/src/proposals/start-stakeOsmo.js b/packages/orchestration/src/proposals/start-stakeOsmo.js index c6956b130b4..84dfadb1337 100644 --- a/packages/orchestration/src/proposals/start-stakeOsmo.js +++ b/packages/orchestration/src/proposals/start-stakeOsmo.js @@ -66,7 +66,6 @@ export const startStakeOsmo = async ({ chainId: osmosis.chainId, hostConnectionId: connectionInfo.counterparty.connection_id, controllerConnectionId: connectionInfo.id, - bondDenom: osmosis.stakingTokens[0].denom, icqEnabled: osmosis.icqEnabled, }, privateArgs: await deeplyFulfilledObject( diff --git a/packages/orchestration/test/examples/stake-ica.contract.test.ts b/packages/orchestration/test/examples/stake-ica.contract.test.ts index 6845546c107..24698de61e2 100644 --- a/packages/orchestration/test/examples/stake-ica.contract.test.ts +++ b/packages/orchestration/test/examples/stake-ica.contract.test.ts @@ -41,7 +41,6 @@ const getChainTerms = ( chainId, hostConnectionId: agoricConns[chainId].counterparty.connection_id, controllerConnectionId: agoricConns[chainId].id, - bondDenom: stakingTokens[0].denom, icqEnabled: !!icqEnabled, }; }; diff --git a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts index ff54c9365e6..73600224e6e 100644 --- a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts +++ b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts @@ -372,7 +372,6 @@ test('getBalance and getBalances', async t => { { t.log('osmo1test mocked to have a 10 uosmo balance'); const account = await makeTestCOAKit({ - bondDenom: 'uosmo', chainId: 'osmosis-1', icqEnabled: true, }); @@ -390,7 +389,6 @@ test('getBalance and getBalances', async t => { { t.log('osmo1test1 mocked to have no balances'); const account = await makeTestCOAKit({ - bondDenom: 'uosmo', chainId: 'osmosis-1', icqEnabled: true, }); diff --git a/packages/orchestration/test/exos/make-test-coa-kit.ts b/packages/orchestration/test/exos/make-test-coa-kit.ts index 1f6335a1cb8..77cb2ca3ec2 100644 --- a/packages/orchestration/test/exos/make-test-coa-kit.ts +++ b/packages/orchestration/test/exos/make-test-coa-kit.ts @@ -46,7 +46,6 @@ export const prepareMakeTestCOAKit = ( chainId = 'cosmoshub-4', hostConnectionId = 'connection-0' as const, controllerConnectionId = 'connection-1' as const, - bondDenom = 'uatom', icqEnabled = false, } = {}) => { t.log('request account from orchestration service'); @@ -72,7 +71,7 @@ export const prepareMakeTestCOAKit = ( t.log('make a CosmosOrchestrationAccount'); const holder = makeCosmosOrchestrationAccount( - { chainAddress, bondDenom, localAddress, remoteAddress }, + { chainAddress, localAddress, remoteAddress }, { account: cosmosOrchAccount, storageNode: storageNode.makeChildNode(chainAddress.value), diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index 234e24ef6e1..d70287b1874 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -268,7 +268,6 @@ test('makeAccount() writes to storage', async t => { chainAddress: account.getAddress(), localAddress: account.getLocalAddress(), remoteAddress: account.getRemoteAddress(), - bondDenom: 'uatom', }, { account, @@ -318,7 +317,6 @@ test('withdrawRewards() on StakingAccountHolder formats message correctly', asyn chainAddress: account.getAddress(), localAddress: account.getLocalAddress(), remoteAddress: account.getRemoteAddress(), - bondDenom: 'uatom', }, { account, @@ -363,7 +361,6 @@ test(`delegate; redelegate using invitationMakers`, async t => { chainAddress: account.getAddress(), localAddress: account.getLocalAddress(), remoteAddress: account.getRemoteAddress(), - bondDenom: 'uatom', }, { account, @@ -454,7 +451,6 @@ test(`withdraw rewards using invitationMakers`, async t => { chainAddress: account.getAddress(), localAddress: account.getLocalAddress(), remoteAddress: account.getRemoteAddress(), - bondDenom: 'uatom', }, { account, @@ -503,7 +499,6 @@ test(`undelegate waits for unbonding period`, async t => { chainAddress: account.getAddress(), localAddress: account.getLocalAddress(), remoteAddress: account.getRemoteAddress(), - bondDenom: 'uatom', }, { account, From 5415202b3859ea1097a926fd23159cd5f0c67d82 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 16:07:53 -0400 Subject: [PATCH 5/9] refactor: initChain -> registerChain - updates ChainHubAdmin to be consistent with ChainHub --- packages/orchestration/src/exos/chain-hub-admin.js | 4 ++-- packages/orchestration/test/examples/send-anywhere.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/exos/chain-hub-admin.js b/packages/orchestration/src/exos/chain-hub-admin.js index 772d188b04b..e790257b3b4 100644 --- a/packages/orchestration/src/exos/chain-hub-admin.js +++ b/packages/orchestration/src/exos/chain-hub-admin.js @@ -35,7 +35,7 @@ export const prepareChainHubAdmin = (zone, chainHub) => { const makeCreatorFacet = zone.exo( 'ChainHub Admin', M.interface('ChainHub Admin', { - initChain: M.callWhen( + registerChain: M.callWhen( M.string(), CosmosChainInfoShape, ConnectionInfoShape, @@ -50,7 +50,7 @@ export const prepareChainHubAdmin = (zone, chainHub) => { * @param {CosmosChainInfo} chainInfo * @param {IBCConnectionInfo} connectionInfo - from Agoric chain */ - async initChain(chainName, chainInfo, connectionInfo) { + async registerChain(chainName, chainInfo, connectionInfo) { // when() because chainHub methods return vows. If this were inside // orchestrate() the membrane would wrap/unwrap automatically. const agoricChainInfo = await heapVowE.when( diff --git a/packages/orchestration/test/examples/send-anywhere.test.ts b/packages/orchestration/test/examples/send-anywhere.test.ts index 4ed9c9f8842..7a05060f767 100644 --- a/packages/orchestration/test/examples/send-anywhere.test.ts +++ b/packages/orchestration/test/examples/send-anywhere.test.ts @@ -108,7 +108,7 @@ test('send using arbitrary chain info', async t => { }, } as IBCConnectionInfo; const chainName = 'hot'; - await E(sendKit.creatorFacet).initChain( + await E(sendKit.creatorFacet).registerChain( chainName, hotChainInfo, agoricToHotConnection, From 06703a570149469dbf5dacd4b3bbbe704a863e78 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 16:10:10 -0400 Subject: [PATCH 6/9] feat: complete `depositAndDelegate` flow --- .../examples/staking-combinations.contract.js | 84 +++++++++----- .../examples/staking-combinations.flows.js | 60 +++++++--- .../src/exos/cosmos-orchestration-account.js | 6 +- .../snapshots/staking-combinations.test.ts.md | 25 ++++- .../staking-combinations.test.ts.snap | Bin 1464 -> 1624 bytes .../examples/staking-combinations.test.ts | 103 +++++++++++++----- packages/orchestration/test/supports.ts | 4 +- 7 files changed, 201 insertions(+), 81 deletions(-) diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js index 97275cdd111..209b328f674 100644 --- a/packages/orchestration/src/examples/staking-combinations.contract.js +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -5,25 +5,20 @@ * The primary offer result is a power for invitation makers that can perform * actions with an ICA account. */ +import { makeSharedStateRecord } from '@agoric/async-flow'; import { AmountShape } from '@agoric/ertp'; import { M } from '@endo/patterns'; import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js'; -import { CosmosOrchestrationInvitationMakersInterface } from '../exos/cosmos-orchestration-account.js'; +import { CosmosOrchestrationInvitationMakersI } from '../exos/cosmos-orchestration-account.js'; import { ChainAddressShape, DelegationShape } from '../typeGuards.js'; import { withOrchestration } from '../utils/start-helper.js'; import * as flows from './staking-combinations.flows.js'; +import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js'; /** * @import {GuestInterface} from '@agoric/async-flow'; - * @import {ContinuingOfferResult} from '@agoric/smart-wallet/src/types.js'; - * @import {TimerService} from '@agoric/time'; - * @import {LocalChain} from '@agoric/vats/src/localchain.js'; - * @import {NameHub} from '@agoric/vats'; - * @import {Vow} from '@agoric/vow'; - * @import {Remote} from '@agoric/internal'; * @import {Zone} from '@agoric/zone'; - * @import {CosmosInterchainService} from '../exos/exo-interfaces.js'; - * @import {OrchestrationTools} from '../utils/start-helper.js'; + * @import {OrchestrationTools, OrchestrationPowers} from '../utils/start-helper.js'; * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; * @import {AmountArg, ChainAddress, CosmosValidatorAddress} from '../types.js'; */ @@ -39,29 +34,43 @@ const emptyOfferShape = harden({ * Orchestration contract to be wrapped by withOrchestration for Zoe. * * @param {ZCF} zcf - * @param {{ - * agoricNames: Remote; - * localchain: Remote; - * orchestrationService: Remote; - * storageNode: Remote; + * @param {OrchestrationPowers & { * marshaller: Marshaller; - * timerService: Remote; * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools */ -const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { - const ExtraInvitationMakerInterface = M.interface('', { - DepositAndDelegate: M.call(M.array()).returns(M.promise()), - UndelegateAndTransfer: M.call( - M.arrayOf(DelegationShape), - ChainAddressShape, - ).returns(M.promise()), - }); +const contract = async ( + zcf, + privateArgs, + zone, + { orchestrateAll, zoeTools, chainHub }, +) => { + const contractState = makeSharedStateRecord( + /** + * @type {{ + * account: (OrchestrationAccount & LocalAccountMethods) | undefined; + * }} + */ { + localAccount: undefined, + }, + ); + + const StakingCombinationsInvitationMakersI = M.interface( + 'StakingCombinationsInvitationMakersI', + { + DepositAndDelegate: M.call().returns(M.promise()), + UndelegateAndTransfer: M.call( + M.arrayOf(DelegationShape), + ChainAddressShape, + ).returns(M.promise()), + }, + ); + /** @type {any} XXX async membrane */ const makeExtraInvitationMaker = zone.exoClass( - 'ContinuingInvitationExampleInvitationMakers', - ExtraInvitationMakerInterface, + 'StakingCombinationsInvitationMakers', + StakingCombinationsInvitationMakersI, /** @param {GuestInterface} account */ account => { return { account }; @@ -71,15 +80,22 @@ const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { const { account } = this.state; return zcf.makeInvitation( - (seat, validatorAddr, amountArg) => + /** + * @param {ZCFSeat} seat + * @param {{ validator: CosmosValidatorAddress }} offerArgs + */ + (seat, { validator }) => // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this - orchFns.depositAndDelegate(account, seat, validatorAddr, amountArg), + orchFns.depositAndDelegate(account, seat, validator), 'Deposit and delegate', undefined, { give: { Stake: AmountShape, }, + want: {}, + // user cannot exit their seat; contract must exit it. + exit: { waived: M.null() }, }, ); }, @@ -108,17 +124,25 @@ const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { /** @type {any} XXX async membrane */ const makeCombineInvitationMakers = prepareCombineInvitationMakers( zone, - CosmosOrchestrationInvitationMakersInterface, - ExtraInvitationMakerInterface, + CosmosOrchestrationInvitationMakersI, + StakingCombinationsInvitationMakersI, ); const orchFns = orchestrateAll(flows, { + contractState, makeCombineInvitationMakers, makeExtraInvitationMaker, flows, zcf, + zoeTools, }); + /** + * Provide invitations to contract deployer for registering assets and chains + * in the local ChainHub for this contract. + */ + const creatorFacet = prepareChainHubAdmin(zone, chainHub); + const publicFacet = zone.exo('publicFacet', undefined, { makeAccount() { return zcf.makeInvitation( @@ -130,7 +154,7 @@ const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { }, }); - return harden({ publicFacet }); + return harden({ publicFacet, creatorFacet }); }; export const start = withOrchestration(contract); diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js index 526f4ea7599..4a413add051 100644 --- a/packages/orchestration/src/examples/staking-combinations.flows.js +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -1,11 +1,19 @@ /** * @import {GuestInterface} from '@agoric/async-flow'; - * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress, ChainAddress} from '../types.js' + * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress, ChainAddress, LocalAccountMethods, OrchestrationAccountI} from '../types.js' * @import {ContinuingOfferResult, InvitationMakers} from '@agoric/smart-wallet/src/types.js'; * @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js'; * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; + * @import {ZoeTools} from '../utils/zoe-tools.js'; */ +import { mustMatch } from '@endo/patterns'; +import { makeError } from '@endo/errors'; +import { makeTracer } from '@agoric/internal'; +import { ChainAddressShape } from '../typeGuards.js'; + +const trace = makeTracer('StakingCombinationsFlows'); + /** * @satisfies {OrchestrationFlow} * @param {Orchestrator} orch @@ -38,43 +46,60 @@ harden(makeAccount); /** * @satisfies {OrchestrationFlow} - * @param {Orchestrator} _orch - * @param {object} _ctx + * @param {Orchestrator} orch + * @param {object} ctx + * @param {{ localAccount?: OrchestrationAccountI & LocalAccountMethods }} ctx.contractState + * @param {GuestInterface} ctx.zoeTools * @param {GuestInterface} account * @param {ZCFSeat} seat * @param {CosmosValidatorAddress} validator - * @param {AmountArg} amount - * @returns {Promise} + * @returns {Promise} */ export const depositAndDelegate = async ( - _orch, - _ctx, + orch, + { contractState, zoeTools }, account, seat, validator, - amount, ) => { - console.log('depositAndDelegate', account, seat, validator, amount); - // TODO deposit the amount - await account.delegate(validator, amount); - return 'guest depositAndDelegate complete'; + await null; + trace('depositAndDelegate', account, seat, validator); + mustMatch(validator, ChainAddressShape); + if (!contractState.localAccount) { + const agoricChain = await orch.getChain('agoric'); + contractState.localAccount = await agoricChain.makeAccount(); + } + const { give } = seat.getProposal(); + await zoeTools.localTransfer(seat, contractState.localAccount, give); + + // @ts-expect-error Type 'GuestInterface<() => HostInterface>' has no call signatures. + const address = account.getAddress(); + try { + await contractState.localAccount.transfer(give.Stake, address); + } catch (cause) { + // TODO, put funds back on user seat and exit + // https://github.com/Agoric/agoric-sdk/issues/9925 + throw makeError('ibc transfer failed', undefined, { cause }); + } + seat.exit(); + await account.delegate(validator, give.Stake); }; harden(depositAndDelegate); /** * @satisfies {OrchestrationFlow} - * @param {Orchestrator} _orch - * @param {object} _ctx + * @param {Orchestrator} orch + * @param {object} ctx * @param {GuestInterface} account * @param {{ * delegations: { amount: AmountArg; validator: CosmosValidatorAddress }[]; * destination: ChainAddress; * }} offerArgs - * @returns {Promise} + * @returns {Promise} */ export const undelegateAndTransfer = async ( - _orch, - _ctx, + orch, + ctx, account, { delegations, destination }, ) => { @@ -82,6 +107,5 @@ export const undelegateAndTransfer = async ( for (const { amount } of delegations) { await account.transfer(amount, destination); } - return 'guest undelegateAndTransfer complete'; }; harden(undelegateAndTransfer); diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index ba7aa5aa136..269aeae7eb8 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -112,7 +112,7 @@ const PUBLIC_TOPICS = { account: ['Staking Account holder status', M.any()], }; -export const CosmosOrchestrationInvitationMakersInterface = M.interface( +export const CosmosOrchestrationInvitationMakersI = M.interface( 'invitationMakers', { Delegate: M.call(ChainAddressShape, AmountArgShape).returns(M.promise()), @@ -131,7 +131,7 @@ export const CosmosOrchestrationInvitationMakersInterface = M.interface( Transfer: M.call().returns(M.promise()), }, ); -harden(CosmosOrchestrationInvitationMakersInterface); +harden(CosmosOrchestrationInvitationMakersI); /** * @param {Zone} zone @@ -199,7 +199,7 @@ export const prepareCosmosOrchestrationAccountKit = ( .returns(Vow$(M.record())), }), holder: IcaAccountHolderI, - invitationMakers: CosmosOrchestrationInvitationMakersInterface, + invitationMakers: CosmosOrchestrationInvitationMakersI, }, /** * @param {object} info diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md index 88d227b0e7a..8fa76401b61 100644 --- a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md @@ -25,13 +25,17 @@ Generated by [AVA](https://avajs.dev). unwrapMap: 'Alleged: weakMapStore', }, contract: { + 'ChainHub Admin_kindHandle': 'Alleged: kind', + 'ChainHub Admin_singleton': 'Alleged: ChainHub Admin', CombinedInvitationMakers_kindHandle: 'Alleged: kind', - ContinuingInvitationExampleInvitationMakers_kindHandle: 'Alleged: kind', + StakingCombinationsInvitationMakers_kindHandle: 'Alleged: kind', orchestration: { depositAndDelegate: { asyncFlow_kindHandle: 'Alleged: kind', endowments: { 0: { + contractState_kindHandle: 'Alleged: kind', + contractState_singleton: 'Alleged: contractState', flows: { depositAndDelegate_kindHandle: 'Alleged: kind', depositAndDelegate_singleton: 'Alleged: depositAndDelegate', @@ -44,6 +48,10 @@ Generated by [AVA](https://avajs.dev). makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', makeExtraInvitationMaker_kindHandle: 'Alleged: kind', makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + zoeTools: { + localTransfer_kindHandle: 'Alleged: kind', + localTransfer_singleton: 'Alleged: localTransfer', + }, }, }, }, @@ -51,6 +59,8 @@ Generated by [AVA](https://avajs.dev). asyncFlow_kindHandle: 'Alleged: kind', endowments: { 0: { + contractState_kindHandle: 'Alleged: kind', + contractState_singleton: 'Alleged: contractState', flows: { depositAndDelegate_kindHandle: 'Alleged: kind', depositAndDelegate_singleton: 'Alleged: depositAndDelegate', @@ -63,6 +73,10 @@ Generated by [AVA](https://avajs.dev). makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', makeExtraInvitationMaker_kindHandle: 'Alleged: kind', makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + zoeTools: { + localTransfer_kindHandle: 'Alleged: kind', + localTransfer_singleton: 'Alleged: localTransfer', + }, }, }, }, @@ -70,6 +84,8 @@ Generated by [AVA](https://avajs.dev). asyncFlow_kindHandle: 'Alleged: kind', endowments: { 0: { + contractState_kindHandle: 'Alleged: kind', + contractState_singleton: 'Alleged: contractState', flows: { depositAndDelegate_kindHandle: 'Alleged: kind', depositAndDelegate_singleton: 'Alleged: depositAndDelegate', @@ -82,6 +98,10 @@ Generated by [AVA](https://avajs.dev). makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', makeExtraInvitationMaker_kindHandle: 'Alleged: kind', makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + zoeTools: { + localTransfer_kindHandle: 'Alleged: kind', + localTransfer_singleton: 'Alleged: localTransfer', + }, }, }, }, @@ -96,7 +116,8 @@ Generated by [AVA](https://avajs.dev). Orchestrator_kindHandle: 'Alleged: kind', RemoteChainFacade_kindHandle: 'Alleged: kind', chainName: { - osmosis: 'Alleged: RemoteChainFacade public', + agoric: 'Alleged: LocalChainFacade public', + cosmoshub: 'Alleged: RemoteChainFacade public', }, ibcTools: { IBCTransferSenderKit_kindHandle: 'Alleged: kind', diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap index c18f9684acb9eb479eaa1e8660d461490505ee24..0419690848faed7bf50e70dc8e24ce45df0f447f 100644 GIT binary patch literal 1624 zcmV-e2B-N!RzVZt$XPfnowYzTG9xC-vkbnc6%8j;v!?fNucY2JjbvQ35t#c?7m6?G&WT0c)TXGZ8LSCQlnjGanD%_JVG}qq z2bjt&jaln(OcF9hfTlS{?K7r2Y49c2bkSo{RBk#JRUOxI(_=|+OI={LV^K%vrfbfJ z+UCe9zGN@2*~b zKcNayeux0KBEV=Dz`B4ByMRy>7>@$Sqk?->bdS9o1+GSc`%z%J8+g4NxX}&76kwYI zyr2jaLn1}wm;z7*xTF9-iSU8|_ghCKeZ+;@=1W>tH%rYb1(illaJO}{VlaoBO~6pg z+m94@vb*#~lsY$qMldp2H(kd==8{EgwRNe~$54rV5 zLmd|6Es}&3V(kvdF85m9RQJ#dvyRY3W-a&0a~@Q>g_@+gQMcGKtesamnR8|4hGHJ=Q$LR(^Vafa616m;B?@)HYGH>c|5Wkm#5oZ!BO;`~T~XX^y7 zmUWY9)8;ANad(;-H)BBqdV0>Gi@I4^=5vYc4R9t2v16<6fxaaNYmLoZoun!YyqOPp zeWm1OTdp3MTSi5E?FnvIxm}#~##t<>DzBSPaf%!3n-@OL?fbY&ja4W&Ft`dXpDTk& zs!}a*PY$(1;ep>~x^eyQImwwFh{z2bk#rPWA|swOfRT-|hh}_5e3UXij3z$APgppv8f! zaRHte;h|sSz&#N*EDi6lTk4&??Naw^tj2BKDVf@CX0Qr%mW4t+n0S>gvXw4!jc`9l5Zvfyk0yZ=A}lGvtR!$+gk_{c5I3whJYkU?9>e#m z8PX}gK_Rh&Dd3e9@J&ZG0i{0Ry*}VhA25{`qUn+DJ>@j8kOr=$fjem++z-t5 z1E2H*5Bh<_8G#}tQPeWPsSNOG26&JG(plhm7Wgs?C3Yv~rfS_A%(uQN!&`H9RCWj14~eYS=rtsfLH6h6|n5 zppfp52Z7H9f%}8N^bl}i2)Ho>Byz%#iA#qiH3#T9;C&I=D?+<}$pLqBz|K7IY99D1 z4@8E6y~BcQpR~%QVc^;@a9f0?rB${UfE@+EE&yK_fWHesX#{v}L~tFDR{3!R_;~~v z9~Gd3(rk?i)f$zrB7twr9Vnzy7g`LR?mqtSNjx{7z?W37?;78e_%2sIhGNI7RnR!b zIMaMi-#Gs%#70`%@bI$Oe+;y WVMRLcFstbPJo^td|1&qW9RL6d>i?nu literal 1464 zcmV;p1xNZpRzVURIGxx3WA`bp!lftO~j&53id@%AB1|LFFsb?Np^Sk zoFrtT?Njz?cmD7D=AZv|{z=}QsXD@(^DbOs(v}Wmo}{j{Jx&|mtdQP?OO{0?Jw5zf z6iKu`#305bfWrV@0`N6}UjW8%>9v5#Fa?(`HKlL460l^2YtAxHx>VYN`{qKVYmS{2^PYco zZ{TY5ykM;WW}b`yRs?u20^EoI+oM1&3Va>~{)hs*V!(6^I2!|Ak0}9-^aj)*iSJ^- zwHUB94m=YF-iQO&;tDOH(o(|-U@QT+3E*-9h$ex(N#OY;@MaSDBB@A}REgNtByc?m zj2OUaLxB$~@LubPRu^d{rz7K5-R9%%?1U|eVTJD5yyh?|cpEU(VfP{xozgzL<)tln zSPwQ&Hn~qc!RKAtXsoF^dlW`>1?@35bp$l9+9ZcXZCVPKg>_?OT2jfvJVlO>SB!4u zmlk`h$;|__#@thMj=78d@kszx=%6O4?KEAs2)Po3%s66xQn*K&(iC-eQp;AV>3cav z8|^FQj%)Lb&P~yV9|FJP;tKih2~n@woLPtXSzG$8J>|O^Hee4;2rg~jw0W&7bzp(k z8xC8Cc8-wWjFpE^&#ea=tto0wQj>+AqU9i0E3{IF8i4t6G!&itCxllQUgb#8&&s%I ziYAwpgTh(cpzhJ!d03d#SwV6gC&s9+H!(|XK1oe#g>H(Wt}7z+(`-J*>O!(`x6ctW znhG3TW{dqetux=WFJLn4s(D-pXR(2v4p`C+YR)maWXz&=c#eZ1^;7 zIw|H4amidx9d8|JDf{<5A#G50C zDd5MHau!)jD67RjptOuA0z*ir}i#;*d#a+6z2#BpvNcPaN~S_euNgeWOX`ps^8 z?cduvNz#gZX>u%Ecv}zcn`qx>nsJM{>*;E{Y93==(~;|~Urj)pgCiZjid{B5BS*U( zx3(uTNc2V;xYl8UHX5Dl@1gmC&WMrhV~W$jd4$tHIvS0w1cdf|dy4 zyY*$jE%HFX@YhO)UAbO^K@wIDIFkb|}}lEY>83eu` z1WH4IH3Ymf1pG7v+&ipLi`oE8C?VORuX3T^HkOgl;yKz;SZ;Uhea8%Jj-eO)Aa|8_ z%%0`6N1?LpY8uv$t;?%u9*uQGR^Es=Qnrz@jg;NSlre`^nX|CD{rC7vmDXys#wv71 SGPmOY*Ww@kOFSOT7ytnN7|BTh diff --git a/packages/orchestration/test/examples/staking-combinations.test.ts b/packages/orchestration/test/examples/staking-combinations.test.ts index 8c19002dda6..a7540dd8a0c 100644 --- a/packages/orchestration/test/examples/staking-combinations.test.ts +++ b/packages/orchestration/test/examples/staking-combinations.test.ts @@ -1,6 +1,9 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { inspectMapStore } from '@agoric/internal/src/testing-utils.js'; +import { + eventLoopIteration, + inspectMapStore, +} from '@agoric/internal/src/testing-utils.js'; import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E } from '@endo/far'; import path from 'path'; @@ -13,6 +16,7 @@ import { protoMsgMocks, UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js'; import { commonSetup } from '../supports.js'; import { buildMsgResponseString, + buildVTransferEvent, parseOutgoingTxPacket, } from '../../tools/ibc-mocks.js'; @@ -25,9 +29,9 @@ type StartFn = test('start', async t => { const { bootstrap: { timer, vowTools: vt }, - brands: { ist }, - mocks: { ibcBridge }, - utils: { inspectDibcBridge }, + brands: { bld }, + mocks: { ibcBridge, transferBridge }, + utils: { inspectDibcBridge, pourPayment }, commonPrivateArgs, } = await commonSetup(t); @@ -40,9 +44,10 @@ test('start', async t => { const installation: Installation = await bundleAndInstall(contractFile); - const { publicFacet } = await E(zoe).startInstance( + const { publicFacet, creatorFacet } = await E(zoe).startInstance( installation, - { Stable: ist.issuer }, + // TODO use atom https://github.com/Agoric/agoric-sdk/issues/9966 + { Stake: bld.issuer }, {}, commonPrivateArgs, ); @@ -59,7 +64,7 @@ test('start', async t => { {}, {}, { - chainName: 'osmosis', + chainName: 'cosmoshub', }, ); @@ -73,20 +78,65 @@ test('start', async t => { }, }); - // Here the account would get funded through Cosmos native operations. + // use Deposit and Delegate to fund the ICA account and delegate + const depositAndDelegateInv = await E( + result.invitationMakers, + ).DepositAndDelegate(); + + // ensure there's a denom for Stake brand + // TODO use atom https://github.com/Agoric/agoric-sdk/issues/9966 + await E(creatorFacet).registerAsset('ubld', { + chainName: 'agoric', + baseName: 'agoric', + baseDenom: 'ubld', + brand: bld.brand, + }); - // Delegate the funds like so, but don't bother executing the offer - // because the balances aren't tracked. - const delegateInv = await E(result.invitationMakers).Delegate( - { value: '10', encoding: 'bech32', chainId: 'osmosis' }, + ibcBridge.addMockAck( + // Delegate 100 ubld from cosmos1test to cosmosvaloper1test observed in console + 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xVS0l5OWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxelowUmxiR1ZuWVhSbEVpNEtDMk52YzIxdmN6RjBaWE4wRWhKamIzTnRiM04yWVd4dmNHVnlNWFJsYzNRYUN3b0VkV0pzWkJJRE1UQXciLCJtZW1vIjoiIn0=', + protoMsgMocks.delegate.ack, + ); + + // TODO use atom https://github.com/Agoric/agoric-sdk/issues/9966 + const stakeAmount = bld.make(100n); + + const depositAndDelegateUserSeat = await E(zoe).offer( + depositAndDelegateInv, + { + give: { Stake: stakeAmount }, + exit: { waived: null }, + }, { - denom: 'osmo', - value: 10n, + Stake: await pourPayment(stakeAmount), + }, + { + validator: { + chainId: 'cosmoshub', + value: 'cosmosvaloper1test', + encoding: 'bech32', + }, }, ); - t.like(await E(zoe).getInvitationDetails(delegateInv), { - description: 'Delegate', - }); + + // wait for targetApp to exist + await eventLoopIteration(); + // similar transfer ack + await E(transferBridge).fromBridge( + buildVTransferEvent({ + receiver: 'cosmos1test', + sender: 'agoric1fakeLCAAddress', + amount: 100n, + denom: 'ubld', + sourceChannel: 'channel-5', + sequence: 1n, + }), + ); + + const depositAndDelegateResult = await vt.when( + E(depositAndDelegateUserSeat).getOfferResult(), + ); + t.is(depositAndDelegateResult, undefined); // Undelegate the funds using the guest flow ibcBridge.addMockAck( @@ -95,16 +145,16 @@ test('start', async t => { protoMsgMocks.undelegate.ack, ); ibcBridge.addMockAck( - // observed in console - 'eyJ0eXBlIjoxLCJkYXRhIjoiQ25nS0tTOXBZbU11WVhCd2JHbGpZWFJwYjI1ekxuUnlZVzV6Wm1WeUxuWXhMazF6WjFSeVlXNXpabVZ5RWtzS0NIUnlZVzV6Wm1WeUVnbGphR0Z1Ym1Wc0xUQWFEQW9GZFc5emJXOFNBekV3TUNJTFkyOXpiVzl6TVhSbGMzUXFEMk52YzIxdmN6RnlaV05sYVhabGNqSUFPSUNRK0lTZ21nRT0iLCJtZW1vIjoiIn0=', + // MsgTransfer from cosmos1test to osmo1receiver observed in console + 'eyJ0eXBlIjoxLCJkYXRhIjoiQ25nS0tTOXBZbU11WVhCd2JHbGpZWFJwYjI1ekxuUnlZVzV6Wm1WeUxuWXhMazF6WjFSeVlXNXpabVZ5RWtzS0NIUnlZVzV6Wm1WeUVndGphR0Z1Ym1Wc0xURTBNUm9NQ2dWMWIzTnRieElETVRBd0lndGpiM050YjNNeGRHVnpkQ29OYjNOdGJ6RnlaV05sYVhabGNqSUFPSUNRK0lTZ21nRT0iLCJtZW1vIjoiIn0=', buildMsgResponseString(MsgTransferResponse, { sequence: 0n }), ); const destination = { - chainId: 'cosmoshub-4', - value: 'cosmos1receiver', + chainId: 'osmosis-1', + value: 'osmo1receiver', encoding: 'bech32', }; - const undelegateInvVow = await E( + const undelegateAndTransferInv = await E( result.invitationMakers, ).UndelegateAndTransfer( [ @@ -119,12 +169,11 @@ test('start', async t => { ], destination, ); - const undelegateInv = await vt.when(undelegateInvVow); - t.like(await E(zoe).getInvitationDetails(undelegateInv), { + t.like(await E(zoe).getInvitationDetails(undelegateAndTransferInv), { description: 'Undelegate and transfer', }); - const undelegateUserSeat = await E(zoe).offer(undelegateInv); + const undelegateUserSeat = await E(zoe).offer(undelegateAndTransferInv); // Wait for the unbonding period timer.advanceBy(UNBOND_PERIOD_SECONDS * 1000n); @@ -132,7 +181,7 @@ test('start', async t => { const undelegateResult = await vt.when( E(undelegateUserSeat).getOfferResult(), ); - t.is(undelegateResult, 'guest undelegateAndTransfer complete'); + t.is(undelegateResult, undefined); const { bridgeDowncalls } = await inspectDibcBridge(); const { messages } = parseOutgoingTxPacket( @@ -142,7 +191,7 @@ test('start', async t => { t.like( transferMsg, { - receiver: 'cosmos1receiver', + receiver: 'osmo1receiver', sourcePort: 'transfer', token: { amount: '100', diff --git a/packages/orchestration/test/supports.ts b/packages/orchestration/test/supports.ts index 7d8b5584cd8..e5832ebe2a5 100644 --- a/packages/orchestration/test/supports.ts +++ b/packages/orchestration/test/supports.ts @@ -152,7 +152,9 @@ export const commonSetup = async (t: ExecutionContext) => { const chainHub = makeChainHub(agoricNames, vowTools); /** - * Register BLD if it's not already registered + * Register BLD if it's not already registered. + * Does not work with `withOrchestration` contracts, as these have their own + * ChainHub. Use `ChainHubAdmin` instead. */ const registerAgoricBld = () => { if (!chainHub.getAsset('ubld')) { From 0107842bef0548f4be2fdb9247caf5dc52cd33a0 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 16:32:13 -0400 Subject: [PATCH 7/9] docs: staking-combinations USAGE --- packages/orchestration/USAGE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/USAGE.md b/packages/orchestration/USAGE.md index 975ecbb9f0a..472e39acbfe 100644 --- a/packages/orchestration/USAGE.md +++ b/packages/orchestration/USAGE.md @@ -17,7 +17,7 @@ See [`src/examples`](src/examples) | [send-anywhere](/packages/orchestration/src/examples/send-anywhere.contract.js) | Ready 🟢 | Allows sending payments (tokens) over IBC to another chain. | - `LocalOrchestrationAccoun`t
- `Vtransfer` (IBC Hooks) | | [stakeBld](/packages/orchestration/src/examples/stakeBld.contract.js) | Ready 🟢 | Returns a `LocalOrchestrationAccount` that can perform staking actions. | - `LocalOrchestrationAccount` | Ready 🟢 | | [stakeIca](/packages/orchestration/src/examples/stakeIca.contract.js) | Ready 🟢 | Returns a `CosmosOrchestrationAccount` that can perform staking actions. | - `CosmosOrchestrationAccount` | Ready 🟢 | -| [staking-combinations](/packages/orchestration/src/examples/staking-combinations.contract.js) | Under Construction 🚧 | Combines actions into a single offer flow and demonstrates writing continuing offers. | - `CosmosOrchestrationAccount`
- `CombineInvitationMakers`
- Continuing Offers | +| [staking-combinations](/packages/orchestration/src/examples/staking-combinations.contract.js) | Ready 🟢 | Combines actions into a single offer flow and demonstrates writing continuing offers. | - `CosmosOrchestrationAccount`
- `CombineInvitationMakers`
- Continuing Offers | | [swap](/packages/orchestration/src/examples/swap.contract.js) | Under Construction 🚧 | Demonstrates asset swapping on an external chain. | - `CosmosOrchestrationAccount`
- `ChainHub` | | [unbond](/packages/orchestration/src/examples/unbond.contract.js) | Under Construction 🚧 | Undelegates tokens for an ICA and liquid stakes them. | - `CosmosOrchestrationAccount` | @@ -43,4 +43,4 @@ See [`src/examples`](src/examples) | [stakeBld](/packages/orchestration/src/examples/stakeBld.contract.js) | - Everything*, created before e2e test suite
- Consider folding under generic "stake" contract, once [interfaces are the same](https://github.com/Agoric/agoric-sdk/blob/1976c502bcaac2e7d21f42b30447671a61053236/packages/orchestration/src/exos/local-orchestration-account.js#L487)| | [swap](/packages/orchestration/src/examples/swap.contract.js) | - Everything - contract incomplete ([#8863](https://github.com/Agoric/agoric-sdk/issues/8863)) | | [unbond](/packages/orchestration/src/examples/unbond.contract.js) | - Everything - contract incomplete ([#9782](https://github.com/Agoric/agoric-sdk/issues/9782)) | -| +| [staking-combinations](/packages/orchestration/src/examples/staking-combinations.contract.js) | Only tested via [unit tests](/packages/orchestration/src/examples/staking-combinations.contract.js) | From 07fcaba0e85a5ff4b8f64d8c486f8f526441ce2c Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Sat, 7 Sep 2024 16:34:45 -0400 Subject: [PATCH 8/9] types: GuestInterface supports synchronous returns --- packages/async-flow/src/types.d.ts | 8 ++- packages/async-flow/test/types.test-d.ts | 61 +++++++++++++++++-- .../examples/staking-combinations.flows.js | 1 - 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/packages/async-flow/src/types.d.ts b/packages/async-flow/src/types.d.ts index a2d28b6c7ef..5541611961c 100644 --- a/packages/async-flow/src/types.d.ts +++ b/packages/async-flow/src/types.d.ts @@ -68,9 +68,11 @@ export type GuestInterface = { ? (...args: Parameters) => Promise : T[K] extends HostAsyncFuncWrapper ? GuestOf - : T[K] extends object - ? GuestInterface - : T[K]; + : T[K] extends (...args: any[]) => infer R + ? T[K] + : T[K] extends object + ? GuestInterface + : T[K]; }; /** diff --git a/packages/async-flow/test/types.test-d.ts b/packages/async-flow/test/types.test-d.ts index 9c393769464..399a766743d 100644 --- a/packages/async-flow/test/types.test-d.ts +++ b/packages/async-flow/test/types.test-d.ts @@ -1,8 +1,13 @@ import { expectType } from 'tsd'; -import type { Zone } from '@agoric/base-zone'; import type { Vow, VowTools } from '@agoric/vow'; -import type { HostOf, GuestOf } from '../src/types.js'; +import type { + HostOf, + GuestOf, + HostInterface, + GuestInterface, +} from '../src/types.js'; +const castable: unknown = null; const vt: VowTools = null as any; const sumVow = (a: number, b: number) => vt.asVow(() => a + b); @@ -10,13 +15,59 @@ const sumVow = (a: number, b: number) => vt.asVow(() => a + b); const sumPromise = (a: number, b: number) => Promise.resolve(a + b); expectType<(p1: number, p2: number) => Promise>( - null as unknown as GuestOf, + castable as GuestOf, ); expectType<(p1: number, p2: number) => Vow>( - null as unknown as HostOf, + castable as HostOf, ); expectType<(p1: number, p2: number) => Vow>( // @ts-expect-error incompatible return type - null as unknown as HostOf, + castable as HostOf, +); + +// Test HostInterface and GuestInterface with an exoClass object +type ExoAPIBase = { + getValue: () => number; + setValue: (value: number) => void; + getCopyData: () => Record[]; + // TODO include `getRemote() => Guarded<...>`, since durable exos are passable +}; +type ExoGuestAPI = ExoAPIBase & { + getValueAsync: () => Promise; +}; + +type ExoHostAPI = ExoAPIBase & { + getValueAsync: () => Vow; +}; + +expectType< + ExoAPIBase & { + getValueAsync: () => Vow; + } +>(castable as HostInterface); +expectType< + ExoAPIBase & { + getValueAsync: () => Promise; + } +>(castable as GuestInterface); + +// Test HostInterface and GuestInterface with classKit (nested) objects +expectType<{ + facet: ExoAPIBase & { + getValueAsync: () => Vow; + }; +}>( + castable as HostInterface<{ + facet: ExoGuestAPI; + }>, +); +expectType<{ + facet: ExoAPIBase & { + getValueAsync: () => Promise; + }; +}>( + castable as GuestInterface<{ + facet: ExoHostAPI; + }>, ); diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js index 4a413add051..2961ee96dac 100644 --- a/packages/orchestration/src/examples/staking-combinations.flows.js +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -72,7 +72,6 @@ export const depositAndDelegate = async ( const { give } = seat.getProposal(); await zoeTools.localTransfer(seat, contractState.localAccount, give); - // @ts-expect-error Type 'GuestInterface<() => HostInterface>' has no call signatures. const address = account.getAddress(); try { await contractState.localAccount.transfer(give.Stake, address); From 2a50005b5a475581b8095453b8417eb38c0f4137 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 10 Sep 2024 15:05:35 -0400 Subject: [PATCH 9/9] chore: update retry timeouts - updates `MAKE_ACCOUNT_AND_QUERY_BALANCE_TIMEOUT` after observing: - incoming offer at "2024-09-10T17:40:50.076Z" and offer result at "2024-09-10T17:42:16.158Z" - in https://github.com/Agoric/agoric-sdk/actions/runs/10797610413/job/29949245444?pr=10053#step:12:3673 --- multichain-testing/test/config.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/multichain-testing/test/config.ts b/multichain-testing/test/config.ts index 77a07b3396d..975c0d25e34 100644 --- a/multichain-testing/test/config.ts +++ b/multichain-testing/test/config.ts @@ -1,7 +1,7 @@ import type { RetryOptions } from '../tools/sleep.js'; /** - * Wait 90 seconds to ensure staking rewards are available. + * Wait up to 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 @@ -18,7 +18,7 @@ export const STAKING_REWARDS_TIMEOUT: RetryOptions = { }; /** - * Wait 2 minutes to ensure: + * Wait up to 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) @@ -32,11 +32,12 @@ export const AUTO_STAKE_IT_DELEGATIONS_TIMEOUT: RetryOptions = { }; /** - * Wait about 90s to ensure: + * Wait up to 2 minutes to ensure: * - ICA Account is created * - ICQ Connection is established (in some instances) * - Query is executed (sometimes local, sometimes via ICQ) */ export const MAKE_ACCOUNT_AND_QUERY_BALANCE_TIMEOUT: RetryOptions = { - maxRetries: 25, + retryIntervalMs: 5000, + maxRetries: 24, };