Skip to content

Commit

Permalink
Merge pull request #9114 from Agoric/9042-stake-atom
Browse files Browse the repository at this point in the history
feat: create chain account (ica)
  • Loading branch information
mergify[bot] committed Apr 4, 2024
2 parents a477bee + 82f1901 commit 21fe8d9
Show file tree
Hide file tree
Showing 20 changed files with 968 additions and 3 deletions.
37 changes: 36 additions & 1 deletion packages/boot/test/bootstrapTests/test-orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { TestFn } from 'ava';
import { Fail } from '@agoric/assert';
import type { start as stakeBldStart } from '@agoric/orchestration/src/contracts/stakeBld.contract.js';
import type { Instance } from '@agoric/zoe/src/zoeService/utils.js';
import { M, matches } from '@endo/patterns';
import { makeWalletFactoryContext } from './walletFactory.ts';

type DefaultTestContext = Awaited<ReturnType<typeof makeWalletFactoryContext>>;
Expand All @@ -14,7 +15,7 @@ const test: TestFn<DefaultTestContext> = anyTest;
test.before(async t => (t.context = await makeWalletFactoryContext(t)));
test.after.always(t => t.context.shutdown?.());

test('stakeBld', async t => {
test.serial('stakeBld', async t => {
const {
agoricNamesRemotes,
buildProposal,
Expand Down Expand Up @@ -86,3 +87,37 @@ test('stakeBld', async t => {
},
});
});

test.serial('stakeAtom', async t => {
const {
buildProposal,
evalProposal,
runUtils: { EV },
} = t.context;
// TODO move into a vm-config for u15
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-network.js'),
);
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-orchestration.js'),
);
await evalProposal(
buildProposal('@agoric/builders/scripts/orchestration/init-stakeAtom.js'),
);

const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames');
const instance = await EV(agoricNames).lookup('instance', 'stakeAtom');
t.truthy(instance, 'stakeAtom instance is available');

const zoe = await EV.vat('bootstrap').consumeItem('zoe');
const publicFacet = await EV(zoe).getPublicFacet(instance);
t.truthy(publicFacet, 'stakeAtom publicFacet is available');

const account = await EV(publicFacet).createAccount();
t.log('account', account);
t.truthy(account, 'createAccount returns an account on ATOM connection');
t.truthy(
matches(account, M.remotable('ChainAccount')),
'account is a remotable',
);
});
89 changes: 89 additions & 0 deletions packages/boot/test/bootstrapTests/test-vat-orchestration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import type { ExecutionContext, TestFn } from 'ava';
import { M, matches } from '@endo/patterns';
import { makeWalletFactoryContext } from './walletFactory.ts';

const makeTestContext = async (t: ExecutionContext) =>
makeWalletFactoryContext(t);

type DefaultTestContext = Awaited<ReturnType<typeof makeTestContext>>;
const test: TestFn<DefaultTestContext> = anyTest;

test.before(async t => {
t.context = await makeTestContext(t);

async function setupDeps() {
const {
buildProposal,
evalProposal,
runUtils: { EV },
} = t.context;
/** ensure network, ibc, and orchestration are available */
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-network.js'),
);
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-orchestration.js'),
);
const vatStore = await EV.vat('bootstrap').consumeItem('vatStore');
t.true(await EV(vatStore).has('ibc'), 'ibc');
t.true(await EV(vatStore).has('network'), 'network');
t.true(await EV(vatStore).has('orchestration'), 'orchestration');
}

await setupDeps();
});

test.after.always(t => t.context.shutdown?.());

test('createAccount returns an ICA connection', async t => {
const {
runUtils: { EV },
} = t.context;

const orchestration = await EV.vat('bootstrap').consumeItem('orchestration');

const account = await EV(orchestration).createAccount(
'connection-0',
'connection-0',
);
t.truthy(account, 'createAccount returns an account');
t.truthy(
matches(account, M.remotable('ChainAccount')),
'account is a remotable',
);
const [remoteAddress, localAddress, accountAddress, port] = await Promise.all(
[
EV(account).getRemoteAddress(),
EV(account).getLocalAddress(),
EV(account).getAccountAddress(),
EV(account).getPort(),
],
);
t.regex(remoteAddress, /icahost/);
t.regex(localAddress, /icacontroller/);
t.regex(accountAddress, /osmo1/);
t.truthy(matches(port, M.remotable('Port')));
t.log('ICA Account Addresses', {
remoteAddress,
localAddress,
accountAddress,
});
});

