Skip to content

Commit dd4296f

Browse files
committed
chore: integrate address hooks, unit tests partially complete
Refs: #8863
1 parent 65bd3ce commit dd4296f

File tree

3 files changed

+210
-5
lines changed

3 files changed

+210
-5
lines changed

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

+87-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
2+
import { makeTracer } from '@agoric/internal';
23
import { E } from '@endo/far';
34
import { M } from '@endo/patterns';
5+
import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
46
import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js';
57
import { withOrchestration } from '../utils/start-helper.js';
68
import * as sharedFlows from './shared.flows.js';
7-
import { swapIt } from './swap-anything.flows.js';
9+
import { swapIt, swapAnythingViaHook } from './swap-anything.flows.js';
810
import { AnyNatAmountShape } from '../typeGuards.js';
911
import { registerChainsAndAssets } from '../utils/chain-hub-helper.js';
1012

13+
const trace = makeTracer('SwapAnything.Contract');
14+
15+
const interfaceTODO = undefined;
16+
1117
/**
1218
* @import {Remote, Vow} from '@agoric/vow';
1319
* @import {Zone} from '@agoric/zone';
@@ -65,12 +71,90 @@ export const contract = async (
6571
makeLocalAccount(),
6672
);
6773

68-
const swapAnything = orchestrate(
74+
const swapAnythingOffer = orchestrate(
6975
'swapAnything',
7076
{ chainHub, sharedLocalAccountP, log, zoeTools },
7177
swapIt,
7278
);
7379

80+
const swapAnythingAddressHook = orchestrate(
81+
'swapAnythingViaHook',
82+
{
83+
chainHub,
84+
sharedLocalAccountP,
85+
log,
86+
},
87+
swapAnythingViaHook,
88+
);
89+
90+
const tap = zone.makeOnce('tapPosition', _key => {
91+
console.log('making tap');
92+
return zone.exo('tap', interfaceTODO, {
93+
/*
94+
* @param {import('@agoric/vats').VTransferIBCEvent} event
95+
*/
96+
async receiveUpcall(event) {
97+
await null;
98+
console.log('receiveUpcall', event);
99+
/**
100+
* Extract the incoming packet data.
101+
*
102+
* @type {import('@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js').FungibleTokenPacketData}
103+
*/
104+
const {
105+
amount,
106+
denom,
107+
receiver: origReceiver,
108+
} = JSON.parse(atob(event.packet.data));
109+
110+
trace({ amount, denom, origReceiver });
111+
112+
const { baseAddress, query } = decodeAddressHook(origReceiver);
113+
114+
/**
115+
* @type {{
116+
* destAddr: string;
117+
* receiverAddr: string;
118+
* outDenom: string;
119+
* }}
120+
*/
121+
// @ts-expect-error
122+
const { destAddr, receiverAddr, outDenom } = query;
123+
124+
trace({
125+
baseAddress,
126+
destAddr,
127+
receiverAddr,
128+
outDenom,
129+
});
130+
131+
if (!receiverAddr || !destAddr || !outDenom) return;
132+
// Invoke the flow to perform swap and end up at the final destination.
133+
return swapAnythingAddressHook(
134+
{ denom, amount },
135+
{
136+
destAddr,
137+
receiverAddr,
138+
outDenom,
139+
onFailedDelivery: 'do_nothing',
140+
slippage: {
141+
slippagePercentage: '20',
142+
windowSeconds: 10,
143+
},
144+
},
145+
);
146+
},
147+
});
148+
});
149+
150+
void vowTools.when(sharedLocalAccountP, async sharedLocalAccount => {
151+
sharedLocalAccount.monitorTransfers(tap);
152+
const encoded = await E(privateArgs.marshaller).toCapData({
153+
sharedLocalAccount: sharedLocalAccount.getAddress(),
154+
});
155+
void E(privateArgs.storageNode).setValue(JSON.stringify(encoded));
156+
});
157+
74158
const publicFacet = zone.exo(
75159
'Send PF',
76160
M.interface('Send PF', {
@@ -79,7 +163,7 @@ export const contract = async (
79163
{
80164
makeSendInvitation() {
81165
return zcf.makeInvitation(
82-
swapAnything,
166+
swapAnythingOffer,
83167
'swap',
84168
undefined,
85169
M.splitRecord({ give: SingleNatAmountRecord }),

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

+65-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NonNullish, makeTracer } from '@agoric/internal';
22
import { Fail, makeError, q } from '@endo/errors';
33
import { M, mustMatch } from '@endo/patterns';
44

5-
const trace = makeTracer('SwapAnything');
5+
const trace = makeTracer('SwapAnything.Flow');
66

77
const { entries } = Object;
88

@@ -22,7 +22,7 @@ const { entries } = Object;
2222
* @import {Vow} from '@agoric/vow';
2323
* @import {LocalOrchestrationAccountKit} from '../exos/local-orchestration-account.js';
2424
* @import {ZoeTools} from '../utils/zoe-tools.js';
25-
* @import {Orchestrator, OrchestrationFlow, ChainHub, ChainInfo, CosmosChainInfo} from '../types.js';
25+
* @import {Orchestrator, OrchestrationFlow, ChainHub, CosmosChainInfo} from '../types.js';
2626
*/
2727

2828
const denomForBrand = async (orch, brand) => {
@@ -161,3 +161,66 @@ export const swapIt = async (
161161
void log(`transfer complete, seat exited`);
162162
};
163163
harden(swapIt);
164+
165+
/**
166+
* @satisfies {OrchestrationFlow}
167+
* @param {Orchestrator} _orch
168+
* @param {object} ctx
169+
* @param {GuestInterface<ChainHub>} ctx.chainHub
170+
* @param {Promise<GuestInterface<LocalOrchestrationAccountKit['holder']>>} ctx.sharedLocalAccountP
171+
* @param {GuestOf<(msg: string) => Vow<void>>} ctx.log
172+
* @param {{ denom: string; amount: string }} transferInfo
173+
* @param {SwapInfo} memoArgs
174+
*/
175+
export const swapAnythingViaHook = async (
176+
_orch,
177+
{ chainHub, sharedLocalAccountP, log },
178+
{ denom, amount },
179+
memoArgs,
180+
) => {
181+
mustMatch(
182+
memoArgs,
183+
M.splitRecord(
184+
{
185+
destAddr: M.string(),
186+
receiverAddr: M.string(),
187+
outDenom: M.string(),
188+
onFailedDelivery: M.string(),
189+
slippage: { slippagePercentage: M.string(), windowSeconds: M.number() },
190+
},
191+
{ nextMemo: M.string() },
192+
),
193+
);
194+
195+
const { receiverAddr, destAddr } = memoArgs;
196+
void log(`sending {${amount}} from osmosis to ${receiverAddr}`);
197+
198+
/**
199+
* @type {any} XXX methods returning vows
200+
* https://github.com/Agoric/agoric-sdk/issues/9822
201+
*/
202+
const sharedLocalAccount = await sharedLocalAccountP;
203+
204+
const [_a, osmosisChainInfo, connection] =
205+
await chainHub.getChainsAndConnection('agoric', 'osmosis');
206+
207+
connection.counterparty || Fail`No IBC connection to Osmosis`;
208+
209+
void log(`got info for chain: osmosis ${osmosisChainInfo}`);
210+
trace(osmosisChainInfo);
211+
212+
const memo = buildXCSMemo(memoArgs);
213+
214+
await sharedLocalAccount.transfer(
215+
{
216+
value: destAddr,
217+
encoding: 'bech32',
218+
chainId: /** @type {CosmosChainInfo} */ (osmosisChainInfo).chainId,
219+
},
220+
{ denom, value: BigInt(amount) },
221+
{ memo },
222+
);
223+
224+
return 'Done';
225+
};
226+
harden(swapAnythingViaHook);

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

+58
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
22
import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
33
import { E } from '@endo/far';
4+
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
5+
import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
6+
import { NonNullish } from '@agoric/internal';
47
import * as contractExports from '../../src/examples/swap-anything.contract.js';
58
import { commonSetup } from '../supports.js';
9+
import { buildVTransferEvent } from '../../tools/ibc-mocks.js';
610

711
const contractName = 'swap-anything';
812
type StartFn = typeof contractExports.start;
@@ -22,6 +26,7 @@ const bootstrapOrchestration = async t => {
2226
commonPrivateArgs,
2327
brands: { bld },
2428
utils: { inspectLocalBridge, pourPayment, transmitTransferAck },
29+
mocks: { transferBridge },
2530
} = await commonSetup(t);
2631
const vt = bootstrap.vowTools;
2732

@@ -55,6 +60,7 @@ const bootstrapOrchestration = async t => {
5560
installation,
5661
storageNode,
5762
swapKit,
63+
transferBridge,
5864
};
5965
};
6066

@@ -138,4 +144,56 @@ test('swap BLD for Osmo, receiver on Agoric', async t => {
138144
);
139145
});
140146

147+
test('trigger osmosis swap from an address hook', async t => {
148+
const {
149+
bootstrap: { storage },
150+
transferBridge,
151+
transmitTransferAck,
152+
inspectLocalBridge,
153+
commonPrivateArgs,
154+
} = await bootstrapOrchestration(t);
155+
await eventLoopIteration();
156+
157+
const { sharedLocalAccount } = commonPrivateArgs.marshaller.fromCapData(
158+
JSON.parse(
159+
NonNullish(storage.data.get('mockChainStorageRoot.swap-anything')),
160+
),
161+
);
162+
163+
t.deepEqual(sharedLocalAccount.value, 'agoric1fakeLCAAddress');
164+
165+
const memoArgs = buildOfferArgs(config.xcsInformation.rawMsgNoNextMemo);
166+
t.log(memoArgs);
167+
168+
await E(transferBridge).fromBridge(
169+
buildVTransferEvent({
170+
denom: 'ubld',
171+
receiver: encodeAddressHook(
172+
'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht',
173+
{
174+
destAddr: memoArgs.destAddr,
175+
receiverAddr: memoArgs.receiverAddr,
176+
outDenom: memoArgs.outDenom,
177+
},
178+
),
179+
}),
180+
);
181+
182+
await transmitTransferAck();
183+
184+
const history = inspectLocalBridge();
185+
t.log(history);
186+
187+
t.like(
188+
history.find(x => x.type === 'VLOCALCHAIN_EXECUTE_TX')?.messages?.[0],
189+
{
190+
'@type': '/ibc.applications.transfer.v1.MsgTransfer',
191+
receiver: memoArgs.destAddr, // XCS contract has to be the one receiving the IBC message
192+
sender: 'agoric1fakeLCAAddress',
193+
memo: config.xcsInformation.rawMsgNoNextMemo,
194+
},
195+
'crosschain swap sent',
196+
);
197+
});
198+
141199
test.todo('should throw when Osmosis not connected');

0 commit comments

Comments
 (0)