Skip to content

Commit

Permalink
feat(localchain): add transfer method to LocalChainAccountKit holder (
Browse files Browse the repository at this point in the history
#9380)

refs: #9193

## Description

Adds `.transfer()` method to `LocalChainAccountKit`, working towards
`OrchestrationAccountI['transfer']` interface.


### Security Considerations

<!-- Does this change introduce new assumptions or dependencies that, if
violated, could introduce security vulnerabilities? How does this PR
change the boundaries between mutually-suspicious components? What new
authorities are introduced by this change, perhaps by new API calls?
-->

### Scaling Considerations

<!-- Does this change require or encourage significant increase in
consumption of CPU cycles, RAM, on-chain storage, message exchanges, or
other scarce resources? If so, can that be prevented or mitigated? -->

### Documentation Considerations

<!-- Give our docs folks some hints about what needs to be described to
downstream users.

Backwards compatibility: what happens to existing data or deployments
when this code is shipped? Do we need to instruct users to do something
to upgrade their saved data? If there is no upgrade path possible, how
bad will that be for users?

-->

### Testing Considerations

<!-- Every PR should of course come with tests of its own functionality.
What additional tests are still needed beyond those unit tests? How does
this affect CI, other test automation, or the testnet?
-->

### Upgrade Considerations

<!-- What aspects of this PR are relevant to upgrading live production
systems, and how should they be addressed? -->
  • Loading branch information
mergify[bot] authored May 24, 2024
2 parents 90906b1 + fd11145 commit 96f4c73
Show file tree
Hide file tree
Showing 22 changed files with 962 additions and 234 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
54 changes: 40 additions & 14 deletions packages/orchestration/src/cosmos-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import type {
LocalIbcAddress,
RemoteIbcAddress,
} from '@agoric/vats/tools/ibc-utils.js';
import { MsgTransfer } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js';
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';

/** A helper type for type extensions. */
Expand All @@ -29,25 +37,37 @@ export type CosmosValidatorAddress = ChainAddress & {
addressEncoding: 'bech32';
};

/** Represents an IBC Connection between two chains, which can contain multiple Channels. */
export type IBCConnectionInfo = {
id: IBCConnectionID; // e.g. connection-0
client_id: string; // '07-tendermint-0'
state: IBCConnectionState;
counterparty: {
client_id: string;
connection_id: IBCConnectionID;
prefix: {
key_prefix: string;
};
};
versions: { identifier: string; features: string[] }[];
delay_period: bigint;
transferChannel: {
portId: string;
channelId: IBCChannelID;
counterPartyPortId: string;
counterPartyChannelId: IBCChannelID;
ordering: Order;
state: IBCChannelState;
version: string; // e.eg. 'ics20-1'
};
};

/**
* Info for a Cosmos-based chain.
*/
export type CosmosChainInfo = {
chainId: string;
ibcConnectionInfo: {
id: string; // e.g. connection-0
client_id: string; // '07-tendermint-0'
state: 'OPEN' | 'TRYOPEN' | 'INIT' | 'CLOSED';
counterparty: {
client_id: string;
connection_id: string;
prefix: {
key_prefix: string;
};
};
versions: { identifier: string; features: string[] }[];
delay_period: bigint;
};
connections: MapStore<string, IBCConnectionInfo>; // chainId or wellKnownName
icaEnabled: boolean;
icqEnabled: boolean;
pfmEnabled: boolean;
Expand Down Expand Up @@ -197,3 +217,9 @@ export interface IcaAccount {
export type LiquidStakingMethods = {
liquidStake: (amount: AmountArg) => Promise<void>;
};

export type IBCMsgTransferOptions = {
timeoutHeight?: MsgTransfer['timeoutHeight'];
timeoutTimestamp?: MsgTransfer['timeoutTimestamp'];
memo?: string;
};
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
119 changes: 80 additions & 39 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 { prepareLocalchainAccountKit } from '../exos/localchainAccountKit.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,
);
const makeLocalchainAccountKit = prepareLocalchainAccountKit(

// 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 96f4c73

Please sign in to comment.