From 0a7338b328d79d80e754ffc9fb2d8474d0ff2292 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 4 Sep 2024 13:53:34 -0700 Subject: [PATCH 1/4] test: trace network-fakes --- packages/orchestration/test/network-fakes.ts | 31 ++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/test/network-fakes.ts b/packages/orchestration/test/network-fakes.ts index 8df40c2a310..9d73594f2a3 100644 --- a/packages/orchestration/test/network-fakes.ts +++ b/packages/orchestration/test/network-fakes.ts @@ -1,5 +1,6 @@ import { VowTools } from '@agoric/vow'; import { + base64ToBytes, prepareEchoConnectionKit, prepareLoopbackProtocolHandler, prepareNetworkPowers, @@ -19,11 +20,13 @@ import { prepareCallbacks as prepareIBCCallbacks, prepareIBCProtocol, } from '@agoric/vats/src/ibc.js'; -import { BridgeId } from '@agoric/internal'; +import { BridgeId, makeTracer } from '@agoric/internal'; import { E, Far } from '@endo/far'; import type { Guarded } from '@endo/exo'; import { defaultMockAckMap, errorAcknowledgments } from './ibc-mocks.js'; +const trace = makeTracer('NetworkFakes'); + /** * Mimic IBC Channel version negotation * @@ -152,6 +155,7 @@ export const makeFakeIBCBridge = ( zone: Zone, ): Guarded< ScopedBridgeManagerMethods<'dibc'> & { + addMockAck: (msgData: string, ackData: string) => void; setMockAck: (mockAckMap: Record) => void; setAddressPrefix: (addressPrefix: string) => void; inspectDibcBridge: () => { @@ -209,6 +213,11 @@ export const makeFakeIBCBridge = ( return zone.exo('Fake IBC Bridge Manager', undefined, { getBridgeId: () => BridgeId.DIBC, toBridge: async obj => { + trace( + 'toBridge', + obj, + obj.packet?.data ? base64ToBytes(obj.packet.data) : undefined, + ); if (obj.type === 'IBC_METHOD') { bridgeDowncalls = bridgeDowncalls.concat(obj); switch (obj.method) { @@ -240,10 +249,19 @@ export const makeFakeIBCBridge = ( return undefined; } case 'sendPacket': { + const mockAckMapHasData = obj.packet.data in mockAckMap; + if (!mockAckMapHasData) { + trace( + 'sendPacket acking err bc no mock ack for data:', + obj.packet.data, + base64ToBytes(obj.packet.data), + ); + } const ackEvent = ibcBridgeMocks.acknowledgementPacket(obj, { sequence: ibcSequenceNonce, - acknowledgement: - mockAckMap?.[obj.packet.data] || errorAcknowledgments.error5, + acknowledgement: mockAckMapHasData + ? mockAckMap[obj.packet.data] + : errorAcknowledgments.error5, }); bridgeEvents = bridgeEvents.concat(ackEvent); ibcSequenceNonce += 1; @@ -257,6 +275,7 @@ export const makeFakeIBCBridge = ( return undefined; }, fromBridge: async obj => { + trace('fromBridge', obj); bridgeEvents = bridgeEvents.concat(obj); if (!bridgeHandler) throw Error('no handler!'); return bridgeHandler.fromBridge(obj); @@ -276,14 +295,20 @@ export const makeFakeIBCBridge = ( * @param ackMap */ setMockAck: (ackMap: typeof mockAckMap) => { + trace('setMockAck', ackMap); mockAckMap = ackMap; }, + addMockAck: (msgData: string, ackData: string) => { + trace('addMockAck', msgData, ackData); + mockAckMap[msgData] = ackData; + }, /** * Set a new bech32 prefix for the mocked ICA channel. Defaults to `cosmos`. * * @param newPrefix */ setAddressPrefix: (newPrefix: typeof bech32Prefix) => { + trace('setAddressPrefix', newPrefix); bech32Prefix = newPrefix; }, /** From 19fbaf1e6bce347bcdfd9aceec6a0f9b231c00dd Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 31 Jul 2024 21:10:19 -0400 Subject: [PATCH 2/4] feat: combine-invitation-makers exo - Takes two or more InvitationMaker exos and combines them into a new one. - Useful for combining exos that are already instantiated --- .../src/exos/combine-invitation-makers.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/orchestration/src/exos/combine-invitation-makers.js diff --git a/packages/orchestration/src/exos/combine-invitation-makers.js b/packages/orchestration/src/exos/combine-invitation-makers.js new file mode 100644 index 00000000000..e7b647e3ba6 --- /dev/null +++ b/packages/orchestration/src/exos/combine-invitation-makers.js @@ -0,0 +1,53 @@ +import { M } from '@endo/patterns'; +import { + prepareGuardedAttenuator, + makeSyncMethodCallback, +} from '@agoric/internal/src/callback.js'; +import { getMethodNames } from '@agoric/internal'; + +/** + * @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js'; + * @import {Zone} from '@agoric/zone'; + */ + +// TODO use a helper from Endo https://github.com/endojs/endo/issues/2448 +/** + * Takes two or more InvitationMaker exos and combines them into a new one. + * + * @param {Zone} zone + * @param {import('@endo/patterns').InterfaceGuard[]} interfaceGuards + */ +export const prepareCombineInvitationMakers = (zone, ...interfaceGuards) => { + const methodGuards = interfaceGuards.map(ig => ig.payload.methodGuards); + const CombinedInterfaceGuard = M.interface( + 'CombinedInvitationMakers interface', + Object.assign({}, ...methodGuards), + ); + + const mixin = prepareGuardedAttenuator(zone, CombinedInterfaceGuard, { + tag: 'CombinedInvitationMakers', + }); + + /** + * @template {InvitationMakers[]} IM + * @param {IM} invitationMakers + * @returns {IM[number]} + */ + const combineInvitationMakers = (...invitationMakers) => { + const overrides = {}; + for (const invMakers of invitationMakers) { + // remove '__getInterfaceGuard__', '__getMethodNames__' + const names = getMethodNames(invMakers).filter(n => !n.startsWith('__')); + for (const key of names) { + overrides[key] = makeSyncMethodCallback(invMakers, key); + } + } + return mixin({ + overrides, + }); + }; + + return combineInvitationMakers; +}; + +/** @typedef {ReturnType} MakeCombineInvitationMakers */ From d1fa49ac04804b05875789dad2acaf6eb8ba1d9a Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 5 Sep 2024 14:35:17 -0700 Subject: [PATCH 3/4] feat: export CosmosOrchestrationInvitationMakersInterface --- .../src/exos/cosmos-orchestration-account.js | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index ecfa545b048..d3bb08e7af7 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -111,6 +111,27 @@ const PUBLIC_TOPICS = { account: ['Staking Account holder status', M.any()], }; +export const CosmosOrchestrationInvitationMakersInterface = M.interface( + 'invitationMakers', + { + Delegate: M.call(ChainAddressShape, AmountArgShape).returns(M.promise()), + Redelegate: M.call( + ChainAddressShape, + ChainAddressShape, + AmountArgShape, + ).returns(M.promise()), + WithdrawReward: M.call(ChainAddressShape).returns(M.promise()), + Undelegate: M.call(M.arrayOf(DelegationShape)).returns(M.promise()), + DeactivateAccount: M.call().returns(M.promise()), + ReactivateAccount: M.call().returns(M.promise()), + TransferAccount: M.call().returns(M.promise()), + Send: M.call().returns(M.promise()), + SendAll: M.call().returns(M.promise()), + Transfer: M.call().returns(M.promise()), + }, +); +harden(CosmosOrchestrationInvitationMakersInterface); + /** * @param {Zone} zone * @param {object} powers @@ -177,24 +198,7 @@ export const prepareCosmosOrchestrationAccountKit = ( .returns(Vow$(M.record())), }), holder: IcaAccountHolderI, - invitationMakers: M.interface('invitationMakers', { - Delegate: M.call(ChainAddressShape, AmountArgShape).returns( - M.promise(), - ), - Redelegate: M.call( - ChainAddressShape, - ChainAddressShape, - AmountArgShape, - ).returns(M.promise()), - WithdrawReward: M.call(ChainAddressShape).returns(M.promise()), - Undelegate: M.call(M.arrayOf(DelegationShape)).returns(M.promise()), - DeactivateAccount: M.call().returns(M.promise()), - ReactivateAccount: M.call().returns(M.promise()), - TransferAccount: M.call().returns(M.promise()), - Send: M.call().returns(M.promise()), - SendAll: M.call().returns(M.promise()), - Transfer: M.call().returns(M.promise()), - }), + invitationMakers: CosmosOrchestrationInvitationMakersInterface, }, /** * @param {object} info From 26893723cb1d2a402d819a35c209e0a6a211127d Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 6 Sep 2024 08:54:16 -0700 Subject: [PATCH 4/4] test: staking-combinations --- .../examples/staking-combinations.contract.js | 138 ++++++++++++++++++ .../examples/staking-combinations.flows.js | 83 +++++++++++ .../snapshots/staking-combinations.test.ts.md | 116 +++++++++++++++ .../staking-combinations.test.ts.snap | Bin 0 -> 1464 bytes .../examples/staking-combinations.test.ts | 110 ++++++++++++++ 5 files changed, 447 insertions(+) create mode 100644 packages/orchestration/src/examples/staking-combinations.contract.js create mode 100644 packages/orchestration/src/examples/staking-combinations.flows.js create mode 100644 packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md create mode 100644 packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap create mode 100644 packages/orchestration/test/examples/staking-combinations.test.ts diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js new file mode 100644 index 00000000000..b91eb454d4c --- /dev/null +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -0,0 +1,138 @@ +/** + * @file This contract demonstrates the continuing invitation pattern with async + * flows. + * + * The primary offer result is a power for invitation makers that can perform + * actions with an ICA account. + */ +import { AmountShape } from '@agoric/ertp'; +import { VowShape } from '@agoric/vow'; +import { M } from '@endo/patterns'; +import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js'; +import { CosmosOrchestrationInvitationMakersInterface } from '../exos/cosmos-orchestration-account.js'; +import { withOrchestration } from '../utils/start-helper.js'; +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'; + * @import {NameHub} from '@agoric/vats'; + * @import {Vow} from '@agoric/vow'; + * @import {Remote} from '@agoric/internal'; + * @import {Zone} from '@agoric/zone'; + * @import {CosmosInterchainService} from '../exos/cosmos-interchain-service.js'; + * @import {OrchestrationTools} from '../utils/start-helper.js'; + * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; + */ + +const emptyOfferShape = harden({ + // Nothing to give; the funds are deposited offline + give: {}, + want: {}, // UNTIL https://github.com/Agoric/agoric-sdk/issues/2230 + exit: M.any(), +}); + +/** + * Orchestration contract to be wrapped by withOrchestration for Zoe. + * + * @param {ZCF} zcf + * @param {{ + * agoricNames: Remote; + * localchain: Remote; + * orchestrationService: Remote; + * storageNode: Remote; + * marshaller: Marshaller; + * timerService: Remote; + * }} privateArgs + * @param {Zone} zone + * @param {OrchestrationTools} tools + */ +const contract = async ( + zcf, + privateArgs, + zone, + { orchestrateAll, vowTools }, +) => { + const ExtraInvitationMakerInterface = M.interface('', { + DepositAndDelegate: M.call(M.array()).returns(VowShape), + UndelegateAndTransfer: M.call(M.array()).returns(VowShape), + }); + /** @type {any} XXX async membrane */ + const makeExtraInvitationMaker = zone.exoClass( + 'ContinuingInvitationExampleInvitationMakers', + ExtraInvitationMakerInterface, + /** @param {GuestInterface} account */ + account => { + return { account }; + }, + { + DepositAndDelegate() { + const { account } = this.state; + + const invP = zcf.makeInvitation( + (seat, validatorAddr, amountArg) => + // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this + orchFns.depositAndDelegate(account, seat, validatorAddr, amountArg), + 'Deposit and delegate', + undefined, + { + give: { + Stake: AmountShape, + }, + }, + ); + + return vowTools.watch(invP); + }, + /** + * @param {Omit[]} delegations + */ + UndelegateAndTransfer(delegations) { + const { account } = this.state; + + const invP = zcf.makeInvitation( + // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this + () => orchFns.undelegateAndTransfer(account, delegations), + 'Undelegate and transfer', + undefined, + emptyOfferShape, + ); + + return vowTools.watch(invP); + }, + }, + ); + + /** @type {any} XXX async membrane */ + const makeCombineInvitationMakers = prepareCombineInvitationMakers( + zone, + CosmosOrchestrationInvitationMakersInterface, + ExtraInvitationMakerInterface, + ); + + const orchFns = orchestrateAll(flows, { + makeCombineInvitationMakers, + makeExtraInvitationMaker, + flows, + zcf, + }); + + const publicFacet = zone.exo('publicFacet', undefined, { + makeAccount() { + return zcf.makeInvitation( + orchFns.makeAccount, + 'Make an ICA account', + undefined, + emptyOfferShape, + ); + }, + }); + + return harden({ publicFacet }); +}; + +export const start = withOrchestration(contract); +harden(start); diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js new file mode 100644 index 00000000000..9361bbf5176 --- /dev/null +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -0,0 +1,83 @@ +/** + * @import {GuestInterface} from '@agoric/async-flow'; + * @import {Orchestrator, OrchestrationFlow, OrchestrationAccount, OrchestrationAccountI, StakingAccountActions, 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'; + */ + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {{ + * makeCombineInvitationMakers: MakeCombineInvitationMakers; + * makeExtraInvitationMaker: (account: any) => InvitationMakers; + * }} ctx + * @param {ZCFSeat} _seat + * @param {{ chainName: string }} offerArgs + * @returns {Promise} + */ +export const makeAccount = async (orch, ctx, _seat, { chainName }) => { + const chain = await orch.getChain(chainName); + const account = await chain.makeAccount(); + + const extraMakers = ctx.makeExtraInvitationMaker(account); + + /** @type {ContinuingOfferResult} */ + const result = await account.asContinuingOffer(); + + return { + ...result, + invitationMakers: ctx.makeCombineInvitationMakers( + extraMakers, + result.invitationMakers, + ), + }; +}; +harden(makeAccount); + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {object} ctx + * @param {GuestInterface} account + * @param {ZCFSeat} seat + * @param {CosmosValidatorAddress} validator + * @param {AmountArg} amount + * @returns {Promise} + */ +export const depositAndDelegate = async ( + orch, + ctx, + account, + seat, + validator, + amount, +) => { + console.log('depositAndDelegate', account, seat, validator, amount); + // TODO deposit the amount + await account.delegate(validator, amount); + return 'guest depositAndDelegate complete'; +}; +harden(depositAndDelegate); + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {object} ctx + * @param {GuestInterface} account + * @param {Omit[]} delegations + * @returns {Promise} + */ +export const undelegateAndTransfer = async ( + orch, + ctx, + account, + delegations, +) => { + await account.undelegate(delegations); + // TODO transfer something + return 'guest undelegateAndTransfer complete'; +}; +harden(undelegateAndTransfer); diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md new file mode 100644 index 00000000000..88d227b0e7a --- /dev/null +++ b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md @@ -0,0 +1,116 @@ +# Snapshot report for `test/examples/staking-combinations.test.ts` + +The actual snapshot is saved in `staking-combinations.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## start + +> contract baggage after start + + { + 'Durable Publish Kit_kindHandle': 'Alleged: kind', + Recorder_kindHandle: 'Alleged: kind', + asyncFlow: { + AdminAsyncFlow_kindHandle: 'Alleged: kind', + AdminAsyncFlow_singleton: 'Alleged: AdminAsyncFlow', + Bijection_kindHandle: 'Alleged: kind', + FunctionUnwrapper_kindHandle: 'Alleged: kind', + FunctionUnwrapper_singleton: 'Alleged: FunctionUnwrapper', + LogStore_kindHandle: 'Alleged: kind', + StateUnwrapper_kindHandle: 'Alleged: kind', + asyncFuncEagerWakers: [], + asyncFuncFailures: {}, + flowForOutcomeVow: {}, + unwrapMap: 'Alleged: weakMapStore', + }, + contract: { + CombinedInvitationMakers_kindHandle: 'Alleged: kind', + ContinuingInvitationExampleInvitationMakers_kindHandle: 'Alleged: kind', + orchestration: { + depositAndDelegate: { + asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + flows: { + depositAndDelegate_kindHandle: 'Alleged: kind', + depositAndDelegate_singleton: 'Alleged: depositAndDelegate', + makeAccount_kindHandle: 'Alleged: kind', + makeAccount_singleton: 'Alleged: makeAccount', + undelegateAndTransfer_kindHandle: 'Alleged: kind', + undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer', + }, + makeCombineInvitationMakers_kindHandle: 'Alleged: kind', + makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', + makeExtraInvitationMaker_kindHandle: 'Alleged: kind', + makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + }, + }, + }, + makeAccount: { + asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + flows: { + depositAndDelegate_kindHandle: 'Alleged: kind', + depositAndDelegate_singleton: 'Alleged: depositAndDelegate', + makeAccount_kindHandle: 'Alleged: kind', + makeAccount_singleton: 'Alleged: makeAccount', + undelegateAndTransfer_kindHandle: 'Alleged: kind', + undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer', + }, + makeCombineInvitationMakers_kindHandle: 'Alleged: kind', + makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', + makeExtraInvitationMaker_kindHandle: 'Alleged: kind', + makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + }, + }, + }, + undelegateAndTransfer: { + asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + flows: { + depositAndDelegate_kindHandle: 'Alleged: kind', + depositAndDelegate_singleton: 'Alleged: depositAndDelegate', + makeAccount_kindHandle: 'Alleged: kind', + makeAccount_singleton: 'Alleged: makeAccount', + undelegateAndTransfer_kindHandle: 'Alleged: kind', + undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer', + }, + makeCombineInvitationMakers_kindHandle: 'Alleged: kind', + makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', + makeExtraInvitationMaker_kindHandle: 'Alleged: kind', + makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + }, + }, + }, + }, + publicFacet_kindHandle: 'Alleged: kind', + publicFacet_singleton: 'Alleged: publicFacet', + }, + orchestration: { + 'Cosmos Orchestration Account Holder_kindHandle': 'Alleged: kind', + 'Local Orchestration Account Kit_kindHandle': 'Alleged: kind', + LocalChainFacade_kindHandle: 'Alleged: kind', + Orchestrator_kindHandle: 'Alleged: kind', + RemoteChainFacade_kindHandle: 'Alleged: kind', + chainName: { + osmosis: 'Alleged: RemoteChainFacade public', + }, + ibcTools: { + IBCTransferSenderKit_kindHandle: 'Alleged: kind', + ibcResultWatcher_kindHandle: 'Alleged: kind', + ibcResultWatcher_singleton: 'Alleged: ibcResultWatcher', + }, + packetTools: { + PacketToolsKit_kindHandle: 'Alleged: kind', + }, + }, + vows: { + PromiseWatcher_kindHandle: 'Alleged: kind', + VowInternalsKit_kindHandle: 'Alleged: kind', + WatchUtils_kindHandle: 'Alleged: kind', + }, + zoe: {}, + } diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..c18f9684acb9eb479eaa1e8660d461490505ee24 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/packages/orchestration/test/examples/staking-combinations.test.ts b/packages/orchestration/test/examples/staking-combinations.test.ts new file mode 100644 index 00000000000..58505844d28 --- /dev/null +++ b/packages/orchestration/test/examples/staking-combinations.test.ts @@ -0,0 +1,110 @@ +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +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 { protoMsgMocks, UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js'; +import { commonSetup } from '../supports.js'; + +const dirname = path.dirname(new URL(import.meta.url).pathname); + +const contractFile = `${dirname}/../../src/examples/staking-combinations.contract.js`; +type StartFn = + typeof import('@agoric/orchestration/src/examples/staking-combinations.contract.js').start; + +test('start', async t => { + const { + bootstrap: { timer, vowTools: vt }, + brands: { ist }, + mocks: { ibcBridge }, + commonPrivateArgs, + } = await commonSetup(t); + + let contractBaggage; + const { zoe, bundleAndInstall } = await setUpZoeForTest({ + setJig: ({ baggage }) => { + contractBaggage = baggage; + }, + }); + const installation: Installation = + await bundleAndInstall(contractFile); + + const { publicFacet } = await E(zoe).startInstance( + installation, + { Stable: ist.issuer }, + {}, + commonPrivateArgs, + ); + + const inv = E(publicFacet).makeAccount(); + + t.is( + (await E(zoe).getInvitationDetails(inv)).description, + 'Make an ICA account', + ); + + const userSeat = await E(zoe).offer( + inv, + {}, + {}, + { + chainName: 'osmosis', + }, + ); + + const result = await vt.when(E(userSeat).getOfferResult()); + t.like(result, { + publicSubscribers: { + account: { + description: 'Staking Account holder status', + storagePath: 'mockChainStorageRoot.cosmos1test', + }, + }, + }); + + // Here the account would get funded through Cosmos native operations. + + // 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' }, + { + denom: 'osmo', + value: 10n, + }, + ); + t.like(await E(zoe).getInvitationDetails(delegateInv), { + description: 'Delegate', + }); + + // Undelegate the funds using the guest flow + ibcBridge.addMockAck( + // observed in console + 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xnS0pTOWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxeloxVnVaR1ZzWldkaGRHVVNMd29MWTI5emJXOXpNWFJsYzNRU0VtTnZjMjF2YzNaaGJHOXdaWEl4ZEdWemRCb01DZ1YxYjNOdGJ4SURNVEF3IiwibWVtbyI6IiJ9', + protoMsgMocks.undelegate.ack, + ); + const undelegateInvVow = await E( + result.invitationMakers, + ).UndelegateAndTransfer([ + { validatorAddress: 'cosmosvaloper1test', shares: '100' }, + ]); + const undelegateInv = await vt.when(undelegateInvVow); + t.like(await E(zoe).getInvitationDetails(undelegateInv), { + description: 'Undelegate and transfer', + }); + + const undelegateUserSeat = await E(zoe).offer(undelegateInv); + + // Wait for the unbonding period + timer.advanceBy(UNBOND_PERIOD_SECONDS * 1000n); + + const undelegateResult = await vt.when( + E(undelegateUserSeat).getOfferResult(), + ); + t.is(undelegateResult, 'guest undelegateAndTransfer complete'); + + // snapshot the resulting contract baggage + const tree = inspectMapStore(contractBaggage); + t.snapshot(tree, 'contract baggage after start'); +});