test('ICA connection can be closed', async t => {
const {
runUtils: { EV },
} = t.context;

const orchestration = await EV.vat('bootstrap').consumeItem('orchestration');

const account = await EV(orchestration).createAccount(
'connection-0',
'connection-0',
);
t.truthy(account, 'createAccount returns an account');

const res = await EV(account).close();
t.is(res, 'Connection closed');
});
113 changes: 113 additions & 0 deletions packages/boot/tools/ibc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@

# ICS-27 Interchain Accounts

Sequence diagrams for Interchain Accounts based on [ics-027-interchain-accounts/README.md#9ffb1d2](https://github.com/cosmos/ibc/blob/9ffb1d26d3018b6efda546189ec7e43d56d23da3/spec/app/ics-027-interchain-accounts/README.md).

### IBC Connection Creation

_Prerequisite to creating a channel._

```mermaid
sequenceDiagram
participant CC as Chain A
participant R as Relayer
participant HC as Chain B
CC->>R: ConnOpenInit(ClientId, CounterpartyClientId, Version)
R->>HC: ConnOpenTry(ClientId, CounterpartyClientId, Version, CounterpartyVersion)
HC-->>R: ConnOpenTry(Version)
R->>CC: ConnOpenAck(ConnectionId, Version)
CC-->>R: ConnOpenAck
R->>HC: ConnOpenConfirm(ConnectionId)
```

Mock Testing:
- on `ConnOpenInit`, return `ConnOpenAck`

### ICA Channel Creation

```mermaid
sequenceDiagram
participant CC as Controller Chain
participant R as Relayer
participant HC as Host Chain
CC->>CC: RegisterInterchainAccount()
CC->>R: ChannelOpenInit(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
R->>HC: ChannelOpenTry(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
HC->>HC: RegisterInterchainAccount(CounterpartyPortIdentifier)
HC-->>R: ChannelOpenTry(Version)
R->>CC: ChannelOpenAck(PortIdentifier, ChannelIdentifier, CounterpartyVersion)
CC->>CC: SetInterchainAccountAddress(PortID, Address)
CC->>CC: SetActiveChannelID(PortIdentifier, ConnectionID, ChannelIdentifier)
CC-->>R: ChannelOpenAck
R->>HC: ChannelOpenConfirm(PortIdentifier, ChannelIdentifier)
HC->>HC: SetActiveChannelID(CounterpartyPortIdentifier, ConnectionID, ChannelIdentifier)
```

Mock Testing:
- on `ChannelOpenInit`, return `ChannelOpenAck`

### ICA Transaction

```mermaid
sequenceDiagram
participant CC as Controller Chain
participant R as Relayer
participant HC as Host Chain
CC->>R: SendPacket(PacketData)
R->>HC: onRecvPacket(Packet)
HC->>HC: Deserialize and validate packet data
alt Successful deserialization and validation
HC->>HC: AuthenticateTx(msgs)
HC->>HC: ExecuteTx(msgs)
HC-->>R: Acknowledgement(result)
else Error
HC-->>R: ErrorAcknowledgement(error)
end
R->>CC: onAcknowledgePacket(Packet, Acknowledgement)
CC->>CC: Handle acknowledgement
```

Mock Testing:
- on `SendPacket`, return `onAcknowledgePacket`


### ICA Channel Reactivation

```mermaid
sequenceDiagram
participant CC as Controller Chain
participant R as Relayer
participant HC as Host Chain
Note over CC,HC: Existing ICA channel expires or closes
CC->>CC: Channel closes or times out
CC->>R: ChannelOpenInit(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
Note right of CC: Reusing the same PortIdentifier and ConnectionID
R->>HC: ChannelOpenTry(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
HC->>HC: Verify PortIdentifier and ConnectionID match the previous active channel
HC->>HC: Verify the channel is in CLOSED state
HC->>HC: Verify the new channel has the same order and version as the previous channel
HC-->>R: ChannelOpenTry(Version)
R->>CC: ChannelOpenAck(PortIdentifier, ChannelIdentifier, CounterpartyVersion)
CC->>CC: Verify the CounterpartyVersion matches the previous active channel
CC->>CC: SetActiveChannelID(PortIdentifier, ConnectionID, ChannelIdentifier)
CC-->>R: ChannelOpenAck
R->>HC: ChannelOpenConfirm(PortIdentifier, ChannelIdentifier)
HC->>HC: SetActiveChannelID(CounterpartyPortIdentifier, ConnectionID, ChannelIdentifier)
```

Mock Testing:
- on `ChannelOpenInit`, return `ChannelOpenAck`
- n.b. testing should verify `SetActiveChannelID` flow on CC side


### Testing Mocks Summary

- IBC Connection Creation: on ConnOpenInit, return ConnOpenAck
- ICA Channel Creation: on ChannelOpenInit, return ChannelOpenAck
- ICA Transaction: on SendPacket, return onAcknowledgePacket
- ICA Channel Reactivation: on ChannelOpenInit, return ChannelOpenAck
- testing should verify SetActiveChannelID flow on CC side
28 changes: 28 additions & 0 deletions packages/boot/tools/ibc/mocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* mock bridgeInbound events for ICA (ICS-27) flow
* see [./ics27-1.md](./ics27-1.md) for more details
*
* sourced from [@agoric/vats/test/test-network.js](https://github.com/Agoric/agoric-sdk/blob/4601a1ab65a0c36fbfbfcc1fa59e83ee10a1c996/packages/vats/test/test-network.js)
* and end e2e testing with sim chains (v16: IBC fromBridge logs)
*/
export const icaMocks = {
startChannelOpenInit: {
// ICA Channel Creation
channelOpenAck: obj => ({
type: 'IBC_EVENT',
blockHeight: 99,
blockTime: 1711571357,
event: 'channelOpenAck',
portID: obj.packet.source_port,
channelID: 'channel-0',
counterparty: {
port_id: obj.packet.destination_port,
channel_id: 'channel-1',
},
counterpartyVersion:
'{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-0","address":"osmo1234","encoding":"proto3","txType":"sdk_multi_msg"}',
connectionHops: obj.hops,
}),
// XXX channelOpenAckFailure
},
};
22 changes: 21 additions & 1 deletion packages/boot/tools/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { ExecutionContext as AvaT } from 'ava';
import { makeRunUtils } from '@agoric/swingset-vat/tools/run-utils.js';
import type { CoreEvalSDKType } from '@agoric/cosmic-proto/swingset/swingset.js';
import type { BridgeHandler } from '@agoric/vats';
import { icaMocks } from './ibc/mocks.js';

const trace = makeTracer('BSTSupport', false);

Expand Down Expand Up @@ -289,6 +290,7 @@ export const makeSwingsetTestKit = async (

const outboundMessages = new Map();

let inbound;
/**
* Mock the bridge outbound handler. The real one is implemented in Golang so
* changes there will sometimes require changes here.
Expand Down Expand Up @@ -356,6 +358,21 @@ export const makeSwingsetTestKit = async (
}
case BridgeId.CORE:
case BridgeId.DIBC:
switch (obj.type) {
case 'IBC_METHOD':
switch (obj.method) {
case 'startChannelOpenInit':
inbound(
BridgeId.DIBC,
icaMocks.startChannelOpenInit.channelOpenAck(obj),
);
return undefined;
default:
return undefined;
}
default:
return undefined;
}
case BridgeId.PROVISION:
case BridgeId.PROVISION_SMART_WALLET:
case BridgeId.VTRANSFER:
Expand Down Expand Up @@ -390,7 +407,7 @@ export const makeSwingsetTestKit = async (
},
});
}
const { controller, timer } = await buildSwingset(
const { controller, timer, bridgeInbound } = await buildSwingset(
new Map(),
bridgeOutbound,
kernelStorage,
Expand All @@ -406,6 +423,8 @@ export const makeSwingsetTestKit = async (
debugVats,
},
);
inbound = bridgeInbound;

console.timeLog('makeBaseSwingsetTestKit', 'buildSwingset');

const runUtils = makeRunUtils(controller);
Expand Down Expand Up @@ -491,6 +510,7 @@ export const makeSwingsetTestKit = async (
advanceTimeBy,
advanceTimeTo,
buildProposal,
bridgeInbound,
controller,
evalProposal,
getCrankNumber,
Expand Down
34 changes: 34 additions & 0 deletions packages/builders/scripts/orchestration/init-stakeAtom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async (
{ publishRef, install },
options = {},
) => {
const {
hostConnectionId = 'connection-1',
controllerConnectionId = 'connection-0',
} = options;
return harden({
sourceSpec: '@agoric/orchestration/src/proposals/start-stakeAtom.js',
getManifestCall: [
'getManifestForStakeAtom',
{
installKeys: {
stakeAtom: publishRef(
install(
'@agoric/orchestration/src/contracts/stakeAtom.contract.js',
),
),
},
hostConnectionId,
controllerConnectionId,
},
],
});
};

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('start-stakeAtom', defaultProposalBuilder);
};
Loading

0 comments on commit 21fe8d9

Please sign in to comment.