Skip to content

Commit

Permalink
feat: auto-stake-it-holder (WIP)
Browse files Browse the repository at this point in the history
- extends portfolio holder kit with additional invitation makers for a specific contract
- uses prepareGuardedAttenuator to make a mixin
  • Loading branch information
0xpatrickdev committed Jul 17, 2024
1 parent 15e50f0 commit 7e886b4
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 27 deletions.
134 changes: 134 additions & 0 deletions packages/orchestration/src/examples/auto-stake-it-holder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { E } from '@endo/far';
import { getInterfaceGuardPayload, M } from '@endo/patterns';
import {
makeSyncMethodCallback,
prepareGuardedAttenuator,
} from '@agoric/internal/src/callback.js';
import { EmptyProposalShape } from '@agoric/zoe/src/typeGuards.js';
import { pickFacet } from '@agoric/vat-data';
import { PortfolioHolderKitI } from '../exos/portfolio-holder-kit.js';
import { ChainAddressShape } from '../typeGuards.js';

/**
* @import {Zone} from '@agoric/zone';
* @import {MakeAttenuator} from '@agoric/internal/src/callback.js';
* @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
* @import {CosmosValidatorAddress} from '@agoric/orchestration';
* @import {PortfolioHolderKit} from '../exos/portfolio-holder-kit.js';
* @import {StakingTapHolder} from './auto-stake-it-tap-kit.js';
*/

const AutoStakeInvitationMakersShape = harden({
UpdateValidator: M.call(ChainAddressShape).returns(M.promise()),
CancelAutoStake: M.call().returns(M.promise()),
});

/**
* @param {Zone} zone
* @param {ZCF} zcf
*/
export const prepareAutoStakeInvMakersFacet = (zone, zcf) =>
zone.exoClass(
'AutoStakeHolder Inv Makers',
M.interface('AutoStakeInvitationMakers', AutoStakeInvitationMakersShape),
/**
* @param {TargetRegistration} appRegistration
* @param {StakingTapHolder} tapHolder
*/
(appRegistration, tapHolder) => ({ appRegistration, tapHolder }),
{
/** @param {CosmosValidatorAddress} validator */
UpdateValidator(validator) {
return zcf.makeInvitation(
seat => {
seat.exit();
return E(this.state.tapHolder).updateValidator(validator);
},
'UpdateValidator',
undefined,
EmptyProposalShape,
);
},
CancelAutoStake() {
return zcf.makeInvitation(
seat => {
seat.exit();
return E(this.state.appRegistration).revoke();
},
'CancelAutoStake',
undefined,
EmptyProposalShape,
);
},
},
);

/** @typedef {ReturnType<typeof prepareAutoStakeInvMakersFacet>} MakeAutoStakeInvMakersFacet */

/**
* @param {Zone} zone
* @param {MakeAutoStakeInvMakersFacet} makeAutoStakeInvMakersFacet
*/
export const prepareMixinInvitationMakers = (
zone,
makeAutoStakeInvMakersFacet,
) => {
const MixinI = M.interface('Custom InvitationMakers', {
...getInterfaceGuardPayload(PortfolioHolderKitI.invitationMakers)
.methodGuards,
...AutoStakeInvitationMakersShape,
});

// XXX i think this should be `PortfolioHolderKit` with our new `invitationMakers`
/** @type {MakeAttenuator<PortfolioHolderKit>} */
const mixin = prepareGuardedAttenuator(zone, MixinI, {
tag: 'AutoStakeItInvitationMakers',
});

/**
* @param {PortfolioHolderKit} portfolioHolderKit
* @param {TargetRegistration} appRegistration
* @param {StakingTapHolder} tapHolder
*/
const mixinInvitationMakers = (
portfolioHolderKit,
appRegistration,
tapHolder,
) => {
const invitationMakers = makeAutoStakeInvMakersFacet(
appRegistration,
tapHolder,
);
return mixin({
/// XXX is this is kit, we can include in the prepare? ~~Or an actual
// instance~~
target: portfolioHolderKit,
// does not 'override' anything, but mixes in
overrides: {
UpdateValidator: makeSyncMethodCallback(
invitationMakers,
'UpdateValidator',
),
CancelAutoStake: makeSyncMethodCallback(
invitationMakers,
'CancelAutoStake',
),
},
});
};

return mixinInvitationMakers;
};

