Skip to content

Commit

Permalink
feat: expose monitorTransfers on LocalOrchestrationAccount
Browse files Browse the repository at this point in the history
- monitorTransfers allows calls to register a handler to react to incoming and outgoing IBC ics20 transfers
- includes VTRANSFER_IBC_EVENT<'acknowledgementPacket'> fixture from observed values in a multichain testing environment (see PR comments
  for logs)
- refs: #9042
  • Loading branch information
0xpatrickdev committed Jul 16, 2024
1 parent 80d5bdf commit fc728b5
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 7 deletions.
15 changes: 15 additions & 0 deletions packages/orchestration/src/cosmos-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import type { State as IBCConnectionState } from '@agoric/cosmic-proto/ibc/core/
import type { Brand, Purse, Payment, Amount } from '@agoric/ertp/src/types.js';
import type { Port } from '@agoric/network';
import type { IBCChannelID, IBCConnectionID } from '@agoric/vats';
import type {
TargetApp,
TargetRegistration,
} from '@agoric/vats/src/bridge-target.js';
import type {
LocalIbcAddress,
RemoteIbcAddress,
Expand Down Expand Up @@ -208,6 +212,17 @@ export type LocalAccountMethods = {
deposit: (payment: Payment<'nat'>) => Promise<void>;
/** withdraw a Payment from the account */
withdraw: (amount: Amount<'nat'>) => Promise<Payment<'nat'>>;
/**
* Register a handler that receives an event each time ICS-20 transfers are
* sent or received by the underlying account. Each account may be associated
* with at most one handler at a given time.
* Does not grant the handler the ability to intercept a transfer. For a
* blocking handler, aka 'IBC Hooks', leverage `registerActiveTap` from
* `transferMiddleware` directly.
*
* @param tap
*/
monitorTransfers: (tap: TargetApp) => Promise<TargetRegistration>;
};

export type IBCMsgTransferOptions = {
Expand Down
13 changes: 10 additions & 3 deletions packages/orchestration/src/exos/local-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import { makeTimestampHelper } from '../utils/time.js';
/**
* @import {HostOf} from '@agoric/async-flow';
* @import {LocalChainAccount} from '@agoric/vats/src/localchain.js';
* @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, ChainInfo, IBCConnectionInfo, OrchestrationAccountI} from '@agoric/orchestration';
* @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, ChainInfo, IBCConnectionInfo, OrchestrationAccountI} from '@agoric/orchestration';
* @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'.
* @import {Zone} from '@agoric/zone';
* @import {Remote} from '@agoric/internal';
* @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js';
* @import {TimerService, TimerBrand, TimestampRecord} from '@agoric/time';
* @import {PromiseVow, Vow, VowTools} from '@agoric/vow';
* @import {TimerService, TimestampRecord} from '@agoric/time';
* @import {Vow, VowTools} from '@agoric/vow';
* @import {TypedJson, JsonSafe} from '@agoric/cosmic-proto';
* @import {Matcher} from '@endo/patterns';
* @import {ChainHub} from './chain-hub.js';
Expand Down Expand Up @@ -58,6 +58,9 @@ const HolderI = M.interface('holder', {
deposit: M.call(PaymentShape).returns(VowShape),
withdraw: M.call(AmountShape).returns(Vow$(PaymentShape)),
executeTx: M.call(M.arrayOf(M.record())).returns(Vow$(M.record())),
monitorTransfers: M.call(M.remotable('TransferTap')).returns(
Vow$(M.remotable('TargetRegistration')),
),
});

/** @type {{ [name: string]: [description: string, valueShape: Matcher] }} */
Expand Down Expand Up @@ -477,6 +480,10 @@ export const prepareLocalOrchestrationAccountKit = (
throw Fail`not yet implemented`;
});
},
/** @type {HostOf<LocalChainAccount['monitorTransfers']>} */
monitorTransfers(tap) {
return watch(E(this.state.account).monitorTransfers(tap));
},
},
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
QueryBalanceRequest,
QueryBalanceResponse,
} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js';
import { TimeMath } from '@agoric/time';
import { commonSetup } from '../supports.js';
import { type StakeIcaTerms } from '../../src/examples/stakeIca.contract.js';
import fetchedChainInfo from '../../src/fetched-chain-info.js';
Expand Down Expand Up @@ -78,7 +77,7 @@ const startContract = async ({
};

test('makeAccount, getAddress, getBalances, getBalance', async t => {
const { bootstrap } = await commonSetup(t);
const { bootstrap, mocks } = await commonSetup(t);
{
// stakeAtom
const { publicFacet } = await startContract(bootstrap);
Expand All @@ -105,7 +104,7 @@ test('makeAccount, getAddress, getBalances, getBalance', async t => {
}
{
// stakeOsmo
const { ibcBridge } = bootstrap;
const { ibcBridge } = mocks;
await E(ibcBridge).setAddressPrefix('osmo');
const { publicFacet } = await startContract({
...bootstrap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { commonSetup } from '../supports.js';
import { UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js';
import { maxClockSkew } from '../../src/utils/cosmos.js';
import { prepareMakeTestLOAKit } from './make-test-loa-kit.js';
import { buildVTransferEvent } from '../../tools/ibc-mocks.js';

test('deposit, withdraw', async t => {
const common = await commonSetup(t);
Expand Down Expand Up @@ -172,3 +173,43 @@ test('transfer', async t => {
'accepts custom timeoutHeight',
);
});

test('monitor transfers', async t => {
const common = await commonSetup(t);
const makeTestLOAKit = prepareMakeTestLOAKit(t, common.bootstrap);
const account = await makeTestLOAKit();
const {
mocks: { transferBridge },
bootstrap: { rootZone },
} = common;

let upcallCount = 0;
const zone = rootZone.subZone('tap');
const tap: TargetApp = zone.exo('tap', undefined, {
receiveUpcall: (obj: unknown) => {
upcallCount += 1;
t.log('receiveUpcall', obj);
return Promise.resolve();
},
});

const { value: target } = await E(account).getAddress();
const appRegistration = await E(account).monitorTransfers(tap);

// simulate upcall from golang to VM
const simulateIncomingTransfer = async () =>
E(transferBridge).fromBridge(
buildVTransferEvent({
receiver: target,
}),
);

await simulateIncomingTransfer();
t.is(upcallCount, 1, 'first upcall received');
await simulateIncomingTransfer();
t.is(upcallCount, 2, 'second upcall received');

await appRegistration.revoke();
await t.throwsAsync(simulateIncomingTransfer());
t.is(upcallCount, 2, 'no more events after app is revoked');
});
6 changes: 5 additions & 1 deletion packages/orchestration/test/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const commonSetup = async (t: ExecutionContext<any>) => {
interceptorFactory,
);
finisher.useRegistry(bridgeTargetKit.targetRegistry);
await E(transferBridge).initHandler(bridgeTargetKit.bridgeHandler);

const localBrigeMessages = [] as any[];
const localchainBridge = makeFakeLocalchainBridge(rootZone, obj =>
Expand Down Expand Up @@ -136,12 +137,15 @@ export const commonSetup = async (t: ExecutionContext<any>) => {
rootZone: rootZone.subZone('contract'),
storage,
vowTools,
ibcBridge,
},
brands: {
bld: bldSansMint,
ist: istSansMint,
},
mocks: {
ibcBridge,
transferBridge,
},
commonPrivateArgs: {
agoricNames,
localchain,
Expand Down
75 changes: 75 additions & 0 deletions packages/orchestration/tools/ibc-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
} from '@agoric/cosmic-proto/tendermint/abci/types.js';
import { encodeBase64, btoa } from '@endo/base64';
import { toRequestQueryJson } from '@agoric/cosmic-proto';
import { IBCChannelID, VTransferIBCEvent } from '@agoric/vats';
import { VTRANSFER_IBC_EVENT } from '@agoric/internal/src/action-types.js';
import { FungibleTokenPacketData } from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
import { makeQueryPacket, makeTxPacket } from '../src/utils/packet.js';
import { ChainAddress } from '../src/orchestration-api.js';

interface EncoderI<T> {
encode: (message: T) => {
Expand Down Expand Up @@ -125,3 +129,74 @@ export function buildQueryPacketString(
): string {
return btoa(makeQueryPacket(msgs.map(msg => toRequestQueryJson(msg, opts))));
}

type BuildVTransferEventParams = {
event?: VTransferIBCEvent['event'];
/* defaults to cosmos1AccAddress. set to `agoric1fakeLCAAddress` to simulate an outgoing transfer event */
sender?: ChainAddress['value'];
/** defaults to agoric1fakeLCAAddress. set to a different value to simulate an outgoing transfer event */
receiver?: ChainAddress['value'];
amount?: bigint;
denom?: string;
destinationChannel?: IBCChannelID;
sourceChannel?: IBCChannelID;
};

/**
* `buildVTransferEvent` can be used with `transferBridge` to simulate incoming
* and outgoing IBC fungible tokens transfers to a LocalChain account.
*
* It defaults to simulating incoming transfers. To simulate an outgoing one,
* ensure `sender=agoric1fakeLCAAddress` and this after LocalChainBridge
* receives the outgoing MsgTransfer,
*
* @example
* ```js
* const { mocks: { transferBridge } = await commonSetup(t);
* await E(transferBridge).fromBridge(
* buildVTransferEvent({
* receiver: 'agoric1fakeLCAAddress',
* amount: 10n,
* denom: 'uatom',
* }),
* );
* ```
*
* XXX integrate vlocalchain and vtransfer ScopedBridgeManagers
* in test supports.
*
* @param {{BuildVTransferEventParams}} args
*/
export const buildVTransferEvent = ({
event = 'acknowledgementPacket' as const,
sender = 'cosmos1AccAddress',
receiver = 'agoric1fakeLCAAddress',
amount = 10n,
denom = 'uatom',
destinationChannel = 'channel-0' as IBCChannelID,
sourceChannel = 'channel-405' as IBCChannelID,
}: BuildVTransferEventParams = {}): VTransferIBCEvent => ({
type: VTRANSFER_IBC_EVENT,
blockHeight: 0,
blockTime: 0,
event,
acknowledgement: btoa(JSON.stringify({ result: 'AQ==' })),
relayer: 'agoric123',
target: receiver,
packet: {
data: btoa(
JSON.stringify(
FungibleTokenPacketData.fromPartial({
amount: String(amount),
denom,
sender,
receiver,
}),
),
),
destination_channel: destinationChannel,
source_channel: sourceChannel,
destination_port: 'transfer',
source_port: 'transfer',
},
});

0 comments on commit fc728b5

Please sign in to comment.