Skip to content

Commit

Permalink
feat(localOrchAccount): Deposit, Withdraw invitationMakers
Browse files Browse the repository at this point in the history
- refs: #9193
  • Loading branch information
0xpatrickdev committed Sep 19, 2024
1 parent 5f995c2 commit c92ef27
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 5 deletions.
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
148 changes: 147 additions & 1 deletion packages/orchestration/test/examples/basic-flows.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
import type { Instance } from '@agoric/zoe/src/zoeService/utils.js';
import { E, getInterfaceOf } from '@endo/far';
import path from 'path';
import { makeIssuerKit } from '@agoric/ertp';
import {
type AmountUtils,
withAmountUtils,
} from '@agoric/zoe/tools/test-utils.js';
import { commonSetup } from '../supports.js';

const dirname = path.dirname(new URL(import.meta.url).pathname);
Expand All @@ -16,17 +21,23 @@ type StartFn =
type TestContext = Awaited<ReturnType<typeof commonSetup>> & {
zoe: ZoeService;
instance: Instance<StartFn>;
brands: Awaited<ReturnType<typeof commonSetup>>['brands'] & {
moolah: AmountUtils;
};
};

const test = anyTest as TestFn<TestContext>;

test.beforeEach(async t => {
const setupContext = await commonSetup(t);
const {
brands: { bld, ist },
bootstrap: { storage },
commonPrivateArgs,
} = setupContext;

const moolah = withAmountUtils(makeIssuerKit('MOO'));

const { zoe, bundleAndInstall } = await setUpZoeForTest();

t.log('contract coreEval', contractName);
Expand All @@ -35,7 +46,7 @@ test.beforeEach(async t => {
const storageNode = await E(storage.rootNode).makeChildNode(contractName);
const { instance } = await E(zoe).startInstance(
installation,
undefined,
{ Stable: ist.issuer, Stake: bld.issuer, Moo: moolah.issuer },
{},
{ ...commonPrivateArgs, storageNode },
);
Expand All @@ -44,6 +55,7 @@ test.beforeEach(async t => {
...setupContext,
zoe,
instance,
brands: { ...setupContext.brands, moolah },
};
});

Expand Down Expand Up @@ -88,3 +100,137 @@ const orchestrationAccountScenario = test.macro({

test(orchestrationAccountScenario, 'agoric');
test(orchestrationAccountScenario, 'cosmoshub');

test('Deposit, Withdraw - LocalOrchAccount', async t => {
const {
brands: { bld, ist },
bootstrap: { vowTools: vt },
zoe,
instance,
utils: { inspectBankBridge, pourPayment },
} = t.context;
const publicFacet = await E(zoe).getPublicFacet(instance);
const inv = E(publicFacet).makeOrchAccountInvitation();
const userSeat = E(zoe).offer(inv, {}, undefined, { chainName: 'agoric' });
const { invitationMakers } = await vt.when(E(userSeat).getOfferResult());

const twentyIST = ist.make(20n);
const tenBLD = bld.make(10n);
const Stable = await pourPayment(twentyIST);
const Stake = await pourPayment(tenBLD);

const depositInv = await E(invitationMakers).Deposit();

const depositSeat = E(zoe).offer(
depositInv,
{
give: { Stable: twentyIST, Stake: tenBLD },
want: {},
},
{ Stable, Stake },
);
const depositRes = await vt.when(E(depositSeat).getOfferResult());
t.is(depositRes, undefined, 'undefined on success');

t.deepEqual(
inspectBankBridge().slice(-2),
[
{
type: 'VBANK_GIVE',
recipient: 'agoric1fakeLCAAddress',
denom: 'uist',
amount: '20',
},
{
type: 'VBANK_GIVE',
recipient: 'agoric1fakeLCAAddress',
denom: 'ubld',
amount: '10',
},
],
'funds deposited to LCA',
);

const depositPayouts = await E(depositSeat).getPayouts();
t.is((await ist.issuer.getAmountOf(depositPayouts.Stable)).value, 0n);
t.is((await bld.issuer.getAmountOf(depositPayouts.Stake)).value, 0n);

// withdraw the payments we just deposited
const withdrawInv = await E(invitationMakers).Withdraw();
const withdrawSeat = E(zoe).offer(withdrawInv, {
give: {},
want: { Stable: twentyIST, Stake: tenBLD },
});
const withdrawRes = await vt.when(E(withdrawSeat).getOfferResult());
t.is(withdrawRes, undefined, 'undefined on success');

const withdrawPayouts = await E(withdrawSeat).getPayouts();
t.deepEqual(await ist.issuer.getAmountOf(withdrawPayouts.Stable), twentyIST);
t.deepEqual(await bld.issuer.getAmountOf(withdrawPayouts.Stake), tenBLD);
});

test('Deposit, Withdraw errors - LocalOrchAccount', async t => {
const {
brands: { ist, moolah },
bootstrap: { vowTools: vt },
zoe,
instance,
} = t.context;
const publicFacet = await E(zoe).getPublicFacet(instance);
const inv = E(publicFacet).makeOrchAccountInvitation();
const userSeat = E(zoe).offer(inv, {}, undefined, { chainName: 'agoric' });
const { invitationMakers } = await vt.when(E(userSeat).getOfferResult());

// deposit non-vbank asset (not supported)
const tenMoolah = moolah.make(10n);
const Moo = await E(moolah.mint).mintPayment(tenMoolah);
const depositInv = await E(invitationMakers).Deposit();
const depositSeat = E(zoe).offer(
depositInv,
{
give: { Moo: tenMoolah },
want: {},
},
{ Moo },
);
await t.throwsAsync(vt.when(E(depositSeat).getOfferResult()), {
message:
'One or more deposits failed ["[Error: key \\"[Alleged: MOO brand]\\" not found in collection \\"brandToAssetRecord\\"]"]',
});
const depositPayouts = await E(depositSeat).getPayouts();
t.deepEqual(
await moolah.issuer.getAmountOf(depositPayouts.Moo),
tenMoolah,
'deposit returned on failure',
);

{
// withdraw more than balance (insufficient funds)
const tenIST = ist.make(10n);
const withdrawInv = await E(invitationMakers).Withdraw();
const withdrawSeat = E(zoe).offer(withdrawInv, {
give: {},
want: { Stable: tenIST },
});
await t.throwsAsync(vt.when(E(withdrawSeat).getOfferResult()), {
message:
'One or more withdrawals failed ["[RangeError: -10 is negative]"]',
});
const payouts = await E(withdrawSeat).getPayouts();
t.deepEqual((await ist.issuer.getAmountOf(payouts.Stable)).value, 0n);
}
{
// withdraw non-vbank asset
const withdrawInv = await E(invitationMakers).Withdraw();
const withdrawSeat = E(zoe).offer(withdrawInv, {
give: {},
want: { Moo: tenMoolah },
});
await t.throwsAsync(vt.when(E(withdrawSeat).getOfferResult()), {
message:
'One or more withdrawals failed ["[Error: key \\"[Alleged: MOO brand]\\" not found in collection \\"brandToAssetRecord\\"]"]',
});
const payouts = await E(withdrawSeat).getPayouts();
t.deepEqual((await moolah.issuer.getAmountOf(payouts.Moo)).value, 0n);
}
});

0 comments on commit c92ef27

Please sign in to comment.