From 959f352194de6bbef4ac4d497421adebbeea8a35 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 07:55:44 -0600 Subject: [PATCH 01/48] WIP: toward orch template: sequence diagram --- .../test/examples/my-orch-sequence.mmd | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/orchestration/test/examples/my-orch-sequence.mmd diff --git a/packages/orchestration/test/examples/my-orch-sequence.mmd b/packages/orchestration/test/examples/my-orch-sequence.mmd new file mode 100644 index 00000000000..e1d0e45c4ea --- /dev/null +++ b/packages/orchestration/test/examples/my-orch-sequence.mmd @@ -0,0 +1,32 @@ +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#f0f8ff', + 'primaryTextColor': '#2c3e50', + 'primaryBorderColor': '#7fb2e6', + 'lineColor': '#7fb2e6', + 'secondaryColor': '#f6f8fa', + 'tertiaryColor': '#fff5e6' + } +}}%% +sequenceDiagram + title Elys Orchestration Flow - Transactional Model + autonumber + actor webUA as Elys WebApp
[Browser] + %% [Where it runs] + participant chainOrig as Cosmos Hub + participant chainAg as Agoric Chain + participant chainLS as Stride Chain + participant chainMy as Elys Chain + + %% Notation: ->> for initial message, -->> for consequences + + webUA ->> chainOrig: User signs Cosmos Hub IBC tx to
move 10 ATOM + chainOrig -->> chainAg: 10 ATOM arrives in Agoric Address Hook account + chainAg -->> chainOrig: ATOM sent via Stride Autopilot / PFM to arrive on Elys as stATOM in account held by Agoric ICA + chainOrig -->> chainLS: + chainLS -->> chainMy: + chainMy -->> chainAg: Agoric contract notified that assets have arrived + chainAg -->> chainMy: Agoric ICA executes transfer & deposit functions on Elys Network + chainMy -->> chainMy: Assets transferred
from ICA to user
account and depositied + chainMy -->> webUA: User notified that execution is complete and position is created From fc00e20d895752df32e483bb73b582913b85f817 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 08:31:23 -0600 Subject: [PATCH 02/48] chore: refine sequence diagram to object messages --- .../test/examples/my-orch-sequence.mmd | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-sequence.mmd b/packages/orchestration/test/examples/my-orch-sequence.mmd index e1d0e45c4ea..47b7e9142cc 100644 --- a/packages/orchestration/test/examples/my-orch-sequence.mmd +++ b/packages/orchestration/test/examples/my-orch-sequence.mmd @@ -14,19 +14,40 @@ sequenceDiagram autonumber actor webUA as Elys WebApp
[Browser] %% [Where it runs] - participant chainOrig as Cosmos Hub - participant chainAg as Agoric Chain + box Aqua Cosmos Chain + participant acctOrig as User Acct + end + box Red Agoric Chain + participant myOrch as Orch. Contract + participant orchLCA1 as agoric1orchFEED + end + box Yellow as Stride Chain participant chainLS as Stride Chain + end + box Grey Elys Chain participant chainMy as Elys Chain + participant ICA1 as ICA 145
controlled by Agoric contract + participant acctDest as User Account
elsy176 + end %% Notation: ->> for initial message, -->> for consequences - webUA ->> chainOrig: User signs Cosmos Hub IBC tx to
move 10 ATOM - chainOrig -->> chainAg: 10 ATOM arrives in Agoric Address Hook account - chainAg -->> chainOrig: ATOM sent via Stride Autopilot / PFM to arrive on Elys as stATOM in account held by Agoric ICA - chainOrig -->> chainLS: - chainLS -->> chainMy: - chainMy -->> chainAg: Agoric contract notified that assets have arrived - chainAg -->> chainMy: Agoric ICA executes transfer & deposit functions on Elys Network - chainMy -->> chainMy: Assets transferred
from ICA to user
account and depositied - chainMy -->> webUA: User notified that execution is complete and position is created + note left of myOrch: contract starts + myOrch ->> myOrch: makeLocalAccount() + myOrch ->> orchLCA1: monitorTranfers(...) + myOrch ->> chainMy: makeAccount() + chainMy -->> myOrch: elys145... + + note right of webUA: User Initiates Action + webUA ->> webUA: openPosition(10 ATOM) + webUA -->> acctOrig: send(10 ATOM, agoric1orchFEED) + acctOrig -->> orchLCA1: receiveUpcall(10 ATOM) + orchLCA1 -->> acctOrig: send(10 ATOM, stride123...,
fwd: to stATOM, send to ICA 145 on Elys) + acctOrig -->> chainLS: deposit(10 ATOM, to stATOM, fwd: to ICA 145 on Elys) + chainLS -->> ICA1: deposit(10 ATOM) + ICA1 -->> orchLCA1: resolve(ack) + orchLCA1 -->> ICA1: send(9 stATOM, elsy176) + ICA1 -->> acctDest: deposit(9 stATOM) + webUA -->> acctDest: getBalances() + acctDest -->> webUA: resolve(9 stATOM) + note right of webUA: User notified that execution
is complete and position is created From e70125d37e8f18dd4f6169071ed1173481311829 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 08:54:52 -0600 Subject: [PATCH 03/48] chore: prototype 1st message of diagram --- .../test/examples/my-orch-seq-sim.test.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/orchestration/test/examples/my-orch-seq-sim.test.ts diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts new file mode 100644 index 00000000000..685ffa24432 --- /dev/null +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -0,0 +1,41 @@ +/** @file + * Orchestration Contract template: Test simulation from sequence diagram. + * + * For each (kind of) actor / participant in the diagram (my-orch-sequence.mmd), + * we have a function to make one. + * + * Each arrow in the diagram represents a method call on the receiving object. + */ +import test from 'ava'; +import type { ExecutionContext as Ex } from 'ava'; + +const { freeze } = Object; + +type Coins = { denom: string; amount: number }[]; // XXX rough + +const makeCosmosAccount = (t: Ex, addr: string) => { + return freeze({ + async deposit(amt: Coins) { + t.log('TODO: deposit', amt); + }, + }); +}; + +const makeUA = () => { + return freeze({ + signAndBroadcast(t: Ex, amt: Coins, destAddr: string, memo = '') { + const dest = makeCosmosAccount(t, destAddr); + return dest.deposit(amt); + }, + }); +}; + +test('user sends 10 atom', async t => { + const u1 = makeUA(); + const actual = await u1.signAndBroadcast( + t, + [{ denom: 'ATOM', amount: 10 }], + 'cosmos1567', + ); + t.is(actual, undefined); +}); From e7bfe84cb8b113a76c49ba4b4eff88c70fab919b Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 09:03:32 -0600 Subject: [PATCH 04/48] feat: Expand test simulation with receiveUpcall from cosmos account to orchestration contract --- .../test/examples/my-orch-seq-sim.test.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 685ffa24432..9bb0a25a994 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -13,15 +13,26 @@ const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough -const makeCosmosAccount = (t: Ex, addr: string) => { +const makeOrchContract = (t: Ex, addr = 'agoric1orchFEED') => { + return freeze({ + async receiveUpcall(amt: Coins) { + t.log('orch contract received', amt); + }, + }); +}; + +const makeCosmosAccount = (t: Ex, addr: string, orch?: ReturnType) => { return freeze({ async deposit(amt: Coins) { - t.log('TODO: deposit', amt); + t.log('account received deposit', amt); + if (addr === 'agoric1orchFEED' && orch) { + await orch.receiveUpcall(amt); + } }, }); }; -const makeUA = () => { +const makeUA = (t: Ex) => { return freeze({ signAndBroadcast(t: Ex, amt: Coins, destAddr: string, memo = '') { const dest = makeCosmosAccount(t, destAddr); @@ -31,11 +42,12 @@ const makeUA = () => { }; test('user sends 10 atom', async t => { - const u1 = makeUA(); + const orch = makeOrchContract(t); + const u1 = makeUA(t); const actual = await u1.signAndBroadcast( t, [{ denom: 'ATOM', amount: 10 }], - 'cosmos1567', + 'agoric1orchFEED', ); t.is(actual, undefined); }); From 7f3b28296ec29d37de2a147cb700ea2dc4fbc106 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 10:00:13 -0600 Subject: [PATCH 05/48] chore: refine prototype --- .../test/examples/my-orch-seq-sim.test.ts | 76 ++++++++++++++----- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 9bb0a25a994..9d3f4714700 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -13,41 +13,75 @@ const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough -const makeOrchContract = (t: Ex, addr = 'agoric1orchFEED') => { +const makeCosmosAccount = (addr: string) => { return freeze({ - async receiveUpcall(amt: Coins) { - t.log('orch contract received', amt); + getAddress: () => addr, + async send(t: Ex, amt: Coins, dest: CosmosAccount) { + t.log(addr, 'sending', amt, 'to', dest); + dest.deposit(t, amt); + }, + async deposit(t: Ex, amt: Coins) { + t.log(addr, 'received', amt); }, }); }; +type CosmosAccount = ReturnType; -const makeCosmosAccount = (t: Ex, addr: string, orch?: ReturnType) => { - return freeze({ - async deposit(amt: Coins) { - t.log('account received deposit', amt); - if (addr === 'agoric1orchFEED' && orch) { - await orch.receiveUpcall(amt); +const makeLocalOrchAccount = (addr: string) => { + const base = makeCosmosAccount(addr); + let tap = false; + const self = freeze({ + ...base, + async monitorTransfers() { + tap = true; + }, + async deposit(t: Ex, amt: Coins) { + base.deposit(t, amt); + if (tap) { + await self.receiveUpcall(t, amt); } }, + async receiveUpcall(t: Ex, amt: Coins) { + t.log('orch hook received', amt); + }, }); + return self; }; -const makeUA = (t: Ex) => { +const makeOrchContract = async () => { + const hookAcct = makeLocalOrchAccount('agoric1orchFEED'); + await hookAcct.monitorTransfers(); return freeze({ - signAndBroadcast(t: Ex, amt: Coins, destAddr: string, memo = '') { - const dest = makeCosmosAccount(t, destAddr); - return dest.deposit(amt); + getHookAccount: async () => hookAcct, + }); +}; + +const makeUA = (orch: Awaited>) => { + const myAcct = makeCosmosAccount('cosmos1xyz'); + const hookAcctP = orch.getHookAccount(); + + const signAndBroadcast = async ( + t: Ex, + amt: Coins, + destAddr: string, + memo = '', + ) => { + if (destAddr !== myAcct.getAddress()) throw Error('unsupported'); + const acct = await hookAcctP; + return acct.deposit(t, amt); + }; + + const self = freeze({ + async openPosition(t: Ex, amt: Coins) { + await signAndBroadcast(t, amt, await myAcct.getAddress()); }, }); + return self; }; -test('user sends 10 atom', async t => { - const orch = makeOrchContract(t); - const u1 = makeUA(t); - const actual = await u1.signAndBroadcast( - t, - [{ denom: 'ATOM', amount: 10 }], - 'agoric1orchFEED', - ); +test('user opens a position with 10 ATOM', async t => { + const orch = await makeOrchContract(); + const u1 = makeUA(orch); + const actual = await u1.openPosition(t, [{ denom: 'ATOM', amount: 10 }]); t.is(actual, undefined); }); From a89e338a810ed6e29ff612e3fb5692f5df1ad123 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:05:39 -0600 Subject: [PATCH 06/48] feat: Add send() method to orchestration contract in test simulation --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 9d3f4714700..dab589e3d46 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -43,6 +43,8 @@ const makeLocalOrchAccount = (addr: string) => { }, async receiveUpcall(t: Ex, amt: Coins) { t.log('orch hook received', amt); + // Forward to stride chain + await base.send(t, amt, makeCosmosAccount('stride123')); }, }); return self; From f5f3298bcd4575087e80f21e5c9137fc248c5bc0 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 10:09:09 -0600 Subject: [PATCH 07/48] feat: Add toString method to CosmosAccount for better address representation --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index dab589e3d46..1d53a226ae3 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -15,6 +15,7 @@ type Coins = { denom: string; amount: number }[]; // XXX rough const makeCosmosAccount = (addr: string) => { return freeze({ + toString: () => `<${addr}>`, getAddress: () => addr, async send(t: Ex, amt: Coins, dest: CosmosAccount) { t.log(addr, 'sending', amt, 'to', dest); From 2f5acc2e37f44bf7dd7e37a1ec78afffb7908c1d Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:09:12 -0600 Subject: [PATCH 08/48] fix: Update orchestration test to send funds to Cosmos account first --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 1d53a226ae3..10d3dff9fb9 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -44,8 +44,8 @@ const makeLocalOrchAccount = (addr: string) => { }, async receiveUpcall(t: Ex, amt: Coins) { t.log('orch hook received', amt); - // Forward to stride chain - await base.send(t, amt, makeCosmosAccount('stride123')); + // Send back to cosmos account first + await base.send(t, amt, makeCosmosAccount('cosmos1xyz')); }, }); return self; From 90a549f95cfd2f74b8e6dc4b4a2f9b28d52c2a74 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 10:13:41 -0600 Subject: [PATCH 09/48] fix: Await deposit method in local orchestration account test --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 10d3dff9fb9..3d4860db7f5 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -37,7 +37,7 @@ const makeLocalOrchAccount = (addr: string) => { tap = true; }, async deposit(t: Ex, amt: Coins) { - base.deposit(t, amt); + await base.deposit(t, amt); if (tap) { await self.receiveUpcall(t, amt); } From 167b603ed4875bfea4c53feb93d64fc286e95284 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:13:44 -0600 Subject: [PATCH 10/48] feat: Add optional packet forwarding middleware to send and deposit methods --- .../test/examples/my-orch-seq-sim.test.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 3d4860db7f5..1d2a59b1b5d 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -12,17 +12,18 @@ import type { ExecutionContext as Ex } from 'ava'; const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough +type PFM = { to: string; action?: string } | undefined; const makeCosmosAccount = (addr: string) => { return freeze({ toString: () => `<${addr}>`, getAddress: () => addr, - async send(t: Ex, amt: Coins, dest: CosmosAccount) { - t.log(addr, 'sending', amt, 'to', dest); - dest.deposit(t, amt); + async send(t: Ex, amt: Coins, dest: CosmosAccount, fwd?: PFM) { + t.log(addr, 'sending', amt, 'to', dest, fwd ? `fwd: ${JSON.stringify(fwd)}` : ''); + dest.deposit(t, amt, fwd); }, - async deposit(t: Ex, amt: Coins) { - t.log(addr, 'received', amt); + async deposit(t: Ex, amt: Coins, fwd?: PFM) { + t.log(addr, 'received', amt, fwd ? `fwd: ${JSON.stringify(fwd)}` : ''); }, }); }; @@ -36,16 +37,19 @@ const makeLocalOrchAccount = (addr: string) => { async monitorTransfers() { tap = true; }, - async deposit(t: Ex, amt: Coins) { - await base.deposit(t, amt); + async deposit(t: Ex, amt: Coins, fwd?: PFM) { + await base.deposit(t, amt, fwd); if (tap) { - await self.receiveUpcall(t, amt); + await self.receiveUpcall(t, amt, fwd); } }, - async receiveUpcall(t: Ex, amt: Coins) { + async receiveUpcall(t: Ex, amt: Coins, fwd?: PFM) { t.log('orch hook received', amt); - // Send back to cosmos account first - await base.send(t, amt, makeCosmosAccount('cosmos1xyz')); + // Send back to cosmos account first with forwarding instructions + await base.send(t, amt, makeCosmosAccount('cosmos1xyz'), { + to: 'stride123', + action: 'to stATOM, send to ICA 145 on Elys' + }); }, }); return self; From 4d6c1f8bef1ec4b4f369bcbd03ec6cddb361b10d Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:15:53 -0600 Subject: [PATCH 11/48] refactor: Use forwarding address in receiveUpcall for cosmos account --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 1d2a59b1b5d..c4dccd3b07e 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -46,7 +46,8 @@ const makeLocalOrchAccount = (addr: string) => { async receiveUpcall(t: Ex, amt: Coins, fwd?: PFM) { t.log('orch hook received', amt); // Send back to cosmos account first with forwarding instructions - await base.send(t, amt, makeCosmosAccount('cosmos1xyz'), { + if (!fwd?.to) throw Error('forwarding address required'); + await base.send(t, amt, makeCosmosAccount(fwd.to), { to: 'stride123', action: 'to stATOM, send to ICA 145 on Elys' }); From bab4e4a5bc61fcd3d57981233b2db0a9a20ef588 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:17:19 -0600 Subject: [PATCH 12/48] feat: Add Stride address to orchestration contract for test simulation --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index c4dccd3b07e..588ed492710 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -40,7 +40,7 @@ const makeLocalOrchAccount = (addr: string) => { async deposit(t: Ex, amt: Coins, fwd?: PFM) { await base.deposit(t, amt, fwd); if (tap) { - await self.receiveUpcall(t, amt, fwd); + await self.receiveUpcall(t, amt, { to: strideAddr }); } }, async receiveUpcall(t: Ex, amt: Coins, fwd?: PFM) { @@ -59,14 +59,17 @@ const makeLocalOrchAccount = (addr: string) => { const makeOrchContract = async () => { const hookAcct = makeLocalOrchAccount('agoric1orchFEED'); await hookAcct.monitorTransfers(); + const strideAddr = 'stride123'; return freeze({ getHookAccount: async () => hookAcct, + getStrideAddr: () => strideAddr, }); }; const makeUA = (orch: Awaited>) => { const myAcct = makeCosmosAccount('cosmos1xyz'); const hookAcctP = orch.getHookAccount(); + const strideAddr = orch.getStrideAddr(); const signAndBroadcast = async ( t: Ex, From 75637f5e4df6880677427b7444fc8a8160f7a752 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:17:50 -0600 Subject: [PATCH 13/48] fix: Pass strideAddr to makeLocalOrchAccount to resolve reference error --- .../orchestration/test/examples/my-orch-seq-sim.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 588ed492710..d748cd94976 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -29,7 +29,7 @@ const makeCosmosAccount = (addr: string) => { }; type CosmosAccount = ReturnType; -const makeLocalOrchAccount = (addr: string) => { +const makeLocalOrchAccount = (addr: string, strideAddr: string) => { const base = makeCosmosAccount(addr); let tap = false; const self = freeze({ @@ -57,9 +57,9 @@ const makeLocalOrchAccount = (addr: string) => { }; const makeOrchContract = async () => { - const hookAcct = makeLocalOrchAccount('agoric1orchFEED'); - await hookAcct.monitorTransfers(); const strideAddr = 'stride123'; + const hookAcct = makeLocalOrchAccount('agoric1orchFEED', strideAddr); + await hookAcct.monitorTransfers(); return freeze({ getHookAccount: async () => hookAcct, getStrideAddr: () => strideAddr, From b3eee19dba41319546ede1c716bd6cbb0abf181d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 10:22:23 -0600 Subject: [PATCH 14/48] refactor: Improve logging and upcall handling in orchestration test account --- .../test/examples/my-orch-seq-sim.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index d748cd94976..145ef331136 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -19,7 +19,14 @@ const makeCosmosAccount = (addr: string) => { toString: () => `<${addr}>`, getAddress: () => addr, async send(t: Ex, amt: Coins, dest: CosmosAccount, fwd?: PFM) { - t.log(addr, 'sending', amt, 'to', dest, fwd ? `fwd: ${JSON.stringify(fwd)}` : ''); + t.log( + addr, + 'sending', + amt, + 'to', + `${dest}`, + fwd ? `fwd: ${JSON.stringify(fwd)}` : '', + ); dest.deposit(t, amt, fwd); }, async deposit(t: Ex, amt: Coins, fwd?: PFM) { @@ -40,16 +47,15 @@ const makeLocalOrchAccount = (addr: string, strideAddr: string) => { async deposit(t: Ex, amt: Coins, fwd?: PFM) { await base.deposit(t, amt, fwd); if (tap) { - await self.receiveUpcall(t, amt, { to: strideAddr }); + await self.receiveUpcall(t, amt, fwd); } }, async receiveUpcall(t: Ex, amt: Coins, fwd?: PFM) { t.log('orch hook received', amt); // Send back to cosmos account first with forwarding instructions - if (!fwd?.to) throw Error('forwarding address required'); - await base.send(t, amt, makeCosmosAccount(fwd.to), { - to: 'stride123', - action: 'to stATOM, send to ICA 145 on Elys' + await base.send(t, amt, makeCosmosAccount(strideAddr), { + to: 'elys145', + action: 'Liquid Stake to stATOM', }); }, }); From 9a2350cbaf2d75b6d35339bf117c062550f936c4 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:22:25 -0600 Subject: [PATCH 15/48] feat: Add ICA account forwarding in Stride chain deposit method --- .../orchestration/test/examples/my-orch-seq-sim.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 145ef331136..a1b6dfce69f 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -14,6 +14,8 @@ const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough type PFM = { to: string; action?: string } | undefined; +const makeICAAccount = (addr: string) => makeCosmosAccount(addr); + const makeCosmosAccount = (addr: string) => { return freeze({ toString: () => `<${addr}>`, @@ -31,6 +33,10 @@ const makeCosmosAccount = (addr: string) => { }, async deposit(t: Ex, amt: Coins, fwd?: PFM) { t.log(addr, 'received', amt, fwd ? `fwd: ${JSON.stringify(fwd)}` : ''); + // If this is the Stride chain and we have forwarding instructions, send to ICA + if (addr === 'stride123' && fwd?.to) { + await self.send(t, amt, makeICAAccount(fwd.to)); + } }, }); }; From 5d12e4c07d996605b82bb4c2a438337a9a4032a4 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:23:52 -0600 Subject: [PATCH 16/48] fix: Resolve self reference issue in makeCosmosAccount method --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index a1b6dfce69f..42e1c58a4ef 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -17,7 +17,7 @@ type PFM = { to: string; action?: string } | undefined; const makeICAAccount = (addr: string) => makeCosmosAccount(addr); const makeCosmosAccount = (addr: string) => { - return freeze({ + const self = { toString: () => `<${addr}>`, getAddress: () => addr, async send(t: Ex, amt: Coins, dest: CosmosAccount, fwd?: PFM) { @@ -38,7 +38,8 @@ const makeCosmosAccount = (addr: string) => { await self.send(t, amt, makeICAAccount(fwd.to)); } }, - }); + }; + return freeze(self); }; type CosmosAccount = ReturnType; From 98f47712e1f580cacaf7c046ff33b3cd5b298f41 Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:25:54 -0600 Subject: [PATCH 17/48] feat: Enhance ICA account with stATOM conversion and balance tracking --- .../test/examples/my-orch-seq-sim.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 42e1c58a4ef..a1ada00076d 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -14,7 +14,22 @@ const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough type PFM = { to: string; action?: string } | undefined; -const makeICAAccount = (addr: string) => makeCosmosAccount(addr); +const makeICAAccount = (addr: string) => { + const base = makeCosmosAccount(addr); + return freeze({ + ...base, + async deposit(t: Ex, amt: Coins, fwd?: PFM) { + await base.deposit(t, amt, fwd); + // After receiving ATOM, send ack and convert to stATOM + if (amt[0].denom === 'ATOM') { + const orchAcct = makeLocalOrchAccount('agoric1orchFEED', 'stride123'); + await orchAcct.resolve(t, 'ack'); + // Send stATOM to user's Elys account + await base.send(t, [{ denom: 'stATOM', amount: amt[0].amount * 0.9 }], makeCosmosAccount('elsy176')); + } + }, + }); +}; const makeCosmosAccount = (addr: string) => { const self = { @@ -65,6 +80,9 @@ const makeLocalOrchAccount = (addr: string, strideAddr: string) => { action: 'Liquid Stake to stATOM', }); }, + async resolve(t: Ex, msg: string) { + t.log('orch hook resolved:', msg); + }, }); return self; }; @@ -98,6 +116,10 @@ const makeUA = (orch: Awaited>) => { const self = freeze({ async openPosition(t: Ex, amt: Coins) { await signAndBroadcast(t, amt, await myAcct.getAddress()); + // Check final balance + const destAcct = makeCosmosAccount('elsy176'); + t.log('checking final balance at', destAcct); + t.log('final balance:', [{ denom: 'stATOM', amount: amt[0].amount * 0.9 }]); }, }); return self; From c586fdb24c607ece7d49e1a48775bc458574d72f Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:30:21 -0600 Subject: [PATCH 18/48] feat: Add getBalances method to CosmosAccount and use it in openPosition --- .../orchestration/test/examples/my-orch-seq-sim.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index a1ada00076d..9064b44d09c 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -53,6 +53,10 @@ const makeCosmosAccount = (addr: string) => { await self.send(t, amt, makeICAAccount(fwd.to)); } }, + async getBalances(t: Ex): Promise { + t.log(addr, 'checking balances'); + return [{ denom: 'stATOM', amount: 9 }]; // Mock balance for demo + }, }; return freeze(self); }; @@ -119,7 +123,8 @@ const makeUA = (orch: Awaited>) => { // Check final balance const destAcct = makeCosmosAccount('elsy176'); t.log('checking final balance at', destAcct); - t.log('final balance:', [{ denom: 'stATOM', amount: amt[0].amount * 0.9 }]); + const balance = await destAcct.getBalances(t); + t.log('final balance:', balance); }, }); return self; From 9f8ab326d90904cc08e7840543fc77fa41d5ccdc Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:33:33 -0600 Subject: [PATCH 19/48] refactor: Rename `makeICAAccount` to `makeStrideICAAccount` --- packages/orchestration/test/examples/my-orch-seq-sim.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 9064b44d09c..f036ccaeb9d 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -14,7 +14,7 @@ const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough type PFM = { to: string; action?: string } | undefined; -const makeICAAccount = (addr: string) => { +const makeStrideICAAccount = (addr: string) => { const base = makeCosmosAccount(addr); return freeze({ ...base, @@ -50,7 +50,7 @@ const makeCosmosAccount = (addr: string) => { t.log(addr, 'received', amt, fwd ? `fwd: ${JSON.stringify(fwd)}` : ''); // If this is the Stride chain and we have forwarding instructions, send to ICA if (addr === 'stride123' && fwd?.to) { - await self.send(t, amt, makeICAAccount(fwd.to)); + await self.send(t, amt, makeStrideICAAccount(fwd.to)); } }, async getBalances(t: Ex): Promise { From 2c0606f22f47e3aa88cd77d03e3cc75104a8ef2d Mon Sep 17 00:00:00 2001 From: "Dan Connolly (aider)" Date: Mon, 27 Jan 2025 10:38:56 -0600 Subject: [PATCH 20/48] feat: Return and validate balance in openPosition method --- .../orchestration/test/examples/my-orch-seq-sim.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index f036ccaeb9d..977154b051f 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -118,13 +118,14 @@ const makeUA = (orch: Awaited>) => { }; const self = freeze({ - async openPosition(t: Ex, amt: Coins) { + async openPosition(t: Ex, amt: Coins): Promise { await signAndBroadcast(t, amt, await myAcct.getAddress()); // Check final balance const destAcct = makeCosmosAccount('elsy176'); t.log('checking final balance at', destAcct); const balance = await destAcct.getBalances(t); t.log('final balance:', balance); + return balance; }, }); return self; @@ -133,6 +134,6 @@ const makeUA = (orch: Awaited>) => { test('user opens a position with 10 ATOM', async t => { const orch = await makeOrchContract(); const u1 = makeUA(orch); - const actual = await u1.openPosition(t, [{ denom: 'ATOM', amount: 10 }]); - t.is(actual, undefined); + const balance = await u1.openPosition(t, [{ denom: 'ATOM', amount: 10 }]); + t.deepEqual(balance, [{ denom: 'stATOM', amount: 9 }]); }); From 72958c997bd46f19bd74a0e28fad319136281e3a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 11:02:06 -0600 Subject: [PATCH 21/48] test: finish up simulation - fix liquid staking / forwarding logic - StrideAccount is not ICA - make a stride account for strideAddr - punt resolve; it's just promise resolution --- .../test/examples/my-orch-seq-sim.test.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts index 977154b051f..63f69079419 100644 --- a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts +++ b/packages/orchestration/test/examples/my-orch-seq-sim.test.ts @@ -14,18 +14,17 @@ const { freeze } = Object; type Coins = { denom: string; amount: number }[]; // XXX rough type PFM = { to: string; action?: string } | undefined; -const makeStrideICAAccount = (addr: string) => { +const makeStrideAccount = (addr: string) => { const base = makeCosmosAccount(addr); return freeze({ ...base, async deposit(t: Ex, amt: Coins, fwd?: PFM) { - await base.deposit(t, amt, fwd); + await base.deposit(t, amt); // After receiving ATOM, send ack and convert to stATOM - if (amt[0].denom === 'ATOM') { - const orchAcct = makeLocalOrchAccount('agoric1orchFEED', 'stride123'); - await orchAcct.resolve(t, 'ack'); + if (amt[0].denom === 'ATOM' && fwd?.action === 'Liquid Stake to stATOM') { + const strideAmt = [{ denom: 'stATOM', amount: amt[0].amount * 0.9 }]; // Send stATOM to user's Elys account - await base.send(t, [{ denom: 'stATOM', amount: amt[0].amount * 0.9 }], makeCosmosAccount('elsy176')); + await base.send(t, strideAmt, makeCosmosAccount(fwd.to)); } }, }); @@ -48,9 +47,11 @@ const makeCosmosAccount = (addr: string) => { }, async deposit(t: Ex, amt: Coins, fwd?: PFM) { t.log(addr, 'received', amt, fwd ? `fwd: ${JSON.stringify(fwd)}` : ''); - // If this is the Stride chain and we have forwarding instructions, send to ICA - if (addr === 'stride123' && fwd?.to) { - await self.send(t, amt, makeStrideICAAccount(fwd.to)); + // If we have forwarding instructions, do so + if (fwd) { + const { to } = fwd; + const dest = accountMaker(to)(to, 'stride123'); + await self.send(t, amt, dest); } }, async getBalances(t: Ex): Promise { @@ -62,6 +63,13 @@ const makeCosmosAccount = (addr: string) => { }; type CosmosAccount = ReturnType; +const accountMaker = (to: string) => + to.startsWith('stride1') + ? makeStrideAccount + : to.startsWith('agoric1') + ? makeLocalOrchAccount + : makeCosmosAccount; + const makeLocalOrchAccount = (addr: string, strideAddr: string) => { const base = makeCosmosAccount(addr); let tap = false; @@ -79,14 +87,11 @@ const makeLocalOrchAccount = (addr: string, strideAddr: string) => { async receiveUpcall(t: Ex, amt: Coins, fwd?: PFM) { t.log('orch hook received', amt); // Send back to cosmos account first with forwarding instructions - await base.send(t, amt, makeCosmosAccount(strideAddr), { + await base.send(t, amt, makeStrideAccount(strideAddr), { to: 'elys145', action: 'Liquid Stake to stATOM', }); }, - async resolve(t: Ex, msg: string) { - t.log('orch hook resolved:', msg); - }, }); return self; }; @@ -104,7 +109,6 @@ const makeOrchContract = async () => { const makeUA = (orch: Awaited>) => { const myAcct = makeCosmosAccount('cosmos1xyz'); const hookAcctP = orch.getHookAccount(); - const strideAddr = orch.getStrideAddr(); const signAndBroadcast = async ( t: Ex, @@ -122,7 +126,7 @@ const makeUA = (orch: Awaited>) => { await signAndBroadcast(t, amt, await myAcct.getAddress()); // Check final balance const destAcct = makeCosmosAccount('elsy176'); - t.log('checking final balance at', destAcct); + t.log('checking final balance at', `${destAcct}`); const balance = await destAcct.getBalances(t); t.log('final balance:', balance); return balance; From 204433c83e7cd893ace78ecbfd78c5cdaddf3310 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 21:36:19 -0600 Subject: [PATCH 22/48] chore(orchestration): include labels in OrchestrationPowersShape --- packages/orchestration/src/typeGuards.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index ec13eb4b9f5..3f2fda69ad6 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -243,11 +243,11 @@ export const AnyNatAmountsRecord = M.and( /** @type {TypedPattern} */ export const OrchestrationPowersShape = { - agoricNames: M.remotable(), - localchain: M.remotable(), - orchestrationService: M.remotable(), - storageNode: M.remotable(), - timerService: M.remotable(), + agoricNames: M.remotable('agoricNames'), + localchain: M.remotable('localchain'), + orchestrationService: M.remotable('orchestrationService'), + storageNode: M.remotable('storageNode'), + timerService: M.remotable('timerService'), }; harden(OrchestrationPowersShape); From 53b03a74d777a8599242adcc28c780bb0c10fbd1 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 22:03:36 -0600 Subject: [PATCH 23/48] test: start template orch. contract (w/test) --- .../orchestration/src/examples/my.contract.js | 22 ++++++++++ .../test/examples/my-orch-contract.test.ts | 42 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 packages/orchestration/src/examples/my.contract.js create mode 100644 packages/orchestration/test/examples/my-orch-contract.test.ts diff --git a/packages/orchestration/src/examples/my.contract.js b/packages/orchestration/src/examples/my.contract.js new file mode 100644 index 00000000000..8c3085947ff --- /dev/null +++ b/packages/orchestration/src/examples/my.contract.js @@ -0,0 +1,22 @@ +import { M } from '@endo/patterns'; +import { OrchestrationPowersShape } from '../typeGuards.js'; +import { withOrchestration } from '../utils/start-helper.js'; + +export const meta = M.splitRecord({ + privateArgsShape: { + // @ts-expect-error TypedPattern not recognized as record + ...OrchestrationPowersShape, + marshaller: M.remotable('marshaller'), + }, +}); +harden(meta); + +export const contract = async (_zcf, _privateArgs, zone, _tools) => { + return { + publicFacet: zone.exo('MyPub', undefined, {}), + creatorFacet: zone.exo('MyCreator', undefined, {}), + }; +}; + +export const start = withOrchestration(contract); +harden(start); diff --git a/packages/orchestration/test/examples/my-orch-contract.test.ts b/packages/orchestration/test/examples/my-orch-contract.test.ts new file mode 100644 index 00000000000..fb56bfc1c29 --- /dev/null +++ b/packages/orchestration/test/examples/my-orch-contract.test.ts @@ -0,0 +1,42 @@ +// prepare-test-env has to go 1st; use a blank line to separate it +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { E, passStyleOf } from '@endo/far'; +import { M, mustMatch } from '@endo/patterns'; +import { createRequire } from 'module'; +import { commonSetup } from '../supports.js'; + +const nodeRequire = createRequire(import.meta.url); + +const contractName = 'myOrchContract'; +const contractFile = nodeRequire.resolve('../../src/examples/my.contract.js'); +type StartFn = typeof import('../../src/examples/my.contract.js').start; + +test('start my orch contract', async t => { + const { commonPrivateArgs } = await commonSetup(t); + const { zoe, bundleAndInstall } = await setUpZoeForTest(); + t.log('contract deployment', contractName); + + const installation: Installation = + await bundleAndInstall(contractFile); + t.is(passStyleOf(installation), 'remotable'); + + const myKit = await E(zoe).startInstance( + installation, + {}, // issuers + {}, // terms + commonPrivateArgs, // privateArgs + ); + t.notThrows(() => + mustMatch( + myKit, + M.splitRecord({ + instance: M.remotable(), + publicFacet: M.remotable(), + creatorFacet: M.remotable(), + // ...others are not relevant here + }), + ), + ); +}); From d0b7e254458e9495a7b0be97c5415ac5acc4ba48 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 21:57:22 -0600 Subject: [PATCH 24/48] feat: make hook account on Agoric; add public .getAddress() --- .../orchestration/src/examples/my.contract.js | 27 +++++++++++++++++-- .../orchestration/src/examples/my.flows.js | 13 +++++++++ .../test/examples/my-orch-contract.test.ts | 5 ++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 packages/orchestration/src/examples/my.flows.js diff --git a/packages/orchestration/src/examples/my.contract.js b/packages/orchestration/src/examples/my.contract.js index 8c3085947ff..bc121d02d21 100644 --- a/packages/orchestration/src/examples/my.contract.js +++ b/packages/orchestration/src/examples/my.contract.js @@ -1,6 +1,13 @@ import { M } from '@endo/patterns'; import { OrchestrationPowersShape } from '../typeGuards.js'; import { withOrchestration } from '../utils/start-helper.js'; +import * as flows from './my.flows.js'; +import { E } from '@endo/far'; + +/** + * @import {Zone} from '@agoric/zone'; + * @import {OrchestrationTools} from '../types.js'; + */ export const meta = M.splitRecord({ privateArgsShape: { @@ -11,9 +18,25 @@ export const meta = M.splitRecord({ }); harden(meta); -export const contract = async (_zcf, _privateArgs, zone, _tools) => { +/** + * @param {any} _zcf + * @param {any} _privateArgs + * @param {Zone} zone + * @param {OrchestrationTools} tools + * @returns + */ +export const contract = async (_zcf, _privateArgs, zone, tools) => { + const { orchestrateAll } = tools; + const { makeHookAccount } = orchestrateAll(flows, {}); + + const hookAccountV = zone.makeOnce('hookAccount', _key => makeHookAccount()); + + const { when } = tools.vowTools; + return { - publicFacet: zone.exo('MyPub', undefined, {}), + publicFacet: zone.exo('MyPub', undefined, { + getHookAddress: () => E(when(hookAccountV)).getAddress(), + }), creatorFacet: zone.exo('MyCreator', undefined, {}), }; }; diff --git a/packages/orchestration/src/examples/my.flows.js b/packages/orchestration/src/examples/my.flows.js new file mode 100644 index 00000000000..689ecdb7402 --- /dev/null +++ b/packages/orchestration/src/examples/my.flows.js @@ -0,0 +1,13 @@ +/** + * @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration'; + */ + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + */ +export const makeHookAccount = async orch => { + const agoricChain = await orch.getChain('agoric'); + return agoricChain.makeAccount(); +}; +harden(makeHookAccount); diff --git a/packages/orchestration/test/examples/my-orch-contract.test.ts b/packages/orchestration/test/examples/my-orch-contract.test.ts index fb56bfc1c29..65216e3f018 100644 --- a/packages/orchestration/test/examples/my-orch-contract.test.ts +++ b/packages/orchestration/test/examples/my-orch-contract.test.ts @@ -5,6 +5,7 @@ import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E, passStyleOf } from '@endo/far'; import { M, mustMatch } from '@endo/patterns'; import { createRequire } from 'module'; +import { ChainAddressShape } from '../../src/typeGuards.js'; import { commonSetup } from '../supports.js'; const nodeRequire = createRequire(import.meta.url); @@ -39,4 +40,8 @@ test('start my orch contract', async t => { }), ), ); + + const hookAddress = await E(myKit.publicFacet).getHookAddress(); + t.log('hookAddress', hookAddress); + t.notThrows(() => mustMatch(hookAddress, ChainAddressShape)); }); From 954902f602ca03a280115fcd6b20234760a13ae5 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 22:42:00 -0600 Subject: [PATCH 25/48] fixup: interfaceTODO --- packages/orchestration/src/examples/my.contract.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/src/examples/my.contract.js b/packages/orchestration/src/examples/my.contract.js index bc121d02d21..a1a32cb68bf 100644 --- a/packages/orchestration/src/examples/my.contract.js +++ b/packages/orchestration/src/examples/my.contract.js @@ -4,6 +4,11 @@ import { withOrchestration } from '../utils/start-helper.js'; import * as flows from './my.flows.js'; import { E } from '@endo/far'; +/** + * @import {VTransferIBCEvent} from '@agoric/vats' + */ +const interfaceTODO = undefined; + /** * @import {Zone} from '@agoric/zone'; * @import {OrchestrationTools} from '../types.js'; @@ -34,7 +39,7 @@ export const contract = async (_zcf, _privateArgs, zone, tools) => { const { when } = tools.vowTools; return { - publicFacet: zone.exo('MyPub', undefined, { + publicFacet: zone.exo('MyPub', interfaceTODO, { getHookAddress: () => E(when(hookAccountV)).getAddress(), }), creatorFacet: zone.exo('MyCreator', undefined, {}), From 5f5c743744cbf4945b744c226bd07bb61fcac781 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 22:43:57 -0600 Subject: [PATCH 26/48] WIP: makePosition flow stub --- packages/orchestration/src/examples/my.flows.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/examples/my.flows.js b/packages/orchestration/src/examples/my.flows.js index 689ecdb7402..202a1ecadbd 100644 --- a/packages/orchestration/src/examples/my.flows.js +++ b/packages/orchestration/src/examples/my.flows.js @@ -1,13 +1,22 @@ /** - * @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration'; + * @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration' + * @import {TargetApp} from '@agoric/vats/src/bridge-target' + * @import {Passable} from '@endo/pass-style' */ /** * @satisfies {OrchestrationFlow} * @param {Orchestrator} orch + * @param {unknown} _ctx + * @param {TargetApp & Passable} tap */ -export const makeHookAccount = async orch => { +export const makeHookAccount = async (orch, _ctx, tap) => { const agoricChain = await orch.getChain('agoric'); - return agoricChain.makeAccount(); + const hookAccount = await agoricChain.makeAccount(); + + const registration = hookAccount.monitorTransfers(tap); + console.warn('TODO: keep registration', registration); + + return hookAccount; }; harden(makeHookAccount); From 4b84cac420ad7d675ccb444117fdac1c69f7b28c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 22:44:46 -0600 Subject: [PATCH 27/48] WIP(contract): make tap --- .../orchestration/src/examples/my.contract.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/examples/my.contract.js b/packages/orchestration/src/examples/my.contract.js index a1a32cb68bf..467d783f392 100644 --- a/packages/orchestration/src/examples/my.contract.js +++ b/packages/orchestration/src/examples/my.contract.js @@ -32,9 +32,22 @@ harden(meta); */ export const contract = async (_zcf, _privateArgs, zone, tools) => { const { orchestrateAll } = tools; - const { makeHookAccount } = orchestrateAll(flows, {}); - - const hookAccountV = zone.makeOnce('hookAccount', _key => makeHookAccount()); + const { makeHookAccount, makePosition } = orchestrateAll(flows, {}); + + const tap = zone.makeOnce('tapPosition', _key => { + console.log('making tap'); + return zone.exo('tap', interfaceTODO, { + /** @param {VTransferIBCEvent} event */ + async receiveUpcall(event) { + console.log(event); + return makePosition(); // TODO: vow handling? + }, + }); + }); + + const hookAccountV = zone.makeOnce('hookAccount', _key => + makeHookAccount(tap), + ); const { when } = tools.vowTools; From 10ebbce8dbab1612d26fb292f0db16f2f16023f0 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 22:45:07 -0600 Subject: [PATCH 28/48] WIP: makePosition flow stub --- packages/orchestration/src/examples/my.flows.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/orchestration/src/examples/my.flows.js b/packages/orchestration/src/examples/my.flows.js index 202a1ecadbd..0acae23a44e 100644 --- a/packages/orchestration/src/examples/my.flows.js +++ b/packages/orchestration/src/examples/my.flows.js @@ -20,3 +20,11 @@ export const makeHookAccount = async (orch, _ctx, tap) => { return hookAccount; }; harden(makeHookAccount); + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + */ +export const makePosition = async orch => { + throw Error('TODO!'); +}; From 1cb70e7417a4b70ba362881ffbc808476d4f38be Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 27 Jan 2025 22:45:33 -0600 Subject: [PATCH 29/48] WIP(test): try sending over the bridge to the tap --- .../test/examples/my-orch-contract.test.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/test/examples/my-orch-contract.test.ts b/packages/orchestration/test/examples/my-orch-contract.test.ts index 65216e3f018..a3deccf888e 100644 --- a/packages/orchestration/test/examples/my-orch-contract.test.ts +++ b/packages/orchestration/test/examples/my-orch-contract.test.ts @@ -1,11 +1,16 @@ // prepare-test-env has to go 1st; use a blank line to separate it import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import type { CoinSDKType } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { heapVowE as VE } from '@agoric/vow/vat.js'; import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E, passStyleOf } from '@endo/far'; +import { Nat } from '@endo/nat'; import { M, mustMatch } from '@endo/patterns'; import { createRequire } from 'module'; import { ChainAddressShape } from '../../src/typeGuards.js'; +import { buildVTransferEvent } from '../../tools/ibc-mocks.js'; import { commonSetup } from '../supports.js'; const nodeRequire = createRequire(import.meta.url); @@ -15,7 +20,7 @@ const contractFile = nodeRequire.resolve('../../src/examples/my.contract.js'); type StartFn = typeof import('../../src/examples/my.contract.js').start; test('start my orch contract', async t => { - const { commonPrivateArgs } = await commonSetup(t); + const common = await commonSetup(t); const { zoe, bundleAndInstall } = await setUpZoeForTest(); t.log('contract deployment', contractName); @@ -27,7 +32,7 @@ test('start my orch contract', async t => { installation, {}, // issuers {}, // terms - commonPrivateArgs, // privateArgs + common.commonPrivateArgs, ); t.notThrows(() => mustMatch( @@ -44,4 +49,21 @@ test('start my orch contract', async t => { const hookAddress = await E(myKit.publicFacet).getHookAddress(); t.log('hookAddress', hookAddress); t.notThrows(() => mustMatch(hookAddress, ChainAddressShape)); + + const { transferBridge } = common.mocks; + const deposit = async (coins: CoinSDKType) => { + await VE(transferBridge).fromBridge( + buildVTransferEvent({ + receiver: 'rx TODO', + target: 'agoric1fakeLCAAddress', + sourceChannel: 'channel-1', // TODO: hubToAg.transferChannel.counterPartyChannelId, + denom: coins.denom, + amount: Nat(BigInt(coins.amount)), + sender: 'cosmos1xyz', + }), + ); + await eventLoopIteration(); // let contract do work + }; + + await t.notThrowsAsync(deposit({ amount: '10000000', denom: 'uatom' })); }); From 0d21ee5ed75ff664d0a6007f5409a068cd57fe6c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 10 Feb 2025 14:11:33 -0600 Subject: [PATCH 30/48] fixup: handle how in receiveUpcall --- packages/orchestration/src/examples/my.contract.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/examples/my.contract.js b/packages/orchestration/src/examples/my.contract.js index 467d783f392..a191d0bc920 100644 --- a/packages/orchestration/src/examples/my.contract.js +++ b/packages/orchestration/src/examples/my.contract.js @@ -34,13 +34,15 @@ export const contract = async (_zcf, _privateArgs, zone, tools) => { const { orchestrateAll } = tools; const { makeHookAccount, makePosition } = orchestrateAll(flows, {}); + const { when } = tools.vowTools; + const tap = zone.makeOnce('tapPosition', _key => { console.log('making tap'); return zone.exo('tap', interfaceTODO, { /** @param {VTransferIBCEvent} event */ async receiveUpcall(event) { console.log(event); - return makePosition(); // TODO: vow handling? + return when(makePosition()); }, }); }); @@ -49,8 +51,6 @@ export const contract = async (_zcf, _privateArgs, zone, tools) => { makeHookAccount(tap), ); - const { when } = tools.vowTools; - return { publicFacet: zone.exo('MyPub', interfaceTODO, { getHookAddress: () => E(when(hookAccountV)).getAddress(), From 6fc135b6064e50e4ae1e1c533594a18acd9ef77c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:33:02 -0500 Subject: [PATCH 31/48] chore(orch-skel): move stuff from orchestration/{src,test}/examples --- .../{orchestration/src/examples => orch-skel/src}/my.contract.js | 0 .../{orchestration/src/examples => orch-skel/src}/my.flows.js | 0 .../test/examples => orch-skel/test}/my-orch-contract.test.ts | 0 .../test/examples => orch-skel/test}/my-orch-seq-sim.test.ts | 0 .../test/examples => orch-skel/test}/my-orch-sequence.mmd | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename packages/{orchestration/src/examples => orch-skel/src}/my.contract.js (100%) rename packages/{orchestration/src/examples => orch-skel/src}/my.flows.js (100%) rename packages/{orchestration/test/examples => orch-skel/test}/my-orch-contract.test.ts (100%) rename packages/{orchestration/test/examples => orch-skel/test}/my-orch-seq-sim.test.ts (100%) rename packages/{orchestration/test/examples => orch-skel/test}/my-orch-sequence.mmd (100%) diff --git a/packages/orchestration/src/examples/my.contract.js b/packages/orch-skel/src/my.contract.js similarity index 100% rename from packages/orchestration/src/examples/my.contract.js rename to packages/orch-skel/src/my.contract.js diff --git a/packages/orchestration/src/examples/my.flows.js b/packages/orch-skel/src/my.flows.js similarity index 100% rename from packages/orchestration/src/examples/my.flows.js rename to packages/orch-skel/src/my.flows.js diff --git a/packages/orchestration/test/examples/my-orch-contract.test.ts b/packages/orch-skel/test/my-orch-contract.test.ts similarity index 100% rename from packages/orchestration/test/examples/my-orch-contract.test.ts rename to packages/orch-skel/test/my-orch-contract.test.ts diff --git a/packages/orchestration/test/examples/my-orch-seq-sim.test.ts b/packages/orch-skel/test/my-orch-seq-sim.test.ts similarity index 100% rename from packages/orchestration/test/examples/my-orch-seq-sim.test.ts rename to packages/orch-skel/test/my-orch-seq-sim.test.ts diff --git a/packages/orchestration/test/examples/my-orch-sequence.mmd b/packages/orch-skel/test/my-orch-sequence.mmd similarity index 100% rename from packages/orchestration/test/examples/my-orch-sequence.mmd rename to packages/orch-skel/test/my-orch-sequence.mmd From a748a22a3a5c66821f24d12d2a30847d7ecee6e9 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:38:13 -0500 Subject: [PATCH 32/48] chore: .js -> .ts a la Fast USDC --- packages/orch-skel/src/{my.contract.js => my.contract.ts} | 0 packages/orch-skel/src/{my.flows.js => my.flows.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/orch-skel/src/{my.contract.js => my.contract.ts} (100%) rename packages/orch-skel/src/{my.flows.js => my.flows.ts} (100%) diff --git a/packages/orch-skel/src/my.contract.js b/packages/orch-skel/src/my.contract.ts similarity index 100% rename from packages/orch-skel/src/my.contract.js rename to packages/orch-skel/src/my.contract.ts diff --git a/packages/orch-skel/src/my.flows.js b/packages/orch-skel/src/my.flows.ts similarity index 100% rename from packages/orch-skel/src/my.flows.js rename to packages/orch-skel/src/my.flows.ts From a26f48684abaaf1f30ab1600e5f77f3a82b6365d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:53:49 -0500 Subject: [PATCH 33/48] chore: copy test supports, mocks from fast-usdc-contract --- packages/orch-skel/test/mocks.ts | 132 ++++++++++++ packages/orch-skel/test/supports.ts | 304 ++++++++++++++++++++++++++++ 2 files changed, 436 insertions(+) create mode 100644 packages/orch-skel/test/mocks.ts create mode 100644 packages/orch-skel/test/supports.ts diff --git a/packages/orch-skel/test/mocks.ts b/packages/orch-skel/test/mocks.ts new file mode 100644 index 00000000000..f0e392b7925 --- /dev/null +++ b/packages/orch-skel/test/mocks.ts @@ -0,0 +1,132 @@ +import type { HostInterface } from '@agoric/async-flow'; +import type { Brand, Issuer, Payment } from '@agoric/ertp'; +import type { + CosmosChainAddress, + DenomAmount, + OrchestrationAccount, +} from '@agoric/orchestration'; +import type { VowTools } from '@agoric/vow'; +import { makeRatio } from '@agoric/ertp/src/ratio.js'; +import type { AmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import type { Zone } from '@agoric/zone'; +import type { FeeConfig, LogFn } from '@agoric/fast-usdc/src/types.js'; +import { makePromiseKit } from '@endo/promise-kit'; + +export const prepareMockOrchAccounts = ( + zone: Zone, + { + vowTools: { makeVowKit, asVow }, + log, + usdc, + }: { + vowTools: VowTools; + log: (...args: any[]) => void; + usdc: { brand: Brand<'nat'>; issuer: Issuer<'nat'> }; + }, +) => { + // each can only be resolved/rejected once per test + const poolAccountSendPK = makePromiseKit(); + const poolAccountTransferPK = makePromiseKit(); + const settleAccountTransferPK = makePromiseKit(); + const settleAccountSendPK = makePromiseKit(); + const intermediateAccountTransferPK = makePromiseKit(); + const intermediateAccountDepositForBurnPK = makePromiseKit(); + + const mockedPoolAccount = zone.exo('Mock Pool LocalOrchAccount', undefined, { + transfer(destination: CosmosChainAddress, amount: DenomAmount) { + log('PoolAccount.transfer() called with', destination, amount); + return poolAccountTransferPK.promise; + }, + deposit(payment: Payment<'nat'>) { + log('PoolAccount.deposit() called with', payment); + // XXX consider a mock for deposit failure + return asVow(async () => usdc.issuer.getAmountOf(payment)); + }, + send(destination: CosmosChainAddress, amount: DenomAmount) { + log('PoolAccount.send() called with', destination, amount); + return poolAccountSendPK.promise; + }, + }); + + const poolAccount = mockedPoolAccount as unknown as HostInterface< + OrchestrationAccount<{ chainId: 'agoric-any' }> + >; + + const settlementCallLog = [] as any[]; + const settlementAccountMock = zone.exo('Mock Settlement Account', undefined, { + transfer(...args) { + settlementCallLog.push(harden(['transfer', ...args])); + return settleAccountTransferPK.promise; + }, + send(...args) { + settlementCallLog.push(harden(['send', ...args])); + return settleAccountSendPK.promise; + }, + }); + const settlementAccount = settlementAccountMock as unknown as HostInterface< + OrchestrationAccount<{ chainId: 'agoric-any' }> + >; + const intermediateCallLog = [] as any[]; + const intermediateAccountMock = zone.exo('Mock Noble ICA', undefined, { + getAddress(): CosmosChainAddress { + return { + chainId: 'noble-1', + encoding: 'bech32', + value: 'noble1test', + }; + }, + transfer(...args) { + intermediateCallLog.push(harden(['transfer', ...args])); + return intermediateAccountTransferPK.promise; + }, + depositForBurn(...args) { + intermediateCallLog.push(harden(['depositForBurn', ...args])); + return intermediateAccountDepositForBurnPK.promise; + }, + }); + const intermediateAccount = + intermediateAccountMock as unknown as HostInterface< + OrchestrationAccount<{ chainId: 'noble-any' }> + >; + return { + // These each have VResolver for "vow" resolver. The mocks actually + // deal in promises but the flow that awaits them expects that it's actually + // awaiting a vow (made by the membrane to look like a promise). + mockPoolAccount: { + account: poolAccount, + transferVResolver: poolAccountTransferPK, + sendVResolver: poolAccountSendPK, + }, + settlement: { + account: settlementAccount, + callLog: settlementCallLog, + transferVResolver: settleAccountTransferPK, + sendVResolver: settleAccountSendPK, + }, + intermediate: { + account: intermediateAccount, + callLog: intermediateCallLog, + transferVResolver: intermediateAccountTransferPK, + depositForBurnVResolver: intermediateAccountDepositForBurnPK, + }, + }; +}; + +export const makeTestLogger = (logger: LogFn) => { + const logs: unknown[][] = []; + const log = (...args: any[]) => { + logs.push(args); + logger(args); + }; + const inspectLogs = (index?: number) => + typeof index === 'number' ? logs[index] : logs; + return { log, inspectLogs }; +}; +export type TestLogger = ReturnType; + +export const makeTestFeeConfig = (usdc: Omit): FeeConfig => + harden({ + flat: usdc.make(1n), + variableRate: makeRatio(2n, usdc.brand), + contractRate: makeRatio(20n, usdc.brand), + }); diff --git a/packages/orch-skel/test/supports.ts b/packages/orch-skel/test/supports.ts new file mode 100644 index 00000000000..b6c64c7d38f --- /dev/null +++ b/packages/orch-skel/test/supports.ts @@ -0,0 +1,304 @@ +import { makeIssuerKit } from '@agoric/ertp'; +import { VTRANSFER_IBC_EVENT } from '@agoric/internal/src/action-types.js'; +import { + defaultSerializer, + makeFakeStorageKit, +} from '@agoric/internal/src/storage-test-utils.js'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { + denomHash, + withChainCapabilities, + type CosmosChainInfo, + type Denom, +} from '@agoric/orchestration'; +import { registerKnownChains } from '@agoric/orchestration/src/chain-info.js'; +import { + makeChainHub, + type DenomDetail, +} from '@agoric/orchestration/src/exos/chain-hub.js'; +import { prepareCosmosInterchainService } from '@agoric/orchestration/src/exos/cosmos-interchain-service.js'; +import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; +import { setupFakeNetwork } from '@agoric/orchestration/test/network-fakes.js'; +import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; +import { makeTestAddress } from '@agoric/orchestration/tools/make-test-address.js'; +import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; +import { makeNameHubKit } from '@agoric/vats'; +import { prepareBridgeTargetModule } from '@agoric/vats/src/bridge-target.js'; +import { makeWellKnownSpaces } from '@agoric/vats/src/core/utils.js'; +import { prepareLocalChainTools } from '@agoric/vats/src/localchain.js'; +import { prepareTransferTools } from '@agoric/vats/src/transfer.js'; +import { makeFakeBankManagerKit } from '@agoric/vats/tools/bank-utils.js'; +import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; +import { + makeFakeLocalchainBridge, + makeFakeTransferBridge, +} from '@agoric/vats/tools/fake-bridge.js'; +import { prepareSwingsetVowTools } from '@agoric/vow/vat.js'; +import type { Installation } from '@agoric/zoe/src/zoeService/utils.js'; +import { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js'; +import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import { makeHeapZone, type Zone } from '@agoric/zone'; +import { makeDurableZone } from '@agoric/zone/durable.js'; +import { E } from '@endo/far'; +import type { ExecutionContext } from 'ava'; +import cctpChainInfo from '@agoric/orchestration/src/cctp-chain-info.js'; +import { objectMap } from '@endo/patterns'; +import type { ChainHubChainInfo } from '@agoric/fast-usdc/src/types.js'; +import { CosmosChainInfoShapeV1 } from '@agoric/fast-usdc/src/type-guards.js'; +import { makeTestFeeConfig } from './mocks.js'; + +export { + makeFakeLocalchainBridge, + makeFakeTransferBridge, +} from '@agoric/vats/tools/fake-bridge.js'; + +const assetOn = ( + baseDenom: Denom, + baseName: string, + chainName?: string, + infoOf?: Record, + brandKey?: string, +): [string, DenomDetail & { brandKey?: string }] => { + if (!chainName) { + return [baseDenom, { baseName, chainName: baseName, baseDenom }]; + } + if (!infoOf) throw Error(`must provide infoOf`); + const issuerInfo = infoOf[baseName]; + const holdingInfo = infoOf[chainName]; + if (!holdingInfo) throw Error(`${chainName} missing`); + if (!holdingInfo.connections) + throw Error(`connections missing for ${chainName}`); + const { channelId } = + holdingInfo.connections[issuerInfo.chainId].transferChannel; + const denom = `ibc/${denomHash({ denom: baseDenom, channelId })}`; + return [denom, { baseName, chainName, baseDenom, brandKey }]; +}; + +export const [uusdcOnAgoric, agUSDCDetail] = assetOn( + 'uusdc', + 'noble', + 'agoric', + fetchedChainInfo, + 'USDC', +); + +export const commonSetup = async (t: ExecutionContext) => { + t.log('bootstrap vat dependencies'); + // The common setup cannot support a durable zone because many of the fakes are not durable. + // They were made before we had durable kinds (and thus don't take a zone or baggage). + // To test durability in unit tests, test a particular entity with `relaxDurabilityRules: false`. + // To test durability integrating multiple vats, use a RunUtils/bootstrap test. + const rootZone = makeHeapZone(); + + const { nameHub: agoricNames, nameAdmin: agoricNamesAdmin } = + makeNameHubKit(); + + const usdc = withAmountUtils(makeIssuerKit('USDC')); + const bankBridgeMessages = [] as any[]; + const { bankManager, pourPayment } = await makeFakeBankManagerKit({ + onToBridge: obj => bankBridgeMessages.push(obj), + }); + await E(bankManager).addAsset( + uusdcOnAgoric, + 'USDC', + 'USD Circle Stablecoin', + usdc.issuerKit, + ); + // These mints no longer stay in sync with bankManager. + // Use pourPayment() for IST. + const { mint: _i, ...usdcSansMint } = usdc; + // XXX real bankManager does this. fake should too? + // TODO https://github.com/Agoric/agoric-sdk/issues/9966 + await makeWellKnownSpaces(agoricNamesAdmin, t.log, ['vbankAsset']); + await E(E(agoricNamesAdmin).lookupAdmin('vbankAsset')).update( + uusdcOnAgoric, + /** @type {AssetInfo} */ harden({ + brand: usdc.brand, + issuer: usdc.issuer, + issuerName: 'USDC', + denom: 'uusdc', + proposedName: 'USDC', + displayInfo: { IOU: true }, + }), + ); + + const vowTools = prepareSwingsetVowTools(rootZone.subZone('vows')); + + const transferBridge = makeFakeTransferBridge(rootZone); + const { makeBridgeTargetKit } = prepareBridgeTargetModule( + rootZone.subZone('bridge'), + ); + const { makeTransferMiddlewareKit } = prepareTransferTools( + rootZone.subZone('transfer'), + vowTools, + ); + + const { finisher, interceptorFactory, transferMiddleware } = + makeTransferMiddlewareKit(); + const bridgeTargetKit = makeBridgeTargetKit( + transferBridge, + VTRANSFER_IBC_EVENT, + interceptorFactory, + ); + finisher.useRegistry(bridgeTargetKit.targetRegistry); + await E(transferBridge).initHandler(bridgeTargetKit.bridgeHandler); + + const localBridgeMessages = [] as any[]; + const localchainBridge = makeFakeLocalchainBridge( + rootZone, + obj => localBridgeMessages.push(obj), + makeTestAddress, + ); + const localchain = prepareLocalChainTools( + rootZone.subZone('localchain'), + vowTools, + ).makeLocalChain({ + bankManager, + system: localchainBridge, + transfer: transferMiddleware, + }); + const timer = buildZoeManualTimer(t.log); + const marshaller = makeFakeBoard().getPublishingMarshaller(); + const storage = makeFakeStorageKit( + 'fun', // Fast USDC Node + ); + /** + * Read pure data (CapData that has no slots) from the storage path + * @param path + */ + storage.getDeserialized = (path: string): unknown => + storage.getValues(path).map(defaultSerializer.parse); + + const { portAllocator, setupIBCProtocol, ibcBridge } = setupFakeNetwork( + rootZone.subZone('network'), + { vowTools }, + ); + await setupIBCProtocol(); + + const makeCosmosInterchainService = prepareCosmosInterchainService( + rootZone.subZone('orchestration'), + vowTools, + ); + const cosmosInterchainService = makeCosmosInterchainService({ + portAllocator, + }); + + await registerKnownChains(agoricNamesAdmin, () => {}); + + let ibcSequenceNonce = 0n; + /** simulate incoming message as if the transfer completed over IBC */ + const transmitTransferAck = async () => { + // assume this is called after each outgoing IBC transfer + ibcSequenceNonce += 1n; + // let the promise for the transfer start + await eventLoopIteration(); + const lastMsgTransfer = localBridgeMessages.at(-1).messages[0]; + await E(transferBridge).fromBridge( + buildVTransferEvent({ + receiver: lastMsgTransfer.receiver, + sender: lastMsgTransfer.sender, + target: lastMsgTransfer.sender, + sourceChannel: lastMsgTransfer.sourceChannel, + sequence: ibcSequenceNonce, + denom: lastMsgTransfer.token.denom, + amount: BigInt(lastMsgTransfer.token.amount), + }), + ); + // let the bridge handler finish + await eventLoopIteration(); + }; + + /** A chainHub for Exo tests, distinct from the one a contract makes within `withOrchestration` */ + const chainHub = makeChainHub( + rootZone.subZone('chainHub'), + agoricNames, + vowTools, + { + chainInfoValueShape: CosmosChainInfoShapeV1, + }, + ); + + const chainInfo = harden(() => { + const { agoric, osmosis, noble } = withChainCapabilities(fetchedChainInfo); + const { ethereum, solana } = objectMap(cctpChainInfo, v => ({ + ...v, + // for backwards compatibility with `CosmosChainInfoShapeV1` which expects a `chainId` + chainId: `${v.namespace}:${v.reference}`, + })); + return { + agoric, + osmosis, + noble, + ethereum, + solana, + } as Record; + })(); + + const assetInfo: [Denom, DenomDetail & { brandKey?: string }][] = harden([ + assetOn('uusdc', 'noble'), + [uusdcOnAgoric, agUSDCDetail], + assetOn('uusdc', 'noble', 'osmosis', fetchedChainInfo), + ]); + + return { + bootstrap: { + agoricNames, + agoricNamesAdmin, + bankManager, + timer, + localchain, + cosmosInterchainService, + storage, + }, + brands: { + usdc: usdcSansMint, + }, + mocks: { + ibcBridge, + transferBridge, + }, + commonPrivateArgs: { + agoricNames, + localchain, + orchestrationService: cosmosInterchainService, + storageNode: storage.rootNode, + poolMetricsNode: storage.rootNode.makeChildNode('poolMetrics'), + marshaller, + timerService: timer, + feeConfig: makeTestFeeConfig(usdc), + chainInfo, + assetInfo, + }, + facadeServices: { + agoricNames, + /** A chainHub for Exo tests, distinct from the one a contract makes within `withOrchestration` */ + chainHub, + localchain, + orchestrationService: cosmosInterchainService, + timerService: timer, + }, + utils: { + contractZone: rootZone.subZone('contract'), + pourPayment, + inspectLocalBridge: () => harden([...localBridgeMessages]), + inspectDibcBridge: () => E(ibcBridge).inspectDibcBridge(), + inspectBankBridge: () => harden([...bankBridgeMessages]), + rootZone, + transmitTransferAck, + vowTools, + }, + }; +}; + +export const makeDefaultContext = (contract: Installation) => {}; + +/** + * Reincarnate without relaxDurabilityRules and provide a durable zone in the incarnation. + * @param key + */ +export const provideDurableZone = (key: string): Zone => { + const { fakeVomKit } = reincarnate({ relaxDurabilityRules: false }); + const root = fakeVomKit.cm.provideBaggage(); + const zone = makeDurableZone(root); + return zone.subZone(key); +}; From 3dbd7bd987b2d76e0afa6c72d8a7704bc85a0bdf Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:54:08 -0500 Subject: [PATCH 34/48] chore: copy tsconfig from fast-usdc-contract --- packages/orch-skel/tsconfig.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 packages/orch-skel/tsconfig.json diff --git a/packages/orch-skel/tsconfig.json b/packages/orch-skel/tsconfig.json new file mode 100644 index 00000000000..56bae8d03b0 --- /dev/null +++ b/packages/orch-skel/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": {}, + "include": [ + "src", + "test", + "testing" + ], +} From be9e42cd485c8abc648b0533486bf7d0647078b8 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:54:42 -0500 Subject: [PATCH 35/48] chore: copy fast-usdc-contract/package.json --- packages/orch-skel/package.json | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/orch-skel/package.json diff --git a/packages/orch-skel/package.json b/packages/orch-skel/package.json new file mode 100644 index 00000000000..5d530ec4d40 --- /dev/null +++ b/packages/orch-skel/package.json @@ -0,0 +1,78 @@ +{ + "name": "@aglocal/fast-usdc-contract", + "private": true, + "version": "0.1.0", + "description": "Smart contract component of the Fast USDC product", + "type": "module", + "files": [ + "src", + "tools" + ], + "scripts": { + "build": "exit 0", + "test": "ava", + "test:c8": "c8 --all $C8_OPTIONS ava", + "test:xs": "exit 0", + "lint-fix": "yarn lint:eslint --fix", + "lint": "run-s --continue-on-error lint:*", + "lint:types": "tsc", + "lint:eslint": "eslint ." + }, + "devDependencies": { + "@agoric/swingset-liveslots": "^0.10.2", + "@agoric/vats": "^0.15.1", + "@agoric/zone": "^0.2.2", + "@fast-check/ava": "^2.0.1", + "ava": "^5.3.0", + "c8": "^10.1.3", + "execa": "^9.5.2", + "ts-blank-space": "^0.6.1" + }, + "dependencies": { + "@agoric/client-utils": "^0.1.0", + "@agoric/cosmic-proto": "^0.4.0", + "@agoric/ertp": "^0.16.2", + "@agoric/fast-usdc": "^0.1.0", + "@agoric/internal": "^0.3.2", + "@agoric/notifier": "^0.6.2", + "@agoric/orchestration": "^0.1.0", + "@agoric/store": "^0.9.2", + "@agoric/vat-data": "^0.5.2", + "@agoric/vow": "^0.1.0", + "@agoric/zoe": "^0.26.2", + "@cosmjs/proto-signing": "^0.33.0", + "@cosmjs/stargate": "^0.33.0", + "@endo/base64": "^1.0.9", + "@endo/common": "^1.2.10", + "@endo/errors": "^1.2.10", + "@endo/eventual-send": "^1.3.1", + "@endo/far": "^1.1.11", + "@endo/init": "^1.1.9", + "@endo/marshal": "^1.6.4", + "@endo/nat": "^5.1.0", + "@endo/pass-style": "^1.5.0", + "@endo/patterns": "^1.5.0", + "@endo/promise-kit": "^1.1.10", + "@nick134-bit/noblejs": "0.0.2", + "bech32": "^2.0.0", + "commander": "^12.1.0", + "ethers": "^6.13.4" + }, + "ava": { + "extensions": { + "js": true, + "ts": "module" + }, + "files": [ + "test/**/*.test.*" + ], + "nodeArguments": [ + "--import=ts-blank-space/register", + "--no-warnings" + ], + "require": [ + "@endo/init/debug.js" + ], + "timeout": "20m" + } +} From 8788939ef063b9ec23b6b7be3ed17f4b3a8123c1 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:55:08 -0500 Subject: [PATCH 36/48] chore: distinct package name, description --- packages/orch-skel/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orch-skel/package.json b/packages/orch-skel/package.json index 5d530ec4d40..d120c61d3be 100644 --- a/packages/orch-skel/package.json +++ b/packages/orch-skel/package.json @@ -1,8 +1,8 @@ { - "name": "@aglocal/fast-usdc-contract", + "name": "@aglocal/orch-skel-contract", "private": true, "version": "0.1.0", - "description": "Smart contract component of the Fast USDC product", + "description": "Smart contract component of orchestration skeleton", "type": "module", "files": [ "src", From e55f9829138eb2b4abcf05e2ec33ad1349ed869d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:55:56 -0500 Subject: [PATCH 37/48] chore: update imports after moving out of orchestration pkg --- packages/orch-skel/src/my.contract.ts | 10 ++++++---- packages/orch-skel/test/my-orch-contract.test.ts | 13 +++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/orch-skel/src/my.contract.ts b/packages/orch-skel/src/my.contract.ts index a191d0bc920..673095e1b88 100644 --- a/packages/orch-skel/src/my.contract.ts +++ b/packages/orch-skel/src/my.contract.ts @@ -1,8 +1,10 @@ -import { M } from '@endo/patterns'; -import { OrchestrationPowersShape } from '../typeGuards.js'; -import { withOrchestration } from '../utils/start-helper.js'; -import * as flows from './my.flows.js'; +import { + OrchestrationPowersShape, + withOrchestration, +} from '@agoric/orchestration'; import { E } from '@endo/far'; +import { M } from '@endo/patterns'; +import * as flows from './my.flows.ts'; /** * @import {VTransferIBCEvent} from '@agoric/vats' diff --git a/packages/orch-skel/test/my-orch-contract.test.ts b/packages/orch-skel/test/my-orch-contract.test.ts index a3deccf888e..55e338b6622 100644 --- a/packages/orch-skel/test/my-orch-contract.test.ts +++ b/packages/orch-skel/test/my-orch-contract.test.ts @@ -9,15 +9,15 @@ import { E, passStyleOf } from '@endo/far'; import { Nat } from '@endo/nat'; import { M, mustMatch } from '@endo/patterns'; import { createRequire } from 'module'; -import { ChainAddressShape } from '../../src/typeGuards.js'; -import { buildVTransferEvent } from '../../tools/ibc-mocks.js'; -import { commonSetup } from '../supports.js'; +import { ChainAddressShape } from '@agoric/orchestration'; +import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; +import { commonSetup } from './supports.js'; const nodeRequire = createRequire(import.meta.url); const contractName = 'myOrchContract'; -const contractFile = nodeRequire.resolve('../../src/examples/my.contract.js'); -type StartFn = typeof import('../../src/examples/my.contract.js').start; +const contractFile = nodeRequire.resolve('../src/my.contract.ts'); +type StartFn = typeof import('../src/my.contract.ts').start; test('start my orch contract', async t => { const common = await commonSetup(t); @@ -52,10 +52,11 @@ test('start my orch contract', async t => { const { transferBridge } = common.mocks; const deposit = async (coins: CoinSDKType) => { + const target = 'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht'; // TODO: where does this come from? await VE(transferBridge).fromBridge( buildVTransferEvent({ receiver: 'rx TODO', - target: 'agoric1fakeLCAAddress', + target, sourceChannel: 'channel-1', // TODO: hubToAg.transferChannel.counterPartyChannelId, denom: coins.denom, amount: Nat(BigInt(coins.amount)), From 4445598e61ea4442592334aff8a88fb10c0cd600 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:56:39 -0500 Subject: [PATCH 38/48] fix: circular CosmosAccount type --- packages/orch-skel/test/my-orch-seq-sim.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/orch-skel/test/my-orch-seq-sim.test.ts b/packages/orch-skel/test/my-orch-seq-sim.test.ts index 63f69079419..678e67e4cc2 100644 --- a/packages/orch-skel/test/my-orch-seq-sim.test.ts +++ b/packages/orch-skel/test/my-orch-seq-sim.test.ts @@ -34,7 +34,12 @@ const makeCosmosAccount = (addr: string) => { const self = { toString: () => `<${addr}>`, getAddress: () => addr, - async send(t: Ex, amt: Coins, dest: CosmosAccount, fwd?: PFM) { + async send( + t: Ex, + amt: Coins, + dest: ReturnType, + fwd?: PFM, + ) { t.log( addr, 'sending', From bb45c598c3da752ce37390e4056759eec770f386 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 16:56:53 -0500 Subject: [PATCH 39/48] style: test file comment --- packages/orch-skel/test/my-orch-seq-sim.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orch-skel/test/my-orch-seq-sim.test.ts b/packages/orch-skel/test/my-orch-seq-sim.test.ts index 678e67e4cc2..a058764ca24 100644 --- a/packages/orch-skel/test/my-orch-seq-sim.test.ts +++ b/packages/orch-skel/test/my-orch-seq-sim.test.ts @@ -1,5 +1,5 @@ -/** @file - * Orchestration Contract template: Test simulation from sequence diagram. +/** + * @file Orchestration Contract template: Test simulation from sequence diagram. * * For each (kind of) actor / participant in the diagram (my-orch-sequence.mmd), * we have a function to make one. From a525ef035271808ed9ceead0ceb5978fb2939056 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:03:10 -0500 Subject: [PATCH 40/48] chore(zoe): TestBundle type fix --- packages/zoe/tools/setup-zoe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zoe/tools/setup-zoe.js b/packages/zoe/tools/setup-zoe.js index 244b479a468..8cd6a4e0a3e 100644 --- a/packages/zoe/tools/setup-zoe.js +++ b/packages/zoe/tools/setup-zoe.js @@ -78,7 +78,7 @@ export const setUpZoeForTest = async ({ ); // Copy all the properties so this object can be hardened. const exports = { ...pathOrExports }; - return bundleTestExports(exports); + return /** @type TestBundle */ (bundleTestExports(exports)); } }; From aa072de54d8bdf33d05f93fadd8abe5d3a1d6b95 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:03:55 -0500 Subject: [PATCH 41/48] style: no nested ternary --- packages/orch-skel/test/my-orch-seq-sim.test.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/orch-skel/test/my-orch-seq-sim.test.ts b/packages/orch-skel/test/my-orch-seq-sim.test.ts index a058764ca24..898c4e42883 100644 --- a/packages/orch-skel/test/my-orch-seq-sim.test.ts +++ b/packages/orch-skel/test/my-orch-seq-sim.test.ts @@ -68,12 +68,11 @@ const makeCosmosAccount = (addr: string) => { }; type CosmosAccount = ReturnType; -const accountMaker = (to: string) => - to.startsWith('stride1') - ? makeStrideAccount - : to.startsWith('agoric1') - ? makeLocalOrchAccount - : makeCosmosAccount; +const accountMaker = (to: string) => { + if (to.startsWith('stride1')) return makeStrideAccount; + if (to.startsWith('agoric1')) return makeLocalOrchAccount; + return makeCosmosAccount; +}; const makeLocalOrchAccount = (addr: string, strideAddr: string) => { const base = makeCosmosAccount(addr); From fb1f245ec24c767a0794ab9864fbc38150edcad2 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:04:21 -0500 Subject: [PATCH 42/48] chore: use typescript syntax for types --- packages/orch-skel/src/my.contract.ts | 28 +++++++------------ packages/orch-skel/src/my.flows.ts | 39 +++++++++++++-------------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/packages/orch-skel/src/my.contract.ts b/packages/orch-skel/src/my.contract.ts index 673095e1b88..f315a72ec16 100644 --- a/packages/orch-skel/src/my.contract.ts +++ b/packages/orch-skel/src/my.contract.ts @@ -1,21 +1,16 @@ import { OrchestrationPowersShape, withOrchestration, + type OrchestrationTools, } from '@agoric/orchestration'; +import { type VTransferIBCEvent } from '@agoric/vats'; +import type { Zone } from '@agoric/zone'; import { E } from '@endo/far'; import { M } from '@endo/patterns'; import * as flows from './my.flows.ts'; -/** - * @import {VTransferIBCEvent} from '@agoric/vats' - */ const interfaceTODO = undefined; -/** - * @import {Zone} from '@agoric/zone'; - * @import {OrchestrationTools} from '../types.js'; - */ - export const meta = M.splitRecord({ privateArgsShape: { // @ts-expect-error TypedPattern not recognized as record @@ -25,14 +20,12 @@ export const meta = M.splitRecord({ }); harden(meta); -/** - * @param {any} _zcf - * @param {any} _privateArgs - * @param {Zone} zone - * @param {OrchestrationTools} tools - * @returns - */ -export const contract = async (_zcf, _privateArgs, zone, tools) => { +export const contract = async ( + _zcf, + _privateArgs, + zone: Zone, + tools: OrchestrationTools, +) => { const { orchestrateAll } = tools; const { makeHookAccount, makePosition } = orchestrateAll(flows, {}); @@ -41,8 +34,7 @@ export const contract = async (_zcf, _privateArgs, zone, tools) => { const tap = zone.makeOnce('tapPosition', _key => { console.log('making tap'); return zone.exo('tap', interfaceTODO, { - /** @param {VTransferIBCEvent} event */ - async receiveUpcall(event) { + async receiveUpcall(event: VTransferIBCEvent) { console.log(event); return when(makePosition()); }, diff --git a/packages/orch-skel/src/my.flows.ts b/packages/orch-skel/src/my.flows.ts index 0acae23a44e..3b44e7562c2 100644 --- a/packages/orch-skel/src/my.flows.ts +++ b/packages/orch-skel/src/my.flows.ts @@ -1,30 +1,29 @@ -/** - * @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration' - * @import {TargetApp} from '@agoric/vats/src/bridge-target' - * @import {Passable} from '@endo/pass-style' - */ +import type { + OrchestrationAccount, + OrchestrationFlow, + Orchestrator, +} from '@agoric/orchestration'; +import type { TargetApp } from '@agoric/vats/src/bridge-target.js'; +import type { Passable } from '@endo/pass-style'; -/** - * @satisfies {OrchestrationFlow} - * @param {Orchestrator} orch - * @param {unknown} _ctx - * @param {TargetApp & Passable} tap - */ -export const makeHookAccount = async (orch, _ctx, tap) => { +export const makeHookAccount = (async ( + orch: Orchestrator, + _ctx: unknown, + tap: TargetApp & Passable, +) => { const agoricChain = await orch.getChain('agoric'); - const hookAccount = await agoricChain.makeAccount(); + const hookAccount = + (await agoricChain.makeAccount()) as OrchestrationAccount<{ + chainId: 'agoric-any'; + }>; const registration = hookAccount.monitorTransfers(tap); console.warn('TODO: keep registration', registration); return hookAccount; -}; +}) satisfies OrchestrationFlow; harden(makeHookAccount); -/** - * @satisfies {OrchestrationFlow} - * @param {Orchestrator} orch - */ -export const makePosition = async orch => { +export const makePosition = (async (orch: Orchestrator) => { throw Error('TODO!'); -}; +}) satisfies OrchestrationFlow; From 84f86c02fd2e576b4fbf06062b2bf9f0056a2be1 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:04:34 -0500 Subject: [PATCH 43/48] fix: bech32 addresse needed a 1 --- packages/orch-skel/test/my-orch-contract.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orch-skel/test/my-orch-contract.test.ts b/packages/orch-skel/test/my-orch-contract.test.ts index 55e338b6622..15cdbf83eea 100644 --- a/packages/orch-skel/test/my-orch-contract.test.ts +++ b/packages/orch-skel/test/my-orch-contract.test.ts @@ -55,7 +55,7 @@ test('start my orch contract', async t => { const target = 'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht'; // TODO: where does this come from? await VE(transferBridge).fromBridge( buildVTransferEvent({ - receiver: 'rx TODO', + receiver: 'rx1...TODO', target, sourceChannel: 'channel-1', // TODO: hubToAg.transferChannel.counterPartyChannelId, denom: coins.denom, From 8a774e2ad87f3adb57e6f58dceb379c27a2bc306 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:15:34 -0500 Subject: [PATCH 44/48] build: prune dependencies --- packages/orch-skel/package.json | 37 ++++++++++----------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/packages/orch-skel/package.json b/packages/orch-skel/package.json index d120c61d3be..3e16529bdc5 100644 --- a/packages/orch-skel/package.json +++ b/packages/orch-skel/package.json @@ -19,44 +19,29 @@ "lint:eslint": "eslint ." }, "devDependencies": { + "@agoric/cosmic-proto": "^0.4.0", + "@agoric/ertp": "^0.16.2", + "@agoric/fast-usdc": "^0.1.0", + "@agoric/internal": "^0.3.2", "@agoric/swingset-liveslots": "^0.10.2", - "@agoric/vats": "^0.15.1", + "@agoric/vow": "^0.1.0", + "@agoric/zoe": "^0.26.2", "@agoric/zone": "^0.2.2", + "@endo/init": "^1.1.9", + "@endo/nat": "^5.1.0", + "@endo/promise-kit": "^1.1.10", "@fast-check/ava": "^2.0.1", "ava": "^5.3.0", "c8": "^10.1.3", - "execa": "^9.5.2", "ts-blank-space": "^0.6.1" }, "dependencies": { - "@agoric/client-utils": "^0.1.0", - "@agoric/cosmic-proto": "^0.4.0", - "@agoric/ertp": "^0.16.2", - "@agoric/fast-usdc": "^0.1.0", - "@agoric/internal": "^0.3.2", - "@agoric/notifier": "^0.6.2", "@agoric/orchestration": "^0.1.0", - "@agoric/store": "^0.9.2", - "@agoric/vat-data": "^0.5.2", - "@agoric/vow": "^0.1.0", - "@agoric/zoe": "^0.26.2", - "@cosmjs/proto-signing": "^0.33.0", - "@cosmjs/stargate": "^0.33.0", - "@endo/base64": "^1.0.9", - "@endo/common": "^1.2.10", + "@agoric/vats": "^0.15.1", "@endo/errors": "^1.2.10", - "@endo/eventual-send": "^1.3.1", "@endo/far": "^1.1.11", - "@endo/init": "^1.1.9", - "@endo/marshal": "^1.6.4", - "@endo/nat": "^5.1.0", "@endo/pass-style": "^1.5.0", - "@endo/patterns": "^1.5.0", - "@endo/promise-kit": "^1.1.10", - "@nick134-bit/noblejs": "0.0.2", - "bech32": "^2.0.0", - "commander": "^12.1.0", - "ethers": "^6.13.4" + "@endo/patterns": "^1.5.0" }, "ava": { "extensions": { From 890fe528f980b6fc3aa09e32b8a0a854276e861e Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:31:58 -0500 Subject: [PATCH 45/48] chore: catch flow rejections (sort of) --- packages/orch-skel/src/my.contract.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/orch-skel/src/my.contract.ts b/packages/orch-skel/src/my.contract.ts index f315a72ec16..d7c2a784b4c 100644 --- a/packages/orch-skel/src/my.contract.ts +++ b/packages/orch-skel/src/my.contract.ts @@ -35,8 +35,11 @@ export const contract = async ( console.log('making tap'); return zone.exo('tap', interfaceTODO, { async receiveUpcall(event: VTransferIBCEvent) { - console.log(event); - return when(makePosition()); + console.log('receiveUpcall', event); + // TODO: use watch() rather than when for resumability + await when(makePosition()).catch(error => { + console.log('receiveUpcall: flow failed:', error); + }); }, }); }); From 8f3b65a94ac5e38e3a9be390b2d5ad6585e0313a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 23 Apr 2025 17:41:02 -0500 Subject: [PATCH 46/48] docs: skeletal orchestration contract package --- packages/orch-skel/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/orch-skel/README.md diff --git a/packages/orch-skel/README.md b/packages/orch-skel/README.md new file mode 100644 index 00000000000..877151338ae --- /dev/null +++ b/packages/orch-skel/README.md @@ -0,0 +1,28 @@ +# orch-skel - skeletal orchestration contract package + +To develop an orchestration contract: + 0. copy this package to a new directory; change the name (and description) in `package.json` + 1. make a rough sequence diagram - `test/my-orch-sequence.mmd` + 2. refine the sequence diagram to `@agoric/orchestration` objects and messages + 3. prototype each of the objects in the sequence diagram and make a test to exercise them - `test/my-orch-seq-sim.test.ts` + 4. refine the prototype into a contract (`src/my.contract.ts`) with flows (`my.flows.ts`) and make a test for it (`test/my-orch-contract.test.ts`) + +## Install Dependencies + +``` +yarn install +``` + +## Run Static Checks + +```console +yarn lint +``` + +## Run Tests + +```console +yarn test +``` + +Don't be surprised by `Error#2: TODO!`: the contract and flows are incomplete. From 674bbf0169e36bdb3a99fc3b8a3d84db11529221 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 25 Apr 2025 12:23:20 -0500 Subject: [PATCH 47/48] docs(orch-skel): punt copy to new pkg; a branch suffices --- packages/orch-skel/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/orch-skel/README.md b/packages/orch-skel/README.md index 877151338ae..2f82a37a8cc 100644 --- a/packages/orch-skel/README.md +++ b/packages/orch-skel/README.md @@ -1,7 +1,6 @@ # orch-skel - skeletal orchestration contract package To develop an orchestration contract: - 0. copy this package to a new directory; change the name (and description) in `package.json` 1. make a rough sequence diagram - `test/my-orch-sequence.mmd` 2. refine the sequence diagram to `@agoric/orchestration` objects and messages 3. prototype each of the objects in the sequence diagram and make a test to exercise them - `test/my-orch-seq-sim.test.ts` From b9e6d53d70465bfd8a291acfa5ebf105d926e882 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 25 Apr 2025 15:34:19 -0500 Subject: [PATCH 48/48] docs(orchestration): getBalances @throws when prohibited by chain --- packages/orchestration/src/orchestration-api.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 225840d0e95..c05fffd533a 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -234,10 +234,18 @@ export interface OrchestrationAccountCommon { */ getAddress: () => CosmosChainAddress; - /** @returns an array of amounts for every balance in the account. */ + /** + * @returns an array of amounts for every balance in the account. + * + * @throws when prohibited (see `icqEnabled` in {@link CosmosChainInfo}) + */ getBalances: () => Promise; - /** @returns the balance of a specific denom for the account. */ + /** + * @returns the balance of a specific denom for the account. + * + * @throws when prohibited (see `icqEnabled` in {@link CosmosChainInfo}) + */ getBalance: (denom: DenomArg) => Promise; /**