From d56616a54518b1dfd2fc0fc048c859ffbfe4f60f Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 31 Jul 2024 23:01:06 -0400 Subject: [PATCH] feat: continuing inv as flows (wip) - rewrites some logic in restake.kit.js as flows - uses mocked PersistentState to represent a non-existent exo for sharing state across flows - this exo will also need to accept flows at instantiation and return them as part of a ContinuingOfferResult --- .../src/examples/restake.flows.js | 126 +++++++++++++++++- .../orchestration/src/examples/restake.kit.js | 4 +- 2 files changed, 127 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/examples/restake.flows.js b/packages/orchestration/src/examples/restake.flows.js index 44d9be027bbc..e25d2ab649f6 100644 --- a/packages/orchestration/src/examples/restake.flows.js +++ b/packages/orchestration/src/examples/restake.flows.js @@ -2,11 +2,15 @@ * @file Example contract that allows users to do different things with rewards */ import { M, mustMatch } from '@endo/patterns'; +import { Fail } from '@endo/errors'; +import { ChainAddressShape } from '../typeGuards.js'; +import { RepeaterOptsShape } from './restake.kit.js'; /** - * @import {OrchestrationAccount, OrchestrationFlow, Orchestrator, StakingAccountActions} from '@agoric/orchestration'; - * @import {MakeRestakeHolderKit} from './restake.kit.js'; + * @import {CosmosValidatorAddress, OrchestrationAccount, OrchestrationFlow, Orchestrator, StakingAccountActions} from '@agoric/orchestration'; + * @import {MakeRestakeHolderKit, MakeRestakeWaker, RestakeParams, RepeaterOpts} from './restake.kit.js'; * @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js'; + * @import {TimerRepeater, TimestampRecord, TimerService} from '@agoric/time'; */ /** @@ -51,3 +55,121 @@ export const makeRestakeAccount = async ( publicSubscribers, }); }; +harden(makeRestakeAccount); + +/** + * Placeholder for an (per continuing) Exo that's part of ctx. + * + * Since some values are not available during initialization, we need some sort + * of pattern for setting and getting state. + * + * XXX consider a more simple `getState/setState` + * + * XXX for this exo, need a mechanism to invoke asyncFlows for Restake and + * CancelRestake + * + * @typedef {{ + * getAccount: () => Promise< + * OrchestrationAccount & StakingAccountActions + * >; + * setAccount: ( + * account: OrchestrationAccount & StakingAccountActions, + * ) => Promise; + * getRepeater: () => Promise; + * setRepeater: (repeater: TimerRepeater) => Promise; + * getValidator: () => Promise; + * setValidator: (validator: CosmosValidatorAddress) => Promise; + * }} PersistentState + */ + +/** + * NOT CURRENTLY USED + * + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} _orch + * @param {{ + * makeRestakeWaker: MakeRestakeWaker; + * timerService: TimerService; + * opts: RestakeParams; + * state: PersistentState; + * }} ctx + * @param {ZCFSeat} seat + * @param {{ validator: CosmosValidatorAddress; opts: RepeaterOpts }} offerArgs + */ +export const makeRestakeHandler = async ( + _orch, + { + state, + makeRestakeWaker, + timerService, + opts: { minimumDelay, minimumInterval }, + }, + seat, + { validator, opts }, +) => { + seat.exit(); // no funds exchanged + mustMatch(validator, ChainAddressShape, 'invalid validator address'); + mustMatch(opts, RepeaterOptsShape, 'invalid repeater options'); + + const { delay, interval } = opts; + delay >= minimumDelay || Fail`delay must be at least ${minimumDelay}`; + interval >= minimumInterval || + Fail`interval must be at least ${minimumInterval}`; + + // XXX make stateKit real + const activeRepeater = await state.getRepeater(); + + // only one repeater at a time + // XXX consider logic to allow one per validator + if (activeRepeater) { + await activeRepeater.disable(); + } + // XXX make stateKit real + const orchAccount = await state.getAccount(); + // XXX can we put the waker logic in an async-flow? And have something that turns this into TimerWaker exo + const restakeWaker = makeRestakeWaker(orchAccount, validator); + const repeater = await timerService.makeRepeater(delay, interval); + await repeater.schedule(restakeWaker); + return state.setRepeater(repeater); +}; +harden(makeRestakeHandler); + +/** + * NOT CURRENTLY USED + * + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} _orch + * @param {{ state: PersistentState }} ctx + * @param {ZCFSeat} seat + */ +export const makeCancelRestakeHandler = async (_orch, { state }, seat) => { + seat.exit(); // no funds exchanged + // XXX make stateKit real + const repeater = await state.getRepeater(); + repeater || Fail`No active restake to cancel.`; + return repeater.disable(); +}; +harden(makeCancelRestakeHandler); + +/** + * NOT CURRENTLY USED + * + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} _orch + * @param {{ state: PersistentState }} ctx + * @param {TimestampRecord} timestampRecord + */ +export const makeWakerHandler = async (_orch, { state }, timestampRecord) => { + console.log('Wake Received', timestampRecord); + const orchAccount = await state.getAccount(); + const validator = await state.getValidator(); + + const amounts = await orchAccount.withdrawReward(validator); + if (amounts.length !== 1) { + throw Fail`Received ${amounts.length} amounts, only expected one.`; + } + if (!amounts[0].value) return; + + return orchAccount.delegate(validator, amounts[0]); +}; +harden(makeWakerHandler); diff --git a/packages/orchestration/src/examples/restake.kit.js b/packages/orchestration/src/examples/restake.kit.js index 3b75707335d9..f4f9799fbe0a 100644 --- a/packages/orchestration/src/examples/restake.kit.js +++ b/packages/orchestration/src/examples/restake.kit.js @@ -94,6 +94,8 @@ export const prepareRestakeWaker = (zone, vowTools) => { return (...args) => makeKit(...args).waker; }; +/** @typedef {ReturnType} MakeRestakeWaker */ + /** * @typedef {{ * orchAccount: OrchestrationAccount & StakingAccountActions; @@ -113,7 +115,7 @@ const RepeaterStateShape = { * }} RepeaterOpts */ -const RepeaterOptsShape = { +export const RepeaterOptsShape = { delay: M.nat(), interval: M.nat(), };