Skip to content

Commit e3b2487

Browse files
committed
chore: add multichain tests for swapAnything with address hooks
Refs: #8863
1 parent 827b536 commit e3b2487

File tree

5 files changed

+179
-21
lines changed

5 files changed

+179
-21
lines changed

multichain-testing/test/xcs-swap-anything/swap-anything.test.ts

+157-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import anyTest from '@endo/ses-ava/prepare-endo.js';
22
import type { TestFn } from 'ava';
33
import { AmountMath } from '@agoric/ertp';
4+
import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
45
import { makeDoOffer } from '../../tools/e2e-tools.js';
56
import { commonSetup, type SetupContextWithWallets } from '../support.js';
67
import { makeQueryClient } from '../../tools/query.js';
78
import starshipChainInfo from '../../starship-chain-info.js';
9+
import {
10+
createFundedWalletAndClient,
11+
makeIBCTransferMsg,
12+
} from '../../tools/ibc-transfer.js';
813

914
const test = anyTest as TestFn<SetupContextWithWallets>;
1015

@@ -14,6 +19,69 @@ const contractName = 'swapAnything';
1419
const contractBuilder =
1520
'../packages/builders/scripts/testing/init-swap-anything.js';
1621

22+
const fundRemote = async (
23+
t,
24+
destinationChain,
25+
denomToTransfer = 'ubld',
26+
amount = 100000000n,
27+
) => {
28+
const { retryUntilCondition, useChain } = t.context;
29+
30+
const { client, address, wallet } = await createFundedWalletAndClient(
31+
t.log,
32+
destinationChain,
33+
useChain,
34+
);
35+
const balancesResult = await retryUntilCondition(
36+
() => client.getAllBalances(address),
37+
coins => !!coins?.length,
38+
`Faucet balances found for ${address}`,
39+
);
40+
console.log('Balances:', balancesResult);
41+
42+
const { client: agoricClient, address: agoricAddress } =
43+
await createFundedWalletAndClient(t.log, 'agoric', useChain);
44+
45+
const balancesResultAg = await retryUntilCondition(
46+
() => agoricClient.getAllBalances(agoricAddress),
47+
coins => !!coins?.length,
48+
`Faucet balances found for ${agoricAddress}`,
49+
);
50+
console.log('Balances AGORIC:', balancesResultAg);
51+
52+
const transferArgs = makeIBCTransferMsg(
53+
{ denom: denomToTransfer, value: amount },
54+
{ address, chainName: destinationChain },
55+
{ address: agoricAddress, chainName: 'agoric' },
56+
Date.now(),
57+
useChain,
58+
);
59+
console.log('Transfer Args:', transferArgs);
60+
// TODO #9200 `sendIbcTokens` does not support `memo`
61+
// @ts-expect-error spread argument for concise code
62+
const txRes = await agoricClient.sendIbcTokens(...transferArgs);
63+
if (txRes && txRes.code !== 0) {
64+
console.error(txRes);
65+
throw Error(`failed to ibc transfer funds to ${denomToTransfer}`);
66+
}
67+
const { events: _events, ...txRest } = txRes;
68+
console.log(txRest);
69+
t.is(txRes.code, 0, `Transaction succeeded`);
70+
t.log(`Funds transferred to ${agoricAddress}`);
71+
72+
await retryUntilCondition(
73+
() => client.getAllBalances(address),
74+
coins => !!coins?.length,
75+
`${denomToTransfer} transferred to ${address}`,
76+
);
77+
78+
return {
79+
client,
80+
address,
81+
wallet,
82+
};
83+
};
84+
1785
test.before(async t => {
1886
const { setupTestKeys, ...common } = await commonSetup(t);
1987
const { commonBuilderOpts, deleteTestKeys, startContract } = common;
@@ -24,7 +92,7 @@ test.before(async t => {
2492
await startContract(contractName, contractBuilder, commonBuilderOpts);
2593
});
2694

27-
test('BLD for OSMO, receiver on Agoric', async t => {
95+
test.serial('BLD for OSMO, receiver on Agoric', async t => {
2896
const {
2997
wallets,
3098
provisionSmartWallet,
@@ -103,6 +171,94 @@ test('BLD for OSMO, receiver on Agoric', async t => {
103171
);
104172
});
105173

174+
test.serial('address hook - BLD for OSMO, receiver on Agoric', async t => {
175+
const { wallets, vstorageClient, retryUntilCondition, useChain } = t.context;
176+
const { getRestEndpoint, chain: cosmosChain } = useChain('cosmoshub');
177+
178+
const { address: cosmosHubAddr, client: cosmosHubClient } = await fundRemote(
179+
t,
180+
'cosmoshub',
181+
);
182+
183+
const cosmosHubApiUrl = await getRestEndpoint();
184+
const cosmosHubQueryClient = makeQueryClient(cosmosHubApiUrl);
185+
186+
const {
187+
transferChannel: { counterPartyChannelId },
188+
} = starshipChainInfo.agoric.connections[cosmosChain.chain_id];
189+
190+
const apiUrl = await useChain('agoric').getRestEndpoint();
191+
const queryClient = makeQueryClient(apiUrl);
192+
193+
const { balances: balancesBefore } = await queryClient.queryBalances(
194+
wallets.agoricReceiver,
195+
);
196+
197+
const { hash: bldDenomOnHub } = await cosmosHubQueryClient.queryDenom(
198+
`transfer/${counterPartyChannelId}`,
199+
'ubld',
200+
);
201+
t.log({ bldDenomOnHub, counterPartyChannelId });
202+
203+
const {
204+
sharedLocalAccount: { value: baseAddress },
205+
} = await vstorageClient.queryData('published.swap-anything');
206+
t.log(baseAddress);
207+
208+
const orcContractReceiverAddress = encodeAddressHook(baseAddress, {
209+
destAddr: 'osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8',
210+
receiverAddr: wallets.agoricReceiver,
211+
outDenom: 'uosmo',
212+
});
213+
214+
const transferArgs = makeIBCTransferMsg(
215+
{ denom: `ibc/${bldDenomOnHub}`, value: 125n },
216+
{ address: orcContractReceiverAddress, chainName: 'agoric' },
217+
{ address: cosmosHubAddr, chainName: 'cosmoshub' },
218+
Date.now(),
219+
useChain,
220+
);
221+
console.log('Transfer Args:', transferArgs);
222+
// TODO #9200 `sendIbcTokens` does not support `memo`
223+
// @ts-expect-error spread argument for concise code
224+
const txRes = await cosmosHubClient.sendIbcTokens(...transferArgs);
225+
if (txRes && txRes.code !== 0) {
226+
console.error(txRes);
227+
throw Error(`failed to ibc transfer funds to ibc/${bldDenomOnHub}`);
228+
}
229+
const { events: _events, ...txRest } = txRes;
230+
console.log(txRest);
231+
t.is(txRes.code, 0, `Transaction succeeded`);
232+
t.log(`Funds transferred to ${orcContractReceiverAddress}`);
233+
234+
const osmosisChainId = useChain('osmosis').chain.chain_id;
235+
236+
const {
237+
transferChannel: { channelId },
238+
} = starshipChainInfo.agoric.connections[osmosisChainId];
239+
240+
const { balances: agoricReceiverBalances } = await retryUntilCondition(
241+
() => queryClient.queryBalances(wallets.agoricReceiver),
242+
({ balances }) => balances.length > balancesBefore.length,
243+
'Deposit reflected in localOrchAccount balance',
244+
);
245+
t.log(agoricReceiverBalances);
246+
247+
const { hash: expectedHash } = await queryClient.queryDenom(
248+
`transfer/${channelId}`,
249+
'uosmo',
250+
);
251+
252+
t.log('Expected denom hash:', expectedHash);
253+
254+
t.regex(agoricReceiverBalances[0]?.denom, /^ibc/);
255+
t.is(
256+
agoricReceiverBalances[0]?.denom.split('ibc/')[1],
257+
expectedHash,
258+
'got expected ibc denom hash',
259+
);
260+
});
261+
106262
test.after(async t => {
107263
const { deleteTestKeys } = t.context;
108264
deleteTestKeys(accounts);

packages/orchestration/src/examples/swap-anything.contract.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,23 @@ export const contract = async (
9090
const tap = zone.makeOnce('tapPosition', _key => {
9191
console.log('making tap');
9292
return zone.exo('tap', interfaceTODO, {
93-
/*
93+
/**
9494
* @param {import('@agoric/vats').VTransferIBCEvent} event
9595
*/
9696
async receiveUpcall(event) {
9797
await null;
98-
console.log('receiveUpcall', event);
98+
trace('receiveUpcall', event);
99+
100+
if (event.event !== 'writeAcknowledgement') return;
101+
trace('Moving on...');
99102
/**
100103
* Extract the incoming packet data.
101104
*
102105
* @type {import('@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js').FungibleTokenPacketData}
103106
*/
104107
const {
105108
amount,
106-
denom,
109+
denom, // transfer/channel-1/ubld, uatom
107110
receiver: origReceiver,
108111
} = JSON.parse(atob(event.packet.data));
109112

@@ -131,11 +134,11 @@ export const contract = async (
131134
if (!receiverAddr || !destAddr || !outDenom) return;
132135
// Invoke the flow to perform swap and end up at the final destination.
133136
return swapAnythingAddressHook(
134-
{ denom, amount },
137+
{ denom: 'ubld', amount },
135138
{
136139
destAddr,
137140
receiverAddr,
138-
outDenom,
141+
outDenom, // swapOutDenom
139142
onFailedDelivery: 'do_nothing',
140143
slippage: {
141144
slippagePercentage: '20',

packages/orchestration/src/examples/swap-anything.flows.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,13 @@ export const swapIt = async (
101101
{ nextMemo: M.string() },
102102
),
103103
);
104+
void log('Begin swapIt');
104105

105106
const { receiverAddr, destAddr } = offerArgs;
106107
// NOTE the proposal shape ensures that the `give` is a single asset
107108
const { give } = seat.getProposal();
108109
const [[_kw, amt]] = entries(give);
109-
void log(`sending {${amt.value}} from osmosis to ${receiverAddr}`);
110+
trace(`sending {${amt.value}} from osmosis to ${receiverAddr}`);
110111
const denom = await denomForBrand(orch, amt.brand);
111112

112113
/**
@@ -123,7 +124,7 @@ export const swapIt = async (
123124
const recoverFailedTransfer = async e => {
124125
await withdrawToSeat(sharedLocalAccount, seat, give);
125126
const errorMsg = `IBC Transfer failed ${q(e)}`;
126-
void log(`ERROR: ${errorMsg}`);
127+
trace(`ERROR: ${errorMsg}`);
127128
seat.fail(errorMsg);
128129
throw makeError(errorMsg);
129130
};
@@ -133,11 +134,11 @@ export const swapIt = async (
133134

134135
connection.counterparty || Fail`No IBC connection to Osmosis`;
135136

136-
void log(`got info for chain: osmosis ${osmosisChainInfo}`);
137+
trace(`got info for chain: osmosis ${osmosisChainInfo}`);
137138
trace(osmosisChainInfo);
138139

139140
await localTransfer(seat, sharedLocalAccount, give);
140-
void log(`completed transfer to localAccount`);
141+
trace(`completed transfer to localAccount`);
141142

142143
try {
143144
const memo = buildXCSMemo(offerArgs);
@@ -152,13 +153,13 @@ export const swapIt = async (
152153
{ denom, value: amt.value },
153154
{ memo },
154155
);
155-
void log(`completed transfer to ${destAddr}`);
156+
trace(`completed transfer to ${destAddr}`);
156157
} catch (e) {
157158
return recoverFailedTransfer(e);
158159
}
159160

160161
seat.exit();
161-
void log(`transfer complete, seat exited`);
162+
trace(`transfer complete, seat exited`);
162163
};
163164
harden(swapIt);
164165

@@ -168,13 +169,12 @@ harden(swapIt);
168169
* @param {object} ctx
169170
* @param {GuestInterface<ChainHub>} ctx.chainHub
170171
* @param {Promise<GuestInterface<LocalOrchestrationAccountKit['holder']>>} ctx.sharedLocalAccountP
171-
* @param {GuestOf<(msg: string) => Vow<void>>} ctx.log
172172
* @param {{ denom: string; amount: string }} transferInfo
173173
* @param {SwapInfo} memoArgs
174174
*/
175175
export const swapAnythingViaHook = async (
176176
_orch,
177-
{ chainHub, sharedLocalAccountP, log },
177+
{ chainHub, sharedLocalAccountP },
178178
{ denom, amount },
179179
memoArgs,
180180
) => {
@@ -193,7 +193,7 @@ export const swapAnythingViaHook = async (
193193
);
194194

195195
const { receiverAddr, destAddr } = memoArgs;
196-
void log(`sending {${amount}} from osmosis to ${receiverAddr}`);
196+
trace(`sending {${amount}} from osmosis to ${receiverAddr}`);
197197

198198
/**
199199
* @type {any} XXX methods returning vows
@@ -206,7 +206,7 @@ export const swapAnythingViaHook = async (
206206

207207
connection.counterparty || Fail`No IBC connection to Osmosis`;
208208

209-
void log(`got info for chain: osmosis ${osmosisChainInfo}`);
209+
trace(`got info for chain: osmosis ${osmosisChainInfo}`);
210210
trace(osmosisChainInfo);
211211

212212
const memo = buildXCSMemo(memoArgs);

packages/orchestration/src/proposals/start-swap-anything.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { E } from '@endo/far';
1010
/**
1111
* @import {Installation} from '@agoric/zoe/src/zoeService/utils.js';
1212
* @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration';
13-
* @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js';
13+
* @import {start as StartFn} from '@agoric/orchestration/src/examples/swap-anything.contract.js';
1414
*/
1515

1616
const trace = makeTracer('StartSA', true);
@@ -75,7 +75,7 @@ export const startSwapAnything = async (
7575
marshaller,
7676
orchestrationService: cosmosInterchainService,
7777
storageNode: E(NonNullish(await chainStorage)).makeChildNode(
78-
'send-anywhere',
78+
'swap-anything',
7979
),
8080
timerService: chainTimerService,
8181
chainInfo,

packages/orchestration/test/examples/swap-anything.test.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ test('trigger osmosis swap from an address hook', async t => {
148148
const {
149149
bootstrap: { storage },
150150
transferBridge,
151-
transmitTransferAck,
152151
inspectLocalBridge,
153152
commonPrivateArgs,
154153
} = await bootstrapOrchestration(t);
@@ -167,6 +166,7 @@ test('trigger osmosis swap from an address hook', async t => {
167166

168167
await E(transferBridge).fromBridge(
169168
buildVTransferEvent({
169+
event: 'writeAcknowledgement',
170170
denom: 'ubld',
171171
receiver: encodeAddressHook(
172172
'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht',
@@ -178,8 +178,7 @@ test('trigger osmosis swap from an address hook', async t => {
178178
),
179179
}),
180180
);
181-
182-
await transmitTransferAck();
181+
await eventLoopIteration();
183182

184183
const history = inspectLocalBridge();
185184
t.log(history);

0 commit comments

Comments
 (0)