Skip to content

Commit

Permalink
feat: use orchFns.autoStake as target for receiveUpcall
Browse files Browse the repository at this point in the history
- uses an async-flow function as the handler for a `.monitorTransfers()` tap
- allows guest develoeprs to write handlers as flows instead of with vows and watchers
  • Loading branch information
0xpatrickdev committed Feb 12, 2025
1 parent 5fb1074 commit 5f50d92
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 171 deletions.
154 changes: 0 additions & 154 deletions packages/orchestration/src/examples/auto-stake-it-tap-kit.js

This file was deleted.

79 changes: 71 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,20 +2,57 @@ import {
EmptyProposalShape,
InvitationShape,
} from '@agoric/zoe/src/typeGuards.js';
import { makeTracer } from '@agoric/internal';
import { M } 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 { registerChainsAndAssets } from '../utils/chain-hub-helper.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, CosmosChainInfo, DenomDetail} from '@agoric/orchestration';
* @import {Passable} from '@endo/marshal';
* @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js';
* @import {LocalOrchestrationAccount} from '../exos/local-orchestration-account.js';
* @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js';
* @import {CosmosChainInfo, Denom, DenomDetail} from '../types.js';
*/

/**
* @typedef {{
* stakingAccount: GuestInterface<CosmosOrchestrationAccount>;
* localAccount: GuestInterface<LocalOrchestrationAccount>;
* config: {
* validator: CosmosValidatorAddress;
* localChainAddress: ChainAddress;
* remoteChainAddress: ChainAddress;
* sourceChannel: IBCChannelID;
* remoteDenom: Denom;
* localDenom: Denom;
* };
* }} 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 @@ -37,16 +74,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 => harden(initialState),
{
/**
* Transfers from localAccount to stakingAccount, then delegates from the
* stakingAccount to `validator` if the expected token (remoteDenom) is
* received.
*
* @param {VTransferIBCEvent & Passable} event
*/
receiveUpcall(event) {
trace('receiveUpcall', event);
const { localAccount, stakingAccount, config } = this.state;

orchFns.autoStake(localAccount, stakingAccount, config, event);
},
},
{
stateShape: StakingTapStateShape,
},
);

const orchFns = orchestrateAll(flows, {
makeStakingTap,
makePortfolioHolder,
chainHub,
Expand All @@ -60,7 +123,7 @@ const contract = async (
{
makeAccountsInvitation() {
return zcf.makeInvitation(
makeAccounts,
orchFns.makeAccounts,
'Make Accounts',
undefined,
EmptyProposalShape,
Expand Down
81 changes: 72 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,30 @@
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 {Passable} from '@endo/marshal';
* @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 +74,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 +114,52 @@ 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 & Passable} 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(remoteChainAddress, {
denom: localDenom,
value: BigInt(tx.amount),
});

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

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

0 comments on commit 5f50d92

Please sign in to comment.