Skip to content

Commit

Permalink
feat(orchestration): cosmosOrchestrationAccount returns unwrapped vows
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev authored and turadg committed Jun 19, 2024
1 parent 5b263b9 commit ed28c2c
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 101 deletions.
5 changes: 4 additions & 1 deletion packages/orchestration/src/examples/stakeIca.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { makeTracer, StorageNodeShape } from '@agoric/internal';
import { TimerServiceShape } from '@agoric/time';
import { V as E } from '@agoric/vow/vat.js';
import { V as E, prepareVowTools } from '@agoric/vow/vat.js';
import {
prepareRecorderKitMakers,
provideAll,
Expand Down Expand Up @@ -76,9 +76,12 @@ export const start = async (zcf, privateArgs, baggage) => {

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

const vowTools = prepareVowTools(zone.subZone('vows'));

const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount(
zone,
makeRecorderKit,
vowTools,
zcf,
);

Expand Down
4 changes: 2 additions & 2 deletions packages/orchestration/src/exos/chain-account-kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const prepareChainAccountKit = (zone, { watch, when }) =>
* decoded using the corresponding `Msg*Response` object.
* @throws {Error} if packet fails to send or an error is returned
*/
executeEncodedTx(msgs, opts) {
async executeEncodedTx(msgs, opts) {
const { connection } = this.state;
// TODO #9281 do not throw synchronously when returning a promise; return a rejected Vow
/// see https://github.com/Agoric/agoric-sdk/pull/9454#discussion_r1626898694
Expand All @@ -150,7 +150,7 @@ export const prepareChainAccountKit = (zone, { watch, when }) =>
);
},
/** Close the remote account */
close() {
async close() {
/// TODO #9192 what should the behavior be here? and `onClose`?
// - retrieve assets?
// - revoke the port?
Expand Down
217 changes: 142 additions & 75 deletions packages/orchestration/src/exos/cosmos-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import {
MsgBeginRedelegate,
MsgDelegate,
MsgDelegateResponse,
MsgUndelegate,
MsgUndelegateResponse,
} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js';
Expand All @@ -26,13 +25,10 @@ import {
AmountArgShape,
ChainAddressShape,
ChainAmountShape,
CoinShape,
DelegationShape,
} from '../typeGuards.js';
import {
encodeTxResponse,
maxClockSkew,
tryDecodeResponse,
} from '../utils/cosmos.js';
import { maxClockSkew, tryDecodeResponse } from '../utils/cosmos.js';
import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js';
import { dateInSeconds } from '../utils/time.js';

Expand All @@ -43,7 +39,10 @@ import { dateInSeconds } from '../utils/time.js';
* @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js';
* @import {Remote} from '@agoric/internal';
* @import {TimerService} from '@agoric/time';
* @import {VowTools} from '@agoric/vow';
* @import {Zone} from '@agoric/zone';
* @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js';
* @import {JsonSafe} from '@agoric/cosmic-proto';
*/

const trace = makeTracer('ComosOrchestrationAccountHolder');
Expand Down Expand Up @@ -94,28 +93,19 @@ const PUBLIC_TOPICS = {
account: ['Staking Account holder status', M.any()],
};

export const trivialDelegateResponse = encodeTxResponse(
{},
MsgDelegateResponse.toProtoMsg,
);

const expect = (actual, expected, message) => {
if (actual !== expected) {
console.log(message, { actual, expected });
}
};

/** @type {(c: { denom: string; amount: string }) => DenomAmount} */
const toDenomAmount = c => ({ denom: c.denom, value: BigInt(c.amount) });

/**
* @param {Zone} zone
* @param {MakeRecorderKit} makeRecorderKit
* @param {VowTools} vowTools
* @param {ZCF} zcf
*/
export const prepareCosmosOrchestrationAccountKit = (
zone,
makeRecorderKit,
{ when, watch },
zcf,
) => {
const makeCosmosOrchestrationAccountKit = zone.exoClassKit(
Expand All @@ -126,6 +116,26 @@ export const prepareCosmosOrchestrationAccountKit = (
getUpdater: M.call().returns(M.remotable()),
amountToCoin: M.call(AmountArgShape).returns(M.record()),
}),
returnVoidWatcher: M.interface('returnVoidWatcher', {
onFulfilled: M.call(M.or(M.string(), M.record()))
.optional(M.arrayOf(M.undefined()))
.returns(M.undefined()),
}),
balanceQueryWatcher: M.interface('balanceQueryWatcher', {
onFulfilled: M.call(M.arrayOf(M.record()))
.optional(M.arrayOf(M.undefined())) // empty context
.returns(M.or(M.record(), M.undefined())),
}),
undelegateWatcher: M.interface('undelegateWatcher', {
onFulfilled: M.call(M.string())
.optional(M.arrayOf(M.undefined())) // empty context
.returns(M.promise()),
}),
withdrawRewardWatcher: M.interface('withdrawRewardWatcher', {
onFulfilled: M.call(M.string())
.optional(M.arrayOf(M.undefined())) // empty context
.returns(M.arrayOf(CoinShape)),
}),
holder: IcaAccountHolderI,
invitationMakers: M.interface('invitationMakers', {
Delegate: M.callWhen(ChainAddressShape, AmountArgShape).returns(
Expand Down Expand Up @@ -195,6 +205,59 @@ export const prepareCosmosOrchestrationAccountKit = (
});
},
},
balanceQueryWatcher: {
/**
* @param {JsonSafe<ResponseQuery>[]} results
*/
onFulfilled([result]) {
if (!result?.key) throw Fail`Error parsing result ${result}`;
const { balance } = QueryBalanceResponse.decode(
decodeBase64(result.key),
);
if (!balance) throw Fail`Result lacked balance key: ${result}`;
return harden(toDenomAmount(balance));
},
},
undelegateWatcher: {
/**
* @param {string} result
*/
onFulfilled(result) {
const response = tryDecodeResponse(
result,
MsgUndelegateResponse.fromProtoMsg,
);
trace('undelegate response', response);
const { completionTime } = response;
completionTime || Fail`No completion time result ${result}`;
return E(this.state.timer).wakeAt(
dateInSeconds(completionTime) + maxClockSkew,
);
},
},
/**
* takes an array of results (from `executeEncodedTx`) and returns void
* since we are not interested in the result
*/
returnVoidWatcher: {
/** @param {string | Record<string, unknown>} result */
onFulfilled(result) {
trace('Result', result);
return undefined;
},
},
withdrawRewardWatcher: {
/** @param {string} result */
onFulfilled(result) {
const response = tryDecodeResponse(
result,
MsgWithdrawDelegatorRewardResponse.fromProtoMsg,
);
trace('withdrawReward response', response);
const { amount: coins } = response;
return harden(coins.map(toDenomAmount));
},
},
invitationMakers: {
/**
* @param {CosmosValidatorAddress} validator
Expand Down Expand Up @@ -292,17 +355,20 @@ export const prepareCosmosOrchestrationAccountKit = (
const { helper } = this.facets;
const { chainAddress } = this.state;

const result = await E(helper.owned()).executeEncodedTx([
Any.toJSON(
MsgDelegate.toProtoMsg({
delegatorAddress: chainAddress.address,
validatorAddress: validator.address,
amount: helper.amountToCoin(amount),
}),
return when(
watch(
E(helper.owned()).executeEncodedTx([
Any.toJSON(
MsgDelegate.toProtoMsg({
delegatorAddress: chainAddress.address,
validatorAddress: validator.address,
amount: helper.amountToCoin(amount),
}),
),
]),
this.facets.returnVoidWatcher,
),
]);

expect(result, trivialDelegateResponse, 'MsgDelegateResponse');
);
},
async deposit(payment) {
trace('deposit', payment);
Expand All @@ -325,19 +391,23 @@ export const prepareCosmosOrchestrationAccountKit = (
const { helper } = this.facets;
const { chainAddress } = this.state;

// NOTE: response, including completionTime, is currently discarded.
await E(helper.owned()).executeEncodedTx([
Any.toJSON(
MsgBeginRedelegate.toProtoMsg({
delegatorAddress: chainAddress.address,
validatorSrcAddress: srcValidator.address,
validatorDstAddress: dstValidator.address,
amount: helper.amountToCoin(amount),
}),
return when(
watch(
E(helper.owned()).executeEncodedTx([
Any.toJSON(
MsgBeginRedelegate.toProtoMsg({
delegatorAddress: chainAddress.address,
validatorSrcAddress: srcValidator.address,
validatorDstAddress: dstValidator.address,
amount: helper.amountToCoin(amount),
}),
),
]),
// NOTE: response, including completionTime, is currently discarded.
this.facets.returnVoidWatcher,
),
]);
);
},

/**
* @param {CosmosValidatorAddress} validator
* @returns {Promise<DenomAmount[]>}
Expand All @@ -351,14 +421,13 @@ export const prepareCosmosOrchestrationAccountKit = (
validatorAddress: validator.address,
});
const account = helper.owned();
const result = await E(account).executeEncodedTx([Any.toJSON(msg)]);
const response = tryDecodeResponse(
result,
MsgWithdrawDelegatorRewardResponse.fromProtoMsg,

return when(
watch(
E(account).executeEncodedTx([Any.toJSON(msg)]),
this.facets.withdrawRewardWatcher,
),
);
trace('withdrawReward response', response);
const { amount: coins } = response;
return harden(coins.map(toDenomAmount));
},
/**
* @param {DenomArg} denom
Expand All @@ -372,20 +441,19 @@ export const prepareCosmosOrchestrationAccountKit = (
// TODO #9211 lookup denom from brand
assert.typeof(denom, 'string');

const [result] = await E(icqConnection).query([
toRequestQueryJson(
QueryBalanceRequest.toProtoMsg({
address: chainAddress.address,
denom,
}),
return when(
watch(
E(icqConnection).query([
toRequestQueryJson(
QueryBalanceRequest.toProtoMsg({
address: chainAddress.address,
denom,
}),
),
]),
this.facets.balanceQueryWatcher,
),
]);
if (!result?.key) throw Fail`Error parsing result ${result}`;
const { balance } = QueryBalanceResponse.decode(
decodeBase64(result.key),
);
if (!balance) throw Fail`Result lacked balance key: ${result}`;
return harden(toDenomAmount(balance));
},

send(toAccount, amount) {
Expand All @@ -411,28 +479,24 @@ export const prepareCosmosOrchestrationAccountKit = (
async undelegate(delegations) {
trace('undelegate', delegations);
const { helper } = this.facets;
const { chainAddress, bondDenom, timer } = this.state;

const result = await E(helper.owned()).executeEncodedTx(
delegations.map(d =>
Any.toJSON(
MsgUndelegate.toProtoMsg({
delegatorAddress: chainAddress.address,
validatorAddress: d.validatorAddress,
amount: { denom: bondDenom, amount: d.shares },
}),
const { chainAddress, bondDenom } = this.state;

const undelegateV = watch(
E(helper.owned()).executeEncodedTx(
delegations.map(d =>
Any.toJSON(
MsgUndelegate.toProtoMsg({
delegatorAddress: chainAddress.address,
validatorAddress: d.validatorAddress,
amount: { denom: bondDenom, amount: d.shares },
}),
),
),
),
this.facets.undelegateWatcher,
);

const response = tryDecodeResponse(
result,
MsgUndelegateResponse.fromProtoMsg,
);
trace('undelegate response', response);
const { completionTime } = response;

await E(timer).wakeAt(dateInSeconds(completionTime) + maxClockSkew);
return when(watch(undelegateV, this.facets.returnVoidWatcher));
},
},
},
Expand All @@ -450,6 +514,7 @@ export const prepareCosmosOrchestrationAccountKit = (
/**
* @param {Zone} zone
* @param {MakeRecorderKit} makeRecorderKit
* @param {VowTools} vowTools
* @param {ZCF} zcf
* @returns {(
* ...args: Parameters<
Expand All @@ -460,11 +525,13 @@ export const prepareCosmosOrchestrationAccountKit = (
export const prepareCosmosOrchestrationAccount = (
zone,
makeRecorderKit,
vowTools,
zcf,
) => {
const makeKit = prepareCosmosOrchestrationAccountKit(
zone,
makeRecorderKit,
vowTools,
zcf,
);
return (...args) => makeKit(...args).holder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const prepareLocalOrchestrationAccountKit = (
.optional(M.arrayOf(M.undefined()))
.returns(M.record()),
}),
returnVoidWatcher: M.interface('extractFirstResultWatcher', {
returnVoidWatcher: M.interface('returnVoidWatcher', {
onFulfilled: M.call(M.arrayOf(M.record()))
.optional(M.arrayOf(M.undefined()))
.returns(M.undefined()),
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { prepareOrchestrator } from './exos/orchestrator.js';
/**
* @import {AsyncFlowTools} from '@agoric/async-flow';
* @import {Zone} from '@agoric/zone';
* @import {Vow} from '@agoric/vow';
* @import {Vow, VowTools} from '@agoric/vow';
* @import {TimerService} from '@agoric/time';
* @import {IBCConnectionID} from '@agoric/vats';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
Expand Down
Loading

0 comments on commit ed28c2c

Please sign in to comment.