/**
* @param {Zone} zone
* @param {ZCF} zcf
*/
export const prepareAutoStakeHolder = (zone, zcf) => {
const makeAutoStakeInvMakersFacet = prepareAutoStakeInvMakersFacet(zone, zcf);
const makeMixinInvitationMakers = prepareMixinInvitationMakers(
zone,
makeAutoStakeInvMakersFacet,
);
return pickFacet(makeMixinInvitationMakers, 'holder');
};
22 changes: 20 additions & 2 deletions packages/orchestration/src/examples/auto-stake-it-tap-kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const prepareStakingTapKit = (zone, { watch }) => {
M.or(VowShape, M.undefined()),
),
}),
holder: M.interface('Holder', {
updateValidator: M.call(ChainAddressShape).returns(),
}),
transferWatcher: M.interface('TransferWatcher', {
onFulfilled: M.call(M.undefined())
.optional(M.bigint())
Expand Down Expand Up @@ -113,6 +116,15 @@ const prepareStakingTapKit = (zone, { watch }) => {
);
},
},
holder: {
/**
* @param {CosmosValidatorAddress} validator
*/
updateValidator(validator) {
mustMatch(validator, ChainAddressShape);
this.state.validator = validator;
},
},
transferWatcher: {
/**
* @param {void} _result
Expand All @@ -132,6 +144,8 @@ const prepareStakingTapKit = (zone, { watch }) => {
);
};

/** @typedef {ReturnType<ReturnType<typeof prepareStakingTapKit>>} StakingTapKit */
/** @typedef {StakingTapKit['holder']} StakingTapHolder */
/**
* Provides a {@link TargetApp} that reacts to an incoming IBC transfer by:
*
Expand All @@ -146,11 +160,15 @@ const prepareStakingTapKit = (zone, { watch }) => {
* @param {VowTools} vowTools
* @returns {(
* ...args: Parameters<ReturnType<typeof prepareStakingTapKit>>
* ) => ReturnType<ReturnType<typeof prepareStakingTapKit>>['tap']}
* ) => { holder: StakingTapKit['holder']; tap: StakingTapKit['tap'] }}
*/
export const prepareStakingTap = (zone, vowTools) => {
const makeKit = prepareStakingTapKit(zone, vowTools);
return (...args) => makeKit(...args).tap;
return (...args) => {
// XXX pickFacets?
const { tap, holder } = makeKit(...args);
return harden({ tap, holder });
};
};

/** @typedef {ReturnType<typeof prepareStakingTap>} MakeStakingTap */
Expand Down
10 changes: 8 additions & 2 deletions packages/orchestration/src/examples/auto-stake-it.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { withOrchestration } from '../utils/start-helper.js';
import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js';
import { prepareStakingTap } from './auto-stake-it-tap-kit.js';
import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js';
import {
prepareAutoStakeHolder,
prepareAutoStakeInvMakersFacet,
} from './auto-stake-it-holder.js';

/**
* @import {TimerService} from '@agoric/time';
Expand Down Expand Up @@ -90,7 +94,7 @@ const makeAccountsHandler = async (
assert(transferChannel.counterPartyChannelId, 'unable to find sourceChannel');

// Every time the `localAccount` receives `remoteDenom` over IBC, delegate it.
const tap = makeStakingTap({
const { tap, holder } = makeStakingTap({
localAccount,
stakingAccount,
validator,
Expand All @@ -102,7 +106,7 @@ const makeAccountsHandler = async (
});
// XXX consider storing appRegistration, so we can .revoke() or .updateTargetApp()
// @ts-expect-error tap.receiveUpcall: 'Vow<void> | undefined' not assignable to 'Promise<any>'
await localAccount.monitorTransfers(tap);
const appRegistration = await localAccount.monitorTransfers(tap);

const accountEntries = harden(
/** @type {[string, OrchestrationAccount<any>][]} */ ([
Expand Down Expand Up @@ -155,6 +159,8 @@ const contract = async (
vowTools,
);

const makeAutoStakeHolder = prepareAutoStakeHolder(zone, zcf);

const makeAccounts = orchestrate(
'makeAccounts',
{ makeStakingTap, makePortfolioHolder, chainHub },
Expand Down
50 changes: 27 additions & 23 deletions packages/orchestration/src/exos/portfolio-holder-kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,29 @@ const { fromEntries } = Object;
* @typedef {{
* accounts: MapStore<string, OrchestrationAccount<any>>;
* publicTopics: MapStore<string, ResolvedPublicTopic<unknown>>;
* }} PortfolioHolderState
* }} PortfolioHolderKitState
*/

const ChainNameShape = M.string();

export const PortfolioHolderKitI = harden({
invitationMakers: M.interface('InvitationMakers', {
MakeInvitation: M.call(
ChainNameShape,
M.string(),
M.arrayOf(M.any()),
).returns(M.promise()),
}),
holder: M.interface('Holder', {
asContinuingOffer: M.call().returns(VowShape),
getPublicTopics: M.call().returns(VowShape),
getAccount: M.call(ChainNameShape).returns(VowShape),
addAccount: M.call(ChainNameShape, M.remotable(), PublicTopicShape).returns(
VowShape,
),
}),
});

const AccountEntriesShape = M.arrayOf([
M.string(),
M.remotable('OrchestrationAccount'),
Expand All @@ -38,28 +56,10 @@ const PublicTopicEntriesShape = M.arrayOf([M.string(), PublicTopicShape]);
* @param {Zone} zone
* @param {VowTools} vowTools
*/
const preparePortfolioHolderKit = (zone, { asVow, when }) => {
export const preparePortfolioHolderKit = (zone, { asVow, when }) => {
return zone.exoClassKit(
'PortfolioHolderKit',
{
invitationMakers: M.interface('InvitationMakers', {
MakeInvitation: M.call(
ChainNameShape,
M.string(),
M.arrayOf(M.any()),
).returns(M.promise()),
}),
holder: M.interface('Holder', {
asContinuingOffer: M.call().returns(VowShape),
getPublicTopics: M.call().returns(VowShape),
getAccount: M.call(ChainNameShape).returns(VowShape),
addAccount: M.call(
ChainNameShape,
M.remotable(),
PublicTopicShape,
).returns(VowShape),
}),
},
PortfolioHolderKitI,
/**
* @param {Iterable<[string, OrchestrationAccount<any>]>} accountEntries
* @param {Iterable<[string, ResolvedPublicTopic<unknown>]>} publicTopicEntries
Expand All @@ -79,7 +79,7 @@ const preparePortfolioHolderKit = (zone, { asVow, when }) => {
);
accounts.addAll(accountEntries);
publicTopics.addAll(publicTopicEntries);
return /** @type {PortfolioHolderState} */ (
return /** @type {PortfolioHolderKitState} */ (
harden({
accounts,
publicTopics,
Expand Down Expand Up @@ -153,6 +153,10 @@ const preparePortfolioHolderKit = (zone, { asVow, when }) => {
);
};

/**
* @typedef {ReturnType<ReturnType<typeof preparePortfolioHolderKit>>} PortfolioHolderKit
*/

/**
* A portfolio holder stores two or more OrchestrationAccounts and combines
* ContinuingOfferResult's from each into a single result.
Expand All @@ -164,7 +168,7 @@ const preparePortfolioHolderKit = (zone, { asVow, when }) => {
* @param {VowTools} vowTools
* @returns {(
* ...args: Parameters<ReturnType<typeof preparePortfolioHolderKit>>
* ) => ReturnType<ReturnType<typeof preparePortfolioHolderKit>>['holder']}
* ) => PortfolioHolderKit['holder']}
*/
export const preparePortfolioHolder = (zone, vowTools) => {
const makeKit = preparePortfolioHolderKit(zone, vowTools);
Expand Down

0 comments on commit 7e886b4

Please sign in to comment.