Skip to content

Commit

Permalink
feat(localchain): add .transfer() helper to LocalChainAccount
Browse files Browse the repository at this point in the history
- towards `OrchestrationAccount` interface for a `LocalChainAccount`
- includes `mockChainInfo` as IBCChannelIDs are needed to construct a transfer message from the `ChainAddress` parameter
  • Loading branch information
0xpatrickdev committed May 24, 2024
1 parent 9c23e49 commit fd11145
Show file tree
Hide file tree
Showing 19 changed files with 787 additions and 77 deletions.
8 changes: 8 additions & 0 deletions packages/cosmic-proto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
"types": "./dist/codegen/ibc/applications/interchain_accounts/v1/packet.d.ts",
"default": "./dist/codegen/ibc/applications/interchain_accounts/v1/packet.js"
},
"./ibc/core/channel/v1/channel.js": {
"types": "./dist/codegen/ibc/core/channel/v1/channel.d.ts",
"default": "./dist/codegen/ibc/core/channel/v1/channel.js"
},
"./ibc/core/connection/v1/connection.js": {
"types": "./dist/codegen/ibc/core/connection/v1/connection.d.ts",
"default": "./dist/codegen/ibc/core/connection/v1/connection.js"
},
"./icq/*.js": {
"types": "./dist/codegen/icq/*.d.ts",
"default": "./dist/codegen/icq/v1/*.js"
Expand Down
7 changes: 7 additions & 0 deletions packages/cosmic-proto/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import type {
} from './codegen/cosmos/staking/v1beta1/tx.js';
import { RequestQuery } from './codegen/tendermint/abci/types.js';
import type { Any } from './codegen/google/protobuf/any.js';
import {
MsgTransfer,
MsgTransferResponse,
} from './codegen/ibc/applications/transfer/v1/tx.js';

/**
* The result of Any.toJSON(). The type in cosms-types says it returns
Expand All @@ -28,13 +32,16 @@ export type Proto3Shape = {
'/cosmos.bank.v1beta1.QueryAllBalancesResponse': QueryAllBalancesResponse;
'/cosmos.staking.v1beta1.MsgDelegate': MsgDelegate;
'/cosmos.staking.v1beta1.MsgDelegateResponse': MsgDelegateResponse;
'/ibc.applications.transfer.v1.MsgTransfer': MsgTransfer;
'/ibc.applications.transfer.v1.MsgTransferResponse': MsgTransferResponse;
};

// Often s/Request$/Response/ but not always
type ResponseMap = {
'/cosmos.bank.v1beta1.MsgSend': '/cosmos.bank.v1beta1.MsgSendResponse';
'/cosmos.bank.v1beta1.QueryAllBalancesRequest': '/cosmos.bank.v1beta1.QueryAllBalancesResponse';
'/cosmos.staking.v1beta1.MsgDelegate': '/cosmos.staking.v1beta1.MsgDelegateResponse';
'/ibc.applications.transfer.v1.MsgTransfer': '/ibc.applications.transfer.v1.MsgTransferResponse';
};

/**
Expand Down
18 changes: 13 additions & 5 deletions packages/orchestration/src/cosmos-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import type {
RemoteIbcAddress,
} from '@agoric/vats/tools/ibc-utils.js';
import { MsgTransfer } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js';
import { IBCChannelID } from '@agoric/vats';
import type { State as IBCConnectionState } from '@agoric/cosmic-proto/ibc/core/connection/v1/connection.js';
import type {
Order,
State as IBCChannelState,
} from '@agoric/cosmic-proto/ibc/core/channel/v1/channel.js';
import { IBCChannelID, IBCConnectionID } from '@agoric/vats';
import { MapStore } from '@agoric/store';
import type { AmountArg, ChainAddress, DenomAmount } from './types.js';

Expand All @@ -32,14 +37,14 @@ export type CosmosValidatorAddress = ChainAddress & {
addressEncoding: 'bech32';
};

/** Info for an IBC Connection (Chain:Chain relationship, that can contain multiple Channels) */
/** Represents an IBC Connection between two chains, which can contain multiple Channels. */
export type IBCConnectionInfo = {
id: string; // e.g. connection-0
id: IBCConnectionID; // e.g. connection-0
client_id: string; // '07-tendermint-0'
state: 'OPEN' | 'TRYOPEN' | 'INIT' | 'CLOSED';
state: IBCConnectionState;
counterparty: {
client_id: string;
connection_id: string;
connection_id: IBCConnectionID;
prefix: {
key_prefix: string;
};
Expand All @@ -51,6 +56,9 @@ export type IBCConnectionInfo = {
channelId: IBCChannelID;
counterPartyPortId: string;
counterPartyChannelId: IBCChannelID;
ordering: Order;
state: IBCChannelState;
version: string; // e.eg. 'ics20-1'
};
};

Expand Down
15 changes: 9 additions & 6 deletions packages/orchestration/src/examples/stakeAtom.contract.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/**
* @file Example contract that uses orchestration
*/

import { makeTracer, StorageNodeShape } from '@agoric/internal';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { V as E } from '@agoric/vow/vat.js';
import { M } from '@endo/patterns';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { M } from '@endo/patterns';
import { prepareStakingAccountKit } from '../exos/stakingAccountKit.js';

const trace = makeTracer('StakeAtom');
Expand Down Expand Up @@ -61,7 +63,7 @@ export const start = async (zcf, privateArgs, baggage) => {
zcf,
);

async function makeAccount() {
async function makeAccountKit() {
const account = await E(orchestration).makeAccount(
hostConnectionId,
controllerConnectionId,
Expand Down Expand Up @@ -94,19 +96,20 @@ export const start = async (zcf, privateArgs, baggage) => {
'StakeAtom',
M.interface('StakeAtomI', {
makeAccount: M.callWhen().returns(M.remotable('ChainAccount')),
makeAcountInvitationMaker: M.call().returns(M.promise()),
makeAcountInvitationMaker: M.callWhen().returns(InvitationShape),
}),
{
async makeAccount() {
trace('makeAccount');
return makeAccount().then(({ account }) => account);
const { account } = await makeAccountKit();
return account;
},
makeAcountInvitationMaker() {
trace('makeCreateAccountInvitation');
return zcf.makeInvitation(
async seat => {
seat.exit();
return makeAccount();
return makeAccountKit();
},
'wantStakingAccount',
undefined,
Expand Down
115 changes: 78 additions & 37 deletions packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
/**
* @file Stake BLD contract
*
*/

import { makeTracer } from '@agoric/internal';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { M } from '@endo/patterns';
import { E } from '@endo/far';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { atomicTransfer } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
import { deeplyFulfilled } from '@endo/marshal';
import { M } from '@endo/patterns';
import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js';
import { prepareMockChainInfo } from '../utils/mockChainInfo.js';

/**
* @import {TimerBrand, TimerService} from '@agoric/time';
*/

const trace = makeTracer('StakeBld');

Expand All @@ -20,12 +25,15 @@ const trace = makeTracer('StakeBld');
* localchain: import('@agoric/vats/src/localchain.js').LocalChain;
* marshaller: Marshaller;
* storageNode: StorageNode;
* timerService: TimerService;
* timerBrand: TimerBrand;
* }} privateArgs
* @param {import("@agoric/vat-data").Baggage} baggage
*/
export const start = async (zcf, privateArgs, baggage) => {
const { BLD } = zcf.getTerms().brands;
const BLD = zcf.getTerms().brands.In;

// XXX is this safe to call before prepare statements are completed?
const bldAmountShape = await E(BLD).getAmountShape();

const zone = makeDurableZone(baggage);
Expand All @@ -34,48 +42,81 @@ export const start = async (zcf, privateArgs, baggage) => {
baggage,
privateArgs.marshaller,
);

// Mocked until #8879
// Would expect this to be instantiated elsewhere, and passed in as a reference
const agoricChainInfo = prepareMockChainInfo(zone);

const makeLocalChainAccountKit = prepareLocalChainAccountKit(
baggage,
makeRecorderKit,
zcf,
privateArgs.timerService,
privateArgs.timerBrand,
agoricChainInfo,
);

const publicFacet = zone.exo('StakeBld', undefined, {
makeStakeBldInvitation() {
return zcf.makeInvitation(
async seat => {
const { give } = seat.getProposal();
trace('makeStakeBldInvitation', give);
// XXX type appears local but it's remote
const account = await E(privateArgs.localchain).makeAccount();
const lcaSeatKit = zcf.makeEmptySeatKit();
atomicTransfer(zcf, seat, lcaSeatKit.zcfSeat, give);
seat.exit();
trace('makeStakeBldInvitation tryExit lca userSeat');
await E(lcaSeatKit.userSeat).tryExit();
trace('awaiting payouts');
const payouts = await E(lcaSeatKit.userSeat).getPayouts();
const { holder, invitationMakers } = makeLocalChainAccountKit(
account,
privateArgs.storageNode,
);
trace('awaiting deposit');
await E(account).deposit(await payouts.In);
async function makeLocalAccountKit() {
const account = await E(privateArgs.localchain).makeAccount();
const address = await E(account).getAddress();
return makeLocalChainAccountKit({
account,
address,
storageNode: privateArgs.storageNode,
});
}

return {
const publicFacet = zone.exo(
'StakeBld',
M.interface('StakeBldI', {
makeAccount: M.callWhen().returns(M.remotable('LocalChainAccountHolder')),
makeAcountInvitationMaker: M.callWhen().returns(InvitationShape),
makeStakeBldInvitation: M.callWhen().returns(InvitationShape),
}),
{
makeStakeBldInvitation() {
return zcf.makeInvitation(
async seat => {
const { give } = seat.getProposal();
trace('makeStakeBldInvitation', give);
const { holder, invitationMakers } = await makeLocalAccountKit();
const { In } = await deeplyFulfilled(
withdrawFromSeat(zcf, seat, give),
);
await E(holder).deposit(In);
seat.exit();
return harden({
publicSubscribers: holder.getPublicTopics(),
invitationMakers,
account: holder,
});
},
'wantStake',
undefined,
M.splitRecord({
give: { In: bldAmountShape },
}),
);
},
async makeAccount() {
trace('makeAccount');
const { holder } = await makeLocalAccountKit();
return holder;
},
makeAcountInvitationMaker() {
trace('makeCreateAccountInvitation');
return zcf.makeInvitation(async seat => {
seat.exit();
const { holder, invitationMakers } = await makeLocalAccountKit();
return harden({
publicSubscribers: holder.getPublicTopics(),
invitationMakers,
account: holder,
};
},
'wantStake',
undefined,
M.splitRecord({
give: { In: bldAmountShape },
}),
);
});
}, 'wantLocalChainAccount');
},
},
});
);

return { publicFacet };
};
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const start = async (zcf, privateArgs) => {
// deposit funds from user seat to LocalChainAccount
const payments = await withdrawFromSeat(zcf, seat, give);
await deeplyFulfilled(objectMap(payments, localAccount.deposit));
seat.exit();

// build swap instructions with orcUtils library
const transferMsg = orcUtils.makeOsmosisSwap({
Expand Down
1 change: 0 additions & 1 deletion packages/orchestration/src/exos/chainAccountKit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/** @file ChainAccount exo */

import { NonNullish } from '@agoric/assert';
import { makeTracer } from '@agoric/internal';
import { V as E } from '@agoric/vow/vat.js';
Expand Down
Loading

0 comments on commit fd11145

Please sign in to comment.