Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use orchFns.autoStake as target for receiveUpcall #10065

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 0 additions & 157 deletions packages/orchestration/src/examples/auto-stake-it-tap-kit.js

This file was deleted.

81 changes: 73 additions & 8 deletions packages/orchestration/src/examples/auto-stake-it.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,57 @@ import {
EmptyProposalShape,
InvitationShape,
} from '@agoric/zoe/src/typeGuards.js';
import { M } from '@endo/patterns';
import { makeTracer } from '@agoric/internal';
import { M, mustMatch } from '@endo/patterns';
import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js';
import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js';
import { withOrchestration } from '../utils/start-helper.js';
import { prepareStakingTap } from './auto-stake-it-tap-kit.js';
import * as flows from './auto-stake-it.flows.js';
import { ChainAddressShape } from '../typeGuards.js';

const trace = makeTracer('AutoStakeIt');

/**
* @import {GuestInterface} from '@agoric/async-flow';
* @import {Zone} from '@agoric/zone';
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
* @import {TargetApp} from '@agoric/vats/src/bridge-target.js';
* @import {ChainAddress, CosmosValidatorAddress, Denom} from '@agoric/orchestration';
* @import {TypedPattern} from '@agoric/internal';
* @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js';
* @import {LocalOrchestrationAccount} from '../exos/local-orchestration-account.js';
* @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js';
*/

/**
* @typedef {{
* stakingAccount: GuestInterface<CosmosOrchestrationAccount>;
* localAccount: GuestInterface<LocalOrchestrationAccount>;
Comment on lines +28 to +29
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use OrchestrationAccount<any>, which the flow is expecting, we see Argument of type '<OrchestrationAccount<any>>' is not assignable to parameter of type 'Passable'. on L105.

* config: {
* validator: CosmosValidatorAddress;
* localChainAddress: ChainAddress;
* remoteChainAddress: ChainAddress;
* sourceChannel: IBCChannelID;
* remoteDenom: Denom;
* localDenom: Denom;
* };
* }} StakingTapState
*/

/** @type {TypedPattern<StakingTapState>} */
const StakingTapStateShape = harden({
stakingAccount: M.remotable('CosmosOrchestrationAccount'),
localAccount: M.remotable('LocalOrchestrationAccount'),
config: {
validator: ChainAddressShape,
localChainAddress: ChainAddressShape,
remoteChainAddress: ChainAddressShape,
sourceChannel: M.string(),
remoteDenom: M.string(),
localDenom: M.string(),
},
});

/**
* AutoStakeIt allows users to to create an auto-forwarding address that
* transfers and stakes tokens on a remote chain when received.
Expand All @@ -33,16 +72,42 @@ const contract = async (
zone,
{ chainHub, orchestrateAll, vowTools },
) => {
const makeStakingTap = prepareStakingTap(
zone.subZone('stakingTap'),
vowTools,
);
const makePortfolioHolder = preparePortfolioHolder(
zone.subZone('portfolio'),
vowTools,
);

const { makeAccounts } = orchestrateAll(flows, {
/**
* Provides a {@link TargetApp} that reacts to an incoming IBC transfer.
*/
const makeStakingTap = zone.exoClass(
'StakingTap',
M.interface('AutoStakeItTap', {
receiveUpcall: M.call(M.record()).returns(M.undefined()),
}),
/** @param {StakingTapState} initialState */
initialState => {
mustMatch(initialState, StakingTapStateShape); // TODO use opts.stateShape
return harden(initialState);
},
{
/**
* Transfers from localAccount to stakingAccount, then delegates from the
* stakingAccount to `validator` if the expected token (remoteDenom) is
* received.
*
* @param {VTransferIBCEvent} event
*/
receiveUpcall(event) {
trace('receiveUpcall', event);
const { localAccount, stakingAccount, config } = this.state;
// eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this
orchFns.autoStake(localAccount, stakingAccount, config, event);
Copy link
Member Author

@0xpatrickdev 0xpatrickdev Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to do orchFns.autoStake(this.state, event) here, and call it ~(continuingOfferCtx, event) on the other end, but was greeted with Error: cannot serialize Remotables with accessors like "localAccount"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might try passing a copy with normal properties rather than accessors {... this.state }.

},
},
);

