Skip to content

Commit

Permalink
feat(localOrchAccount): Deposit, Withdraw invitationMakers (#10117)
Browse files Browse the repository at this point in the history
refs: #9193

## Description
Adds `Deposit` and `Withdraw` invitationMakers to `LocalOrchestrationAccount`, leveraging `ZoeTools.localTransfer` and `ZoeTools.withdrawToSeat`

### Security Considerations
Involves withdrawing payments to a temporary seat, which can be risky.  Leverages `ZoeTools` which rolls back allocations in failure and partial failure scenarios.

### Scaling Considerations
n/a

### Documentation Considerations
I'm not sure how well we document the platform-provided `invitationMakers` and `.asContinuingOffer()` helper. We might consider doing so in the future.

### Testing Considerations
Includes new unit tests. Wallet Driver tests in an e2e environment are coming in a future PR after #9966 lands

### Upgrade Considerations
n/a, library code
  • Loading branch information
mergify[bot] authored Sep 20, 2024
2 parents 3e9ff43 + c92ef27 commit c04c91c
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 13 deletions.
10 changes: 5 additions & 5 deletions packages/orchestration/src/examples/send-anywhere.contract.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { makeSharedStateRecord } from '@agoric/async-flow';
import { AmountShape } from '@agoric/ertp';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { M } from '@endo/patterns';
import { withOrchestration } from '../utils/start-helper.js';
import * as flows from './send-anywhere.flows.js';
import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js';
import { AnyNatAmountShape } from '../typeGuards.js';

/**
* @import {Zone} from '@agoric/zone';
* @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js';
*/

export const SingleAmountRecord = M.and(
M.recordOf(M.string(), AmountShape, {
export const SingleNatAmountRecord = M.and(
M.recordOf(M.string(), AnyNatAmountShape, {
numPropertiesLimit: 1,
}),
M.not(harden({})),
);
harden(SingleAmountRecord);
harden(SingleNatAmountRecord);

/**
* Orchestration contract to be wrapped by withOrchestration for Zoe
Expand Down Expand Up @@ -61,7 +61,7 @@ const contract = async (
orchFns.sendIt,
'send',
undefined,
M.splitRecord({ give: SingleAmountRecord }),
M.splitRecord({ give: SingleNatAmountRecord }),
);
},
},
Expand Down
3 changes: 3 additions & 0 deletions packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { M } from '@endo/patterns';
import { makeChainHub } from '../exos/chain-hub.js';
import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js';
import fetchedChainInfo from '../fetched-chain-info.js';
import { makeZoeTools } from '../utils/zoe-tools.js';

/**
* @import {NameHub} from '@agoric/vats';
Expand Down Expand Up @@ -43,6 +44,7 @@ export const start = async (zcf, privateArgs, baggage) => {
const vowTools = prepareVowTools(zone.subZone('vows'));

const chainHub = makeChainHub(privateArgs.agoricNames, vowTools);
const zoeTools = makeZoeTools(zcf, vowTools);

const { localchain, timerService } = privateArgs;
const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit(
Expand All @@ -54,6 +56,7 @@ export const start = async (zcf, privateArgs, baggage) => {
vowTools,
chainHub,
localchain,
zoeTools,
},
);

Expand Down
85 changes: 82 additions & 3 deletions packages/orchestration/src/exos/local-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Fail, q } from '@endo/errors';

import {
AmountArgShape,
AnyNatAmountsRecord,
ChainAddressShape,
DenomAmountShape,
DenomShape,
Expand Down Expand Up @@ -39,6 +40,7 @@ import { coerceCoin, coerceDenomAmount } from '../utils/amounts.js';
* @import {Matcher} from '@endo/patterns';
* @import {ChainHub} from './chain-hub.js';
* @import {PacketTools} from './packet-tools.js';
* @import {ZoeTools} from '../utils/zoe-tools.js';
*/

const trace = makeTracer('LOA');
Expand Down Expand Up @@ -91,10 +93,19 @@ const PUBLIC_TOPICS = {
* @param {VowTools} powers.vowTools
* @param {ChainHub} powers.chainHub
* @param {Remote<LocalChain>} powers.localchain
* @param {ZoeTools} powers.zoeTools
*/
export const prepareLocalOrchestrationAccountKit = (
zone,
{ makeRecorderKit, zcf, timerService, vowTools, chainHub, localchain },
{
makeRecorderKit,
zcf,
timerService,
vowTools,
chainHub,
localchain,
zoeTools,
},
) => {
const { watch, allVows, asVow, when } = vowTools;
const { makeIBCTransferSender } = prepareIBCTools(
Expand Down Expand Up @@ -139,6 +150,12 @@ export const prepareLocalOrchestrationAccountKit = (
returnVoidWatcher: M.interface('returnVoidWatcher', {
onFulfilled: M.call(M.any()).optional(M.any()).returns(M.undefined()),
}),
seatExiterHandler: M.interface('seatExiterHandler', {
onFulfilled: M.call(M.undefined(), M.remotable()).returns(
M.undefined(),
),
onRejected: M.call(M.error(), M.remotable()).returns(M.undefined()),
}),
getBalanceWatcher: M.interface('getBalanceWatcher', {
onFulfilled: M.call(AmountShape, DenomShape).returns(DenomAmountShape),
}),
Expand All @@ -151,12 +168,14 @@ export const prepareLocalOrchestrationAccountKit = (
),
}),
invitationMakers: M.interface('invitationMakers', {
Delegate: M.call(M.string(), AmountShape).returns(M.promise()),
Undelegate: M.call(M.string(), AmountShape).returns(M.promise()),
CloseAccount: M.call().returns(M.promise()),
Delegate: M.call(M.string(), AmountShape).returns(M.promise()),
Deposit: M.call().returns(M.promise()),
Send: M.call().returns(M.promise()),
SendAll: M.call().returns(M.promise()),
Transfer: M.call().returns(M.promise()),
Undelegate: M.call(M.string(), AmountShape).returns(M.promise()),
Withdraw: M.call().returns(M.promise()),
}),
},
/**
Expand Down Expand Up @@ -200,6 +219,27 @@ export const prepareLocalOrchestrationAccountKit = (
);
}, 'Delegate');
},
Deposit() {
trace('Deposit');
return zcf.makeInvitation(
seat => {
const { give } = seat.getProposal();
return watch(
zoeTools.localTransfer(
seat,
// @ts-expect-error LocalAccount vs LocalAccountMethods
this.state.account,
give,
),
this.facets.seatExiterHandler,
seat,
);
},
'Deposit',
undefined,
M.splitRecord({ give: AnyNatAmountsRecord, want: {} }),
);
},
/**
* @param {string} validatorAddress
* @param {Amount<'nat'>} ertpAmount
Expand Down Expand Up @@ -262,6 +302,27 @@ export const prepareLocalOrchestrationAccountKit = (
};
return zcf.makeInvitation(offerHandler, 'Transfer');
},
Withdraw() {
trace('Withdraw');
return zcf.makeInvitation(
seat => {
const { want } = seat.getProposal();
return watch(
zoeTools.withdrawToSeat(
// @ts-expect-error LocalAccount vs LocalAccountMethods
this.state.account,
seat,
want,
),
this.facets.seatExiterHandler,
seat,
);
},
'Withdraw',
undefined,
M.splitRecord({ give: {}, want: AnyNatAmountsRecord }),
);
},
},
undelegateWatcher: {
/**
Expand Down Expand Up @@ -362,6 +423,24 @@ export const prepareLocalOrchestrationAccountKit = (
return harden({ denom, value: natAmount.value });
},
},
/** exits or fails a seat depending the outcome */
seatExiterHandler: {
/**
* @param {undefined} _
* @param {ZCFSeat} seat
*/
onFulfilled(_, seat) {
seat.exit();
},
/**
* @param {Error} reason
* @param {ZCFSeat} seat
*/
onRejected(reason, seat) {
seat.exit(reason);
throw reason;
},
},
/**
* handles a QueryBalanceRequest from localchain.query and returns the
* balance as a DenomAmount
Expand Down
10 changes: 10 additions & 0 deletions packages/orchestration/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,13 @@ export const TxBodyOptsShape = M.splitRecord(
nonCriticalExtensionOptions: M.arrayOf(M.any()),
},
);

/**
* Ensures at least one {@link AmountKeywordRecord} entry is present and only
* permits Nat (fungible) amounts.
*/
export const AnyNatAmountsRecord = M.and(
M.recordOf(M.string(), AnyNatAmountShape),
M.not(harden({})),
);
harden(AnyNatAmountsRecord);
10 changes: 9 additions & 1 deletion packages/orchestration/src/utils/start-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,15 @@ export const provideOrchestration = (
const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);
const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit(
zones.orchestration,
{ makeRecorderKit, zcf, timerService, vowTools, chainHub, localchain },
{
makeRecorderKit,
zcf,
timerService,
vowTools,
chainHub,
localchain,
zoeTools,
},
);

const asyncFlowTools = prepareAsyncFlowTools(zones.asyncFlow, {
Expand Down
Loading

0 comments on commit c04c91c

Please sign in to comment.