Skip to content

Commit

Permalink
9212 chain helpers (#9439)
Browse files Browse the repository at this point in the history
refs: #9212 

## Description

Incremental progress on #9212 

Plus some drive-by improvements 

### 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] committed Jun 4, 2024
2 parents a446a00 + 19bc874 commit ceec1d5
Show file tree
Hide file tree
Showing 27 changed files with 575 additions and 232 deletions.
29 changes: 26 additions & 3 deletions a3p-integration/proposals/c:stake-bld/stakeBld.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,33 @@ test('basic', async t => {
},
});

const postDelegation = await currentDelegation();
t.is(postDelegation.length, 2, 'new delegation now');
t.like(postDelegation[1], {
const afterDelegate = await currentDelegation();
t.is(afterDelegate.length, 2, 'delegations after delegation');
t.like(afterDelegate[1], {
balance: { amount: '10', denom: 'ubld' },
// omit 'delegation' because it has 'delegatorAddress' which is different every test run
});

await walletUtils.broadcastBridgeAction(GOV1ADDR, {
method: 'executeOffer',
offer: {
id: 'request-undelegate',
invitationSpec: {
source: 'continuing',
previousOffer: 'request-stake',
invitationMakerName: 'Undelegate',
invitationArgs: [VALIDATOR_ADDRESS, { brand: brand.BLD, value: 5n }],
},
proposal: {},
},
});

// TODO wait until completionTime, so we can check the count has gone back down
// https://github.com/Agoric/agoric-sdk/pull/9439#discussion_r1626451871

// const afterUndelegate = await currentDelegation();
// t.is(afterDelegate.length, 1, 'delegations after undelegation');
// t.like(afterUndelegate[1], {
// balance: { amount: '5', denom: 'ubld' },
// });
});
2 changes: 1 addition & 1 deletion packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test.serial('stakeAtom - repl-style', async t => {
};
await t.notThrowsAsync(EV(account).delegate(validatorAddress, atomAmount));

const queryRes = await EV(account).getBalance();
const queryRes = await EV(account).getBalance('uatom');
t.deepEqual(queryRes, { value: 0n, denom: 'uatom' });

const queryUnknownDenom = await EV(account).getBalance('some-invalid-denom');
Expand Down
6 changes: 3 additions & 3 deletions packages/orchestration/src/chain-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import type {
StakingAccountQueries,
} from './types.js';

export type ChainInfo = CosmosChainInfo | EthChainInfo;

// TODO generate this automatically with a build script drawing on data sources such as https://github.com/cosmos/chain-registry

// XXX methods ad-hoc and not fully accurate
export type KnownChains = {
export type KnownChains = Record<string, { info: ChainInfo; methods?: {} }> & {
stride: {
info: CosmosChainInfo;
methods: IcaAccount &
Expand Down Expand Up @@ -60,5 +62,3 @@ export type KnownChains = {
StakingAccountQueries;
};
};

export type ChainInfo = CosmosChainInfo | EthChainInfo;
4 changes: 1 addition & 3 deletions packages/orchestration/src/cosmos-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export type IBCConnectionInfo = {
*/
export type CosmosChainInfo = {
chainId: string;
connections: MapStore<string, IBCConnectionInfo>; // chainId or wellKnownName
connections: Record<string, IBCConnectionInfo>; // chainId or wellKnownName
icaEnabled: boolean;
icqEnabled: boolean;
pfmEnabled: boolean;
Expand Down Expand Up @@ -196,8 +196,6 @@ export interface IcaAccount {
msgs: AnyJson[],
opts?: Partial<Omit<TxBody, 'messages'>>,
) => Promise<string>;
/** deposit payment from zoe to the account*/
deposit: (payment: Payment) => Promise<void>;
/** get Purse for a brand to .withdraw() a Payment from the account */
getPurse: (brand: Brand) => Promise<Purse>;
/**
Expand Down
45 changes: 21 additions & 24 deletions packages/orchestration/src/examples/stakeAtom.contract.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/**
* @file Example contract that uses orchestration
*/
/** @file Example contract that uses orchestration */
// TODO rename to "stakeIca" or something else that conveys is parameterized nature

import { makeTracer, StorageNodeShape } from '@agoric/internal';
import { TimerServiceShape } from '@agoric/time';
Expand All @@ -9,7 +8,7 @@ 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';
import { prepareCosmosOrchestrationAccount } from '../exos/cosmosOrchestrationAccount.js';

const trace = makeTracer('StakeAtom');
/**
Expand All @@ -20,6 +19,11 @@ const trace = makeTracer('StakeAtom');
*/

export const meta = harden({
customTermsShape: {
hostConnectionId: M.string(),
controllerConnectionId: M.string(),
bondDenom: M.string(),
},
privateArgsShape: {
orchestration: M.remotable('orchestration'),
storageNode: StorageNodeShape,
Expand Down Expand Up @@ -57,7 +61,7 @@ export const start = async (zcf, privateArgs, baggage) => {

const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);

const makeStakingAccountKit = prepareStakingAccountKit(
const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount(
zone,
makeRecorderKit,
zcf,
Expand All @@ -68,28 +72,21 @@ export const start = async (zcf, privateArgs, baggage) => {
hostConnectionId,
controllerConnectionId,
);
// #9212 TODO do not fail if host does not have `async-icq` module;
// TODO https://github.com/Agoric/agoric-sdk/issues/9326
// Should not fail if host does not have `async-icq` module;
// communicate to OrchestrationAccount that it can't send queries
const icqConnection = await E(orchestration).provideICQConnection(
controllerConnectionId,
);
const accountAddress = await E(account).getAddress();
trace('account address', accountAddress);
const { holder, invitationMakers } = makeStakingAccountKit(
accountAddress,
bondDenom,
{
account,
storageNode,
icqConnection,
timer,
},
);
return {
publicSubscribers: holder.getPublicTopics(),
invitationMakers,
account: holder,
};
const holder = makeCosmosOrchestrationAccount(accountAddress, bondDenom, {
account,
storageNode,
icqConnection,
timer,
});
return holder;
}

const publicFacet = zone.exo(
Expand All @@ -101,15 +98,15 @@ export const start = async (zcf, privateArgs, baggage) => {
{
async makeAccount() {
trace('makeAccount');
const { account } = await makeAccountKit();
return account;
return makeAccountKit();
},
makeAccountInvitationMaker() {
trace('makeCreateAccountInvitation');
return zcf.makeInvitation(
async seat => {
seat.exit();
return makeAccountKit();
const holder = await makeAccountKit();
return holder.asContinuingOffer();
},
'wantStakingAccount',
undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const start = async (zcf, privateArgs, baggage) => {

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

const makeLocalChainAccountKit = prepareLocalChainAccountKit(
zone,
Expand Down
15 changes: 9 additions & 6 deletions packages/orchestration/src/examples/swapExample.contract.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StorageNodeShape } from '@agoric/internal';
import { TimerServiceShape } from '@agoric/time';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { Far } from '@endo/far';
import { deeplyFulfilled } from '@endo/marshal';
import { M, objectMap } from '@endo/patterns';
Expand All @@ -13,6 +14,7 @@ import { orcUtils } from '../utils/orc.js';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {Remote} from '@agoric/internal';
* @import {OrchestrationService} from '../service.js';
* @import {Baggage} from '@agoric/vat-data'
* @import {Zone} from '@agoric/zone';
*/

Expand All @@ -23,7 +25,6 @@ export const meta = {
orchestrationService: M.or(M.remotable('orchestration'), null),
storageNode: StorageNodeShape,
timerService: M.or(TimerServiceShape, null),
zone: M.any(),
},
upgradability: 'canUpgrade',
};
Expand All @@ -42,16 +43,18 @@ export const makeNatAmountShape = (brand, min) =>
* @param {ZCF} zcf
* @param {{
* localchain: Remote<LocalChain>;
* orchestrationService: Remote<OrchestrationService> | null;
* orchestrationService: Remote<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* timerService: Remote<TimerService> | null;
* zone: Zone;
* timerService: Remote<TimerService>;
* }} privateArgs
* @param {Baggage} baggage
*/
export const start = async (zcf, privateArgs) => {
export const start = async (zcf, privateArgs, baggage) => {
const { brands } = zcf.getTerms();

const { localchain, orchestrationService, storageNode, timerService, zone } =
const zone = makeDurableZone(baggage);

const { localchain, orchestrationService, storageNode, timerService } =
privateArgs;

const { orchestrate } = makeOrchestrationFacade({
Expand Down
16 changes: 9 additions & 7 deletions packages/orchestration/src/examples/unbondExample.contract.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { makeDurableZone } from '@agoric/zone/durable.js';
import { Far } from '@endo/far';
import { M } from '@endo/patterns';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { makeOrchestrationFacade } from '../facade.js';

/**
Expand Down Expand Up @@ -49,19 +49,21 @@ export const start = async (zcf, privateArgs, baggage) => {
const celestia = await orch.getChain('celestia');
const celestiaAccount = await celestia.makeAccount();

const delegations = await celestiaAccount.getDelegations();
// wait for the undelegations to be complete (may take weeks)
await celestiaAccount.undelegate(delegations);
// TODO implement these
// const delegations = await celestiaAccount.getDelegations();
// // wait for the undelegations to be complete (may take weeks)
// await celestiaAccount.undelegate(delegations);

// ??? should this be synchronous? depends on how names are resolved.
const stride = await orch.getChain('stride');
const strideAccount = await stride.makeAccount();

// TODO the `TIA` string actually needs to be the Brand from AgoricNames
const tiaAmt = await celestiaAccount.getBalance('TIA');
await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress());
// const tiaAmt = await celestiaAccount.getBalance('TIA');
// await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress());

await strideAccount.liquidStake(tiaAmt);
// await strideAccount.liquidStake(tiaAmt);
console.log(celestiaAccount, strideAccount);
},
);

Expand Down
47 changes: 23 additions & 24 deletions packages/orchestration/src/exos/chainAccountKit.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/** @file ChainAccount exo */
import { NonNullish } from '@agoric/assert';
import { PurseShape } from '@agoric/ertp';
import { makeTracer } from '@agoric/internal';
import { V as E } from '@agoric/vow/vat.js';
import { M } from '@endo/patterns';
import { PaymentShape, PurseShape } from '@agoric/ertp';
import { findAddressField } from '../utils/address.js';
import {
ConnectionHandlerI,
ChainAddressShape,
ConnectionHandlerI,
Proto3Shape,
} from '../typeGuards.js';
import { findAddressField } from '../utils/address.js';
import { makeTxPacket, parseTxPacket } from '../utils/packet.js';

/**
Expand All @@ -30,6 +30,8 @@ const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS';

export const ChainAccountI = M.interface('ChainAccount', {
getAddress: M.call().returns(ChainAddressShape),
getBalance: M.callWhen(M.string()).returns(M.any()),
getBalances: M.callWhen().returns(M.any()),
getLocalAddress: M.call().returns(M.string()),
getRemoteAddress: M.call().returns(M.string()),
getPort: M.call().returns(M.remotable('Port')),
Expand All @@ -38,7 +40,6 @@ export const ChainAccountI = M.interface('ChainAccount', {
.optional(M.record())
.returns(M.promise()),
close: M.callWhen().returns(M.undefined()),
deposit: M.callWhen(PaymentShape).returns(M.undefined()),
getPurse: M.callWhen().returns(PurseShape),
});

Expand All @@ -63,27 +64,31 @@ export const prepareChainAccountKit = zone =>
* @param {string} requestedRemoteAddress
*/
(port, requestedRemoteAddress) =>
/** @type {State} */ (
harden({
port,
connection: undefined,
requestedRemoteAddress,
remoteAddress: undefined,
chainAddress: undefined,
localAddress: undefined,
})
),
/** @type {State} */ ({
port,
connection: undefined,
requestedRemoteAddress,
remoteAddress: undefined,
chainAddress: undefined,
localAddress: undefined,
}),
{
account: {
/**
* @returns {ChainAddress}
*/
/** @returns {ChainAddress} */
getAddress() {
return NonNullish(
this.state.chainAddress,
'ICA channel creation acknowledgement not yet received.',
);
},
getBalance(_denom) {
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9326
throw new Error('not yet implemented');
},
getBalances() {
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9326
throw new Error('not yet implemented');
},
getLocalAddress() {
return NonNullish(
this.state.localAddress,
Expand Down Expand Up @@ -121,9 +126,7 @@ export const prepareChainAccountKit = zone =>
ack => parseTxPacket(ack),
);
},
/**
* Close the remote account
*/
/** Close the remote account */
async close() {
/// XXX what should the behavior be here? and `onClose`?
// - retrieve assets?
Expand All @@ -132,10 +135,6 @@ export const prepareChainAccountKit = zone =>
if (!connection) throw Fail`connection not available`;
await E(connection).close();
},
async deposit(payment) {
console.log('deposit got', payment);
throw new Error('not yet implemented');
},
/**
* get Purse for a brand to .withdraw() a Payment from the account
*
Expand Down
Loading

0 comments on commit ceec1d5

Please sign in to comment.