const orchFns = orchestrateAll(flows, {
makeStakingTap,
makePortfolioHolder,
chainHub,
Expand All @@ -56,7 +121,7 @@ const contract = async (
{
makeAccountsInvitation() {
return zcf.makeInvitation(
makeAccounts,
orchFns.makeAccounts,
'Make Accounts',
undefined,
EmptyProposalShape,
Expand Down
83 changes: 74 additions & 9 deletions packages/orchestration/src/examples/auto-stake-it.flows.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { makeTracer } from '@agoric/internal';
import { atob } from '@endo/base64';
import { Fail } from '@endo/errors';
import { denomHash } from '../utils/denomHash.js';

const trace = makeTracer('AutoStakeItFlows');

/**
* @import {ResolvedPublicTopic} from '@agoric/zoe/src/contractSupport/topics.js';
* @import {GuestInterface} from '@agoric/async-flow';
* @import {CosmosValidatorAddress, Orchestrator, CosmosInterchainService, Denom, OrchestrationAccount, StakingAccountActions, OrchestrationFlow} from '@agoric/orchestration';
* @import {MakeStakingTap} from './auto-stake-it-tap-kit.js';
* @import {VTransferIBCEvent} from '@agoric/vats';
* @import {CosmosValidatorAddress, Orchestrator, OrchestrationAccount, StakingAccountActions, OrchestrationFlow} from '@agoric/orchestration';
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
* @import {Guarded} from '@endo/exo';
* @import {MakePortfolioHolder} from '../exos/portfolio-holder-kit.js';
* @import {ChainHub} from '../exos/chain-hub.js';
* @import {StakingTapState} from './auto-stake-it.contract.js';
*/

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {{
* makeStakingTap: MakeStakingTap;
* makeStakingTap: (
* initialState: StakingTapState,
* ) => Guarded<{ receiveUpcall: (event: VTransferIBCEvent) => void }>;
* makePortfolioHolder: MakePortfolioHolder;
* chainHub: GuestInterface<ChainHub>;
* }} ctx
Expand Down Expand Up @@ -64,14 +73,18 @@ export const makeAccounts = async (

// Every time the `localAccount` receives `remoteDenom` over IBC, delegate it.
const tap = makeStakingTap({
// @ts-expect-error LocalOrchestrationAccount vs. OrchestrationAccount<any>
localAccount,
// @ts-expect-error CosmosOrchestrationAccount vs. OrchestrationAccount<any>
stakingAccount,
validator,
localChainAddress,
remoteChainAddress,
sourceChannel: transferChannel.counterPartyChannelId,
remoteDenom,
localDenom,
config: {
validator,
localChainAddress,
remoteChainAddress,
sourceChannel: transferChannel.counterPartyChannelId,
remoteDenom,
localDenom,
},
});
// XXX consider storing appRegistration, so we can .revoke() or .updateTargetApp()
// @ts-expect-error tap.receiveUpcall: 'Vow<void> | undefined' not assignable to 'Promise<any>'
Expand Down Expand Up @@ -100,3 +113,55 @@ export const makeAccounts = async (
return portfolioHolder.asContinuingOffer();
};
harden(makeAccounts);

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {object} ctx
* @param {StakingTapState['localAccount']} localAccount
* @param {StakingTapState['stakingAccount']} stakingAccount
* @param {StakingTapState['config']} config
* @param {VTransferIBCEvent} event
*/
export const autoStake = async (
orch,
ctx,
localAccount,
stakingAccount,
config,
event,
) => {
// ignore packets from unknown channels
if (event.packet.source_channel !== config.sourceChannel) {
return;
}
const tx = /** @type {FungibleTokenPacketData} */ (
JSON.parse(atob(event.packet.data))
);
trace('receiveUpcall packet data', tx);
const { remoteDenom, localChainAddress } = config;
// ignore outgoing transfers
if (tx.receiver !== localChainAddress.value) {
return;
}
// only interested in transfers of `remoteDenom`
if (tx.denom !== remoteDenom) {
return;
}

const { localDenom, remoteChainAddress, validator } = config;

await localAccount.transfer(
{
denom: localDenom,
value: BigInt(tx.amount),
},
remoteChainAddress,
);

await stakingAccount.delegate(validator, {
denom: remoteDenom,
value: BigInt(tx.amount),
});
};
harden(autoStake);
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,4 @@ export const prepareLocalOrchestrationAccountKit = (

/** @typedef {ReturnType<typeof prepareLocalOrchestrationAccountKit>} MakeLocalOrchestrationAccountKit */
/** @typedef {ReturnType<MakeLocalOrchestrationAccountKit>} LocalOrchestrationAccountKit */
/** @typedef {LocalOrchestrationAccountKit['holder']} LocalOrchestrationAccount */
Loading