Skip to content

Commit

Permalink
chore: chain account kit cleanup (#9818)
Browse files Browse the repository at this point in the history
refs: #9064

## Description
- remove getBalance, getBalances, getPurse from IcaAccountKit
- cleanup TODO surrounding `UNPARSABLE_CHAIN_ADDRESS`
- rename `ChainAccountKit` -> `IcaAccountKit`
- `findAddressField` should treat an empty string as an `UNPARSABLE_ADDRESS`
- improve testing coverage for `IcaAccount.getPort()`
- docs: update exo classDiagram

### Security Considerations
n/a

### Scaling Considerations
n/a

### Documentation Considerations
n/a

### Testing Considerations
n/a

### Upgrade Considerations
n/a
  • Loading branch information
mergify[bot] authored Jul 31, 2024
2 parents 92f2510 + 58bdd14 commit c1ec381
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 83 deletions.
2 changes: 1 addition & 1 deletion packages/orchestration/src/examples/stakeIca.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const start = async (zcf, privateArgs, baggage) => {
const publicFacet = zone.exo(
'StakeAtom',
M.interface('StakeAtomI', {
makeAccount: M.callWhen().returns(M.remotable('ChainAccount')),
makeAccount: M.callWhen().returns(M.remotable('OrchestrationAccountKit')),
makeAccountInvitationMaker: M.callWhen().returns(InvitationShape),
}),
{
Expand Down
127 changes: 97 additions & 30 deletions packages/orchestration/src/exos/README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,123 @@
# Exo structure

As of 2024-05-29…
Last verified 2024-07-31

```mermaid
classDiagram
%% Orchestration vat business logic (Zoe)
LCAKit --* LocalchainAccount
ICQConnectionKit --* Port
ICQConnectionKit --* Connection
ChainAccountKit --* Port
ChainAccountKit --* Connection
StakingAccountKit --* IcaAccount
class ChainAccountKit {
ICQConnection --* Port
ICQConnection --* Connection
IcaAccount --* Port
IcaAccount --* Connection
IcaAccount --* CosmosInterchainService
ICQConnection --* CosmosInterchainService
CosmosInterchainService --* PortAllocator
PortAllocator --* NetworkVat
LocalChainAccount --* LocalChainVat
class IcaAccount {
port: Port
connection: Connection
localAddress: LocalIbcAddress
requestedRemoteAddress: string
remoteAddress: RemoteIbcAddress
chainAddress: ChainAddress
getAddress()
getLocalAddress()
getRemoteAddress()
getPort()
executeTx()
executeEncodedTx()
close()
}
class ICQConnectionKit {
class ICQConnection {
port: Port
connection: Connection
localAddress: LocalIbcAddress
remoteAddress: RemoteIbcAddress
getLocalAddress()
getRemoteAddress()
query()
}
class StakingAccountKit {
chainAddress: ChainAddress
bondDenom: string
account: ICAAccount
timer: Timer
topicKit: TopicKit
makeTransferInvitation()
class CosmosInterchainService {
portAllocator: PortAllocator
icqConnections: MapStore<ConnectionVersionKey, ICQConnection>
sharedICQPort: Port
makeAccount()
provideICQConnection()
}
%% In other vats
class LCAKit {
account: LocalChainAccount
address: ChainAddress
topicKit: RecorderKit<LocalChainAccountNotification>
class Port {
getLocalAddress()
addListener()
connect()
removeListener()
revoke()
}
class LocalchainAccount {
executeTx()
deposit()
withdraw()
class Connection {
getLocalAddress()
getRemoteAddress()
send()
close()
}
class IcaAccount {
executeTx()
deposit()
getPurse()
close()
class PortAllocator {
allocateCustomIBCPort()
allocateICAControllerPort()
allocateICQControllerPort()
}
class LocalChainAccount {
deposit()
executeTx()
getBalance()
withdraw()
executeTx()
monitorTransfers()
}
%% In api consumer vats
LocalOrchestrationAccount --* LocalChainAccount
CosmosOrchestrationAccount --* IcaAccount
class LocalOrchestrationAccount {
account: LocalChainAccount
address: ChainAddress
topicKit: RecorderKit<OrchestrationAccountNotification>
asContinuingOffer()
delegate()
deposit()
executeTx()
getAddress()
getBalance()
getPublicTopics()
monitorTransfers()
send()
transfer()
undelegate()
withdraw()
}
class CosmosOrchestrationAccount {
account: LocalChainAccount
bondDenom: string
chainAddress: ChainAddress
icqConnection: ICQConnection | undefined
timer: Timer
topicKit: RecorderKit<OrchestrationAccountNotification>
asContinuingOffer()
delegate()
executeEncodedTx()
getAddress()
getBalance()
getPublicTopics()
redelegate()
undelegate()
withdrawReward()
}
```
22 changes: 11 additions & 11 deletions packages/orchestration/src/exos/cosmos-interchain-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { E } from '@endo/far';
import { M, mustMatch } from '@endo/patterns';
import { Shape as NetworkShape } from '@agoric/network';
import { prepareChainAccountKit } from './chain-account-kit.js';
import { prepareIcaAccountKit } from './ica-account-kit.js';
import { prepareICQConnectionKit } from './icq-connection-kit.js';
import {
DEFAULT_ICQ_VERSION,
Expand All @@ -18,7 +18,7 @@ import {
* @import {IBCConnectionID} from '@agoric/vats';
* @import {RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js';
* @import {Vow, VowTools} from '@agoric/vow';
* @import {ICQConnection, IcaAccount, ICQConnectionKit, ChainAccountKit} from '../types.js';
* @import {ICQConnection, IcaAccount, ICQConnectionKit, IcaAccountKit} from '../types.js';
* @import {ICAChannelAddressOpts} from '../utils/address.js';
*/

Expand All @@ -32,7 +32,7 @@ const { Vow$ } = NetworkShape; // TODO #9611

/** @typedef {MapStore<string, ICQConnectionKit>} ICQConnectionStore */

/** @typedef {ChainAccountKit | ICQConnectionKit} ConnectionKit */
/** @typedef {IcaAccountKit | ICQConnectionKit} ConnectionKit */

/**
* @typedef {{
Expand All @@ -56,13 +56,13 @@ const getICQConnectionKey = (controllerConnectionId, version) => {
/**
* @param {Zone} zone
* @param {VowTools} vowTools
* @param {ReturnType<typeof prepareChainAccountKit>} makeChainAccountKit
* @param {ReturnType<typeof prepareIcaAccountKit>} makeIcaAccountKit
* @param {ReturnType<typeof prepareICQConnectionKit>} makeICQConnectionKit
*/
const prepareCosmosOrchestrationServiceKit = (
zone,
{ watch, asVow },
makeChainAccountKit,
makeIcaAccountKit,
makeICQConnectionKit,
) =>
zone.exoClassKit(
Expand Down Expand Up @@ -94,7 +94,7 @@ const prepareCosmosOrchestrationServiceKit = (
public: M.interface('CosmosInterchainService', {
makeAccount: M.call(M.string(), M.string(), M.string())
.optional(M.record())
.returns(Vow$(M.remotable('ChainAccountKit'))),
.returns(Vow$(M.remotable('IcaAccountKit'))),
provideICQConnection: M.call(M.string())
.optional(M.string())
.returns(Vow$(M.remotable('ICQConnection'))),
Expand All @@ -121,15 +121,15 @@ const prepareCosmosOrchestrationServiceKit = (
* }} watchContext
*/
onFulfilled(port, { chainId, remoteConnAddr }) {
const chainAccountKit = makeChainAccountKit(
const connectionKit = makeIcaAccountKit(
chainId,
port,
remoteConnAddr,
);
return watch(
E(port).connect(remoteConnAddr, chainAccountKit.connectionHandler),
E(port).connect(remoteConnAddr, connectionKit.connectionHandler),
this.facets.channelOpenWatcher,
{ returnFacet: 'account', connectionKit: chainAccountKit },
{ returnFacet: 'account', connectionKit },
);
},
},
Expand Down Expand Up @@ -252,13 +252,13 @@ const prepareCosmosOrchestrationServiceKit = (
* @param {VowTools} vowTools
*/
export const prepareCosmosInterchainService = (zone, vowTools) => {
const makeChainAccountKit = prepareChainAccountKit(zone, vowTools);
const makeIcaAccountKit = prepareIcaAccountKit(zone, vowTools);
const makeICQConnectionKit = prepareICQConnectionKit(zone, vowTools);
const makeCosmosOrchestrationServiceKit =
prepareCosmosOrchestrationServiceKit(
zone,
vowTools,
makeChainAccountKit,
makeIcaAccountKit,
makeICQConnectionKit,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @file ChainAccount exo */
/** @file IcaAccount exo */
import { Fail } from '@endo/errors';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
Expand All @@ -22,15 +22,13 @@ import { makeTxPacket, parseTxPacket } from '../utils/packet.js';
* @import {ChainAddress} from '../types.js';
*/

const trace = makeTracer('ChainAccountKit');
const trace = makeTracer('IcaAccountKit');

/** @typedef {'UNPARSABLE_CHAIN_ADDRESS'} UnparsableChainAddress */
const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS';

export const ChainAccountI = M.interface('ChainAccount', {
export const IcaAccountI = M.interface('IcaAccount', {
getAddress: M.call().returns(ChainAddressShape),
getBalance: M.call(M.string()).returns(VowShape),
getBalances: M.call().returns(VowShape),
getLocalAddress: M.call().returns(M.string()),
getRemoteAddress: M.call().returns(M.string()),
getPort: M.call().returns(M.remotable('Port')),
Expand All @@ -39,7 +37,6 @@ export const ChainAccountI = M.interface('ChainAccount', {
.optional(M.record())
.returns(VowShape),
close: M.call().returns(VowShape),
getPurse: M.call().returns(VowShape),
});

/**
Expand All @@ -58,11 +55,11 @@ export const ChainAccountI = M.interface('ChainAccount', {
* @param {Zone} zone
* @param {VowTools} vowTools
*/
export const prepareChainAccountKit = (zone, { watch, asVow }) =>
export const prepareIcaAccountKit = (zone, { watch, asVow }) =>
zone.exoClassKit(
'ChainAccountKit',
'IcaAccountKit',
{
account: ChainAccountI,
account: IcaAccountI,
connectionHandler: OutboundConnectionHandlerI,
parseTxPacketWatcher: M.interface('ParseTxPacketWatcher', {
onFulfilled: M.call(M.string())
Expand Down Expand Up @@ -100,16 +97,6 @@ export const prepareChainAccountKit = (zone, { watch, asVow }) =>
'ICA channel creation acknowledgement not yet received.',
);
},
getBalance(_denom) {
// TODO https://github.com/Agoric/agoric-sdk/issues/9610
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9326
return asVow(() => Fail`not yet implemented`);
},
getBalances() {
// TODO https://github.com/Agoric/agoric-sdk/issues/9610
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9326
return asVow(() => Fail`not yet implemented`);
},
getLocalAddress() {
return NonNullish(
this.state.localAddress,
Expand Down Expand Up @@ -164,15 +151,6 @@ export const prepareChainAccountKit = (zone, { watch, asVow }) =>
return E(connection).close();
});
},
/**
* get Purse for a brand to .withdraw() a Payment from the account
*
* @param {Brand} brand
*/
getPurse(brand) {
console.log('getPurse got', brand);
return asVow(() => Fail`not yet implemented`);
},
},
connectionHandler: {
/**
Expand All @@ -185,10 +163,12 @@ export const prepareChainAccountKit = (zone, { watch, asVow }) =>
this.state.connection = connection;
this.state.remoteAddress = remoteAddr;
this.state.localAddress = localAddr;
const address = findAddressField(remoteAddr);
if (!address) {
console.error('⚠️ failed to parse chain address', remoteAddr);
}
this.state.chainAddress = harden({
// FIXME need a fallback value like icacontroller-1-connection-1 if this fails
// https://github.com/Agoric/agoric-sdk/issues/9066
value: findAddressField(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS,
value: address || UNPARSABLE_CHAIN_ADDRESS,
chainId: this.state.chainId,
encoding: 'bech32',
});
Expand All @@ -206,4 +186,4 @@ export const prepareChainAccountKit = (zone, { watch, asVow }) =>
},
);

/** @typedef {ReturnType<ReturnType<typeof prepareChainAccountKit>>} ChainAccountKit */
/** @typedef {ReturnType<ReturnType<typeof prepareIcaAccountKit>>} IcaAccountKit */
2 changes: 1 addition & 1 deletion packages/orchestration/src/exos/local-chain-facade.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @file ChainAccount exo */
/** @file Localchain Facade exo */
import { E } from '@endo/far';
// eslint-disable-next-line no-restricted-syntax -- just the import
import { heapVowE } from '@agoric/vow/vat.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/exos/orchestrator.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @file ChainAccount exo */
/** @file Orchestrator exo */
import { AmountShape } from '@agoric/ertp';
import { pickFacet } from '@agoric/vat-data';
import { makeTracer } from '@agoric/internal';
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/exos/remote-chain-facade.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @file ChainAccount exo */
/** @file Remote Chain Facade exo */
import { makeTracer } from '@agoric/internal';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export type * from './chain-info.js';
export type * from './cosmos-api.js';
export type * from './ethereum-api.js';
export type * from './exos/chain-account-kit.js';
export type * from './exos/ica-account-kit.js';
export type * from './exos/icq-connection-kit.js';
export type * from './orchestration-api.js';
export type * from './exos/cosmos-interchain-service.js';
Expand Down
3 changes: 2 additions & 1 deletion packages/orchestration/src/utils/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export const findAddressField = remoteAddressString => {
// Extract JSON version string assuming it's always surrounded by {}
const jsonStr = remoteAddressString?.match(/{.*?}/)?.[0];
const jsonObj = jsonStr ? JSON.parse(jsonStr) : undefined;
return jsonObj?.address ?? undefined;
if (!jsonObj?.address?.length) return undefined;
return jsonObj.address;
} catch (error) {
return undefined;
}
Expand Down
Loading

0 comments on commit c1ec381

Please sign in to comment.