Skip to content

docs: orchestration contract package skeleton #11323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 48 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
959f352
WIP: toward orch template: sequence diagram
dckc Jan 27, 2025
fc00e20
chore: refine sequence diagram to object messages
dckc Jan 27, 2025
e70125d
chore: prototype 1st message of diagram
dckc Jan 27, 2025
e7bfe84
feat: Expand test simulation with receiveUpcall from cosmos account t…
dckc Jan 27, 2025
7f3b282
chore: refine prototype
dckc Jan 27, 2025
a89e338
feat: Add send() method to orchestration contract in test simulation
dckc Jan 27, 2025
f5f3298
feat: Add toString method to CosmosAccount for better address represe…
dckc Jan 27, 2025
2f5acc2
fix: Update orchestration test to send funds to Cosmos account first
dckc Jan 27, 2025
90a549f
fix: Await deposit method in local orchestration account test
dckc Jan 27, 2025
167b603
feat: Add optional packet forwarding middleware to send and deposit m…
dckc Jan 27, 2025
4d6c1f8
refactor: Use forwarding address in receiveUpcall for cosmos account
dckc Jan 27, 2025
bab4e4a
feat: Add Stride address to orchestration contract for test simulation
dckc Jan 27, 2025
75637f5
fix: Pass strideAddr to makeLocalOrchAccount to resolve reference error
dckc Jan 27, 2025
b3eee19
refactor: Improve logging and upcall handling in orchestration test a…
dckc Jan 27, 2025
9a2350c
feat: Add ICA account forwarding in Stride chain deposit method
dckc Jan 27, 2025
5d12e4c
fix: Resolve self reference issue in makeCosmosAccount method
dckc Jan 27, 2025
98f4771
feat: Enhance ICA account with stATOM conversion and balance tracking
dckc Jan 27, 2025
c586fdb
feat: Add getBalances method to CosmosAccount and use it in openPosition
dckc Jan 27, 2025
9f8ab32
refactor: Rename `makeICAAccount` to `makeStrideICAAccount`
dckc Jan 27, 2025
2c0606f
feat: Return and validate balance in openPosition method
dckc Jan 27, 2025
72958c9
test: finish up simulation
dckc Jan 27, 2025
204433c
chore(orchestration): include labels in OrchestrationPowersShape
dckc Jan 28, 2025
53b03a7
test: start template orch. contract (w/test)
dckc Jan 28, 2025
d0b7e25
feat: make hook account on Agoric; add public .getAddress()
dckc Jan 28, 2025
954902f
fixup: interfaceTODO
dckc Jan 28, 2025
5f5c743
WIP: makePosition flow stub
dckc Jan 28, 2025
4b84cac
WIP(contract): make tap
dckc Jan 28, 2025
10ebbce
WIP: makePosition flow stub
dckc Jan 28, 2025
1cb70e7
WIP(test): try sending over the bridge to the tap
dckc Jan 28, 2025
0d21ee5
fixup: handle how in receiveUpcall
dckc Feb 10, 2025
6fc135b
chore(orch-skel): move stuff from orchestration/{src,test}/examples
dckc Apr 23, 2025
a748a22
chore: .js -> .ts a la Fast USDC
dckc Apr 23, 2025
a26f486
chore: copy test supports, mocks from fast-usdc-contract
dckc Apr 23, 2025
3dbd7bd
chore: copy tsconfig from fast-usdc-contract
dckc Apr 23, 2025
be9e42c
chore: copy fast-usdc-contract/package.json
dckc Apr 23, 2025
8788939
chore: distinct package name, description
dckc Apr 23, 2025
e55f982
chore: update imports after moving out of orchestration pkg
dckc Apr 23, 2025
4445598
fix: circular CosmosAccount type
dckc Apr 23, 2025
bb45c59
style: test file comment
dckc Apr 23, 2025
a525ef0
chore(zoe): TestBundle type fix
dckc Apr 23, 2025
aa072de
style: no nested ternary
dckc Apr 23, 2025
fb1f245
chore: use typescript syntax for types
dckc Apr 23, 2025
84f86c0
fix: bech32 addresse needed a 1
dckc Apr 23, 2025
8a774e2
build: prune dependencies
dckc Apr 23, 2025
890fe52
chore: catch flow rejections (sort of)
dckc Apr 23, 2025
8f3b65a
docs: skeletal orchestration contract package
dckc Apr 23, 2025
674bbf0
docs(orch-skel): punt copy to new pkg; a branch suffices
dckc Apr 25, 2025
b9e6d53
docs(orchestration): getBalances @throws when prohibited by chain
dckc Apr 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/orch-skel/README.md
Original file line number Diff line number Diff line change
@@ -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.
63 changes: 63 additions & 0 deletions packages/orch-skel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"name": "@aglocal/orch-skel-contract",
"private": true,
"version": "0.1.0",
"description": "Smart contract component of orchestration skeleton",
"type": "module",
"files": [
"src",
"tools"
],
"scripts": {
"build": "exit 0",
"test": "ava",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a new repo, yarn test says:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/connolly/projects/orch-skel/node_modules/@agoric/orchestration/test/network-fakes.ts' imported from /home/connolly/projects/orch-skel/test/supports.ts

"test:c8": "c8 --all $C8_OPTIONS ava",
"test:xs": "exit 0",
"lint-fix": "yarn lint:eslint --fix",
"lint": "run-s --continue-on-error lint:*",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a new repo, yarn lint says /bin/sh: 1: run-s: not found

"lint:types": "tsc",
"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/vow": "^0.1.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@turadg when I use a new repository, I get a bunch of...

? Please choose a version of "@agoric/vow" from this list: 0.2.0-upgrade-20-dev-ef71
cfd.0

I guess I could use dev packages. But then it's another independent set of dependencies to track. hm.

"@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",
"ts-blank-space": "^0.6.1"
},
"dependencies": {
"@agoric/orchestration": "^0.1.0",
"@agoric/vats": "^0.15.1",
"@endo/errors": "^1.2.10",
"@endo/far": "^1.1.11",
"@endo/pass-style": "^1.5.0",
"@endo/patterns": "^1.5.0"
},
"ava": {
"extensions": {
"js": true,
"ts": "module"
},
"files": [
"test/**/*.test.*"
],
"nodeArguments": [
"--import=ts-blank-space/register",
"--no-warnings"
],
"require": [
"@endo/init/debug.js"
],
"timeout": "20m"
}
}
60 changes: 60 additions & 0 deletions packages/orch-skel/src/my.contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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';

const interfaceTODO = undefined;

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: Zone,
tools: OrchestrationTools,
) => {
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, {
async receiveUpcall(event: VTransferIBCEvent) {
console.log('receiveUpcall', event);
// TODO: use watch() rather than when for resumability
await when(makePosition()).catch(error => {
console.log('receiveUpcall: flow failed:', error);
});
},
});
});

const hookAccountV = zone.makeOnce('hookAccount', _key =>
makeHookAccount(tap),
);

return {
publicFacet: zone.exo('MyPub', interfaceTODO, {
getHookAddress: () => E(when(hookAccountV)).getAddress(),
}),
creatorFacet: zone.exo('MyCreator', undefined, {}),
};
};

export const start = withOrchestration(contract);
harden(start);
29 changes: 29 additions & 0 deletions packages/orch-skel/src/my.flows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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';

export const makeHookAccount = (async (
orch: Orchestrator,
_ctx: unknown,
tap: TargetApp & Passable,
) => {
const agoricChain = await orch.getChain('agoric');
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);

export const makePosition = (async (orch: Orchestrator) => {
throw Error('TODO!');
}) satisfies OrchestrationFlow;
132 changes: 132 additions & 0 deletions packages/orch-skel/test/mocks.ts
Original file line number Diff line number Diff line change
@@ -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<void>();
const poolAccountTransferPK = makePromiseKit<void>();
const settleAccountTransferPK = makePromiseKit<void>();
const settleAccountSendPK = makePromiseKit<void>();
const intermediateAccountTransferPK = makePromiseKit<void>();
const intermediateAccountDepositForBurnPK = makePromiseKit<void>();

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<typeof makeTestLogger>;

export const makeTestFeeConfig = (usdc: Omit<AmountUtils, 'mint'>): FeeConfig =>
harden({
flat: usdc.make(1n),
variableRate: makeRatio(2n, usdc.brand),
contractRate: makeRatio(20n, usdc.brand),
});
70 changes: 70 additions & 0 deletions packages/orch-skel/test/my-orch-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 '@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/my.contract.ts');
type StartFn = typeof import('../src/my.contract.ts').start;

test('start my orch contract', async t => {
const common = await commonSetup(t);
const { zoe, bundleAndInstall } = await setUpZoeForTest();
t.log('contract deployment', contractName);

const installation: Installation<StartFn> =
await bundleAndInstall(contractFile);
t.is(passStyleOf(installation), 'remotable');

const myKit = await E(zoe).startInstance(
installation,
{}, // issuers
{}, // terms
common.commonPrivateArgs,
);
t.notThrows(() =>
mustMatch(
myKit,
M.splitRecord({
instance: M.remotable(),
publicFacet: M.remotable(),
creatorFacet: M.remotable(),
// ...others are not relevant here
}),
),
);

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) => {
const target = 'agoric1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp7zqht'; // TODO: where does this come from?
await VE(transferBridge).fromBridge(
buildVTransferEvent({
receiver: 'rx1...TODO',
target,
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' }));
});
Loading
Loading