Skip to content

Commit

Permalink
basic tests of example Orchestration contracts (#9390)
Browse files Browse the repository at this point in the history
closes: #9207 

## Description

While working on #9376 I needed
some tests to check how the pieces fit together. None of them actually
make progress on #9063 that that branch points to so I'm submitting them
under this other PR.

The goal of this PR is just to provide unit tests and some cleanup of
the app code. It still has a lot of fake stuff.

### Security Considerations

<!-- Does this change introduce new assumptions or dependencies that, if
violated, could introduce security vulnerabilities? How does this PR
change the boundaries between mutually-suspicious components? What new
authorities are introduced by this change, perhaps by new API calls?
-->

### Scaling Considerations

<!-- Does this change require or encourage significant increase in
consumption of CPU cycles, RAM, on-chain storage, message exchanges, or
other scarce resources? If so, can that be prevented or mitigated? -->

### Documentation Considerations

<!-- Give our docs folks some hints about what needs to be described to
downstream users.

Backwards compatibility: what happens to existing data or deployments
when this code is shipped? Do we need to instruct users to do something
to upgrade their saved data? If there is no upgrade path possible, how
bad will that be for users?

-->

### Testing Considerations

<!-- Every PR should of course come with tests of its own functionality.
What additional tests are still needed beyond those unit tests? How does
this affect CI, other test automation, or the testnet?
-->

### Upgrade Considerations

never yet deployed
  • Loading branch information
mergify[bot] authored May 21, 2024
2 parents 980ff41 + 92e2392 commit 8343a0c
Show file tree
Hide file tree
Showing 7 changed files with 424 additions and 30 deletions.
59 changes: 42 additions & 17 deletions packages/orchestration/src/examples/swapExample.contract.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,63 @@
import { Fail } from '@agoric/assert';
import { AmountMath, AmountShape } from '@agoric/ertp';
import { E, Far } from '@endo/far';
import { M } from '@endo/patterns';
import { StorageNodeShape } from '@agoric/internal';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { Far } from '@endo/far';
import { deeplyFulfilled } from '@endo/marshal';
import { M, objectMap } from '@endo/patterns';
import { makeOrchestrationFacade } from '../facade.js';
import { orcUtils } from '../utils/orc.js';

/**
* @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js'
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {ERef} from '@endo/far'
* @import {OrchestrationService} from '../service.js';
* @import {Zone} from '@agoric/zone';
*/

/** @type {ContractMeta} */
export const meta = {
privateArgsShape: {
localchain: M.any(),
orchestrationService: M.any(),
storageNode: StorageNodeShape,
timerService: M.any(),
zone: M.any(),
},
upgradability: 'canUpgrade',
};
harden(meta);

// XXX copied from inter-protocol
// TODO move to new `@agoric/contracts` package when we have it
/**
* @param {Brand} brand must be a 'nat' brand, not checked
* @param {NatValue} [min]
*/
export const makeNatAmountShape = (brand, min) =>
harden({ brand, value: min ? M.gte(min) : M.nat() });

/**
* @param {ZCF} zcf
* @param {{
* localchain: ERef<LocalChain>;
* orchestrationService: ERef<OrchestrationService>;
* storageNode: ERef<StorageNode>;
* timerService: ERef<TimerService>;
* zone: Zone;
* }} privateArgs
*/
export const start = async (zcf, privateArgs) => {
const { orchestrationService, storageNode, timerService, zone } = privateArgs;
const { brands } = zcf.getTerms();

const { localchain, orchestrationService, storageNode, timerService, zone } =
privateArgs;

const { orchestrate } = makeOrchestrationFacade({
zone,
timerService,
zcf,
localchain,
storageNode,
orchestrationService,
});
Expand All @@ -41,7 +70,6 @@ export const start = async (zcf, privateArgs) => {
// eslint-disable-next-line no-shadow -- this `zcf` is enclosed in a membrane
async (/** @type {Orchestrator} */ orch, { zcf }, seat, offerArgs) => {
const { give } = seat.getProposal();
!AmountMath.isEmpty(give.USDC.value) || Fail`Must provide USDC.`;

const celestia = await orch.getChain('celestia');
const agoric = await orch.getChain('agoric');
Expand All @@ -51,33 +79,30 @@ export const start = async (zcf, privateArgs) => {
agoric.makeAccount(),
]);

const tiaAddress = await celestiaAccount.getAddress();
const tiaAddress = celestiaAccount.getAddress();

// deposit funds from user seat to LocalChainAccount
const seatKit = zcf.makeEmptySeatKit();
zcf.atomicRearrange(harden([[seat, seatKit.zcfSeat, give]]));
// seat.exit() // exit user seat now, or later?
const payment = await E(seatKit.userSeat).getPayout('USDC');
await localAccount.deposit(payment);
const payments = await withdrawFromSeat(zcf, seat, give);
await deeplyFulfilled(objectMap(payments, localAccount.deposit));

// build swap instructions with orcUtils library
const transferMsg = orcUtils.makeOsmosisSwap({
destChain: 'celestia',
destAddress: tiaAddress,
amountIn: give.USDC,
brandOut: offerArgs.staked.brand,
amountIn: give.Stable,
brandOut: /** @type {any} */ ('FIXME'),
slippage: 0.03,
});

await localAccount
.transferSteps(give.USDC, transferMsg)
.transferSteps(give.Stable, transferMsg)
.then(_txResult =>
celestiaAccount.delegate(offerArgs.validator, offerArgs.staked),
)
.catch(e => console.error(e));

// XXX close localAccount?
return celestiaAccount; // should be continuing inv since this is an offer?
// return continuing inv since this is an offer?
},
);

Expand All @@ -87,7 +112,7 @@ export const start = async (zcf, privateArgs) => {
'Swap for TIA and stake',
undefined,
harden({
give: { USDC: AmountShape },
give: { Stable: makeNatAmountShape(brands.Stable, 1n) },
want: {}, // XXX ChainAccount Ownable?
exit: M.any(),
}),
Expand Down
16 changes: 8 additions & 8 deletions packages/orchestration/src/examples/unbondExample.contract.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Fail } from '@agoric/assert';
import { AmountMath, AmountShape } from '@agoric/ertp';
import { Far } from '@endo/far';
import { M } from '@endo/patterns';
import { makeOrchestrationFacade } from '../facade.js';

/**
* @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js'
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {ERef} from '@endo/far'
* @import {OrchestrationService} from '../service.js';
* @import {Zone} from '@agoric/zone';
Expand All @@ -15,16 +14,19 @@ import { makeOrchestrationFacade } from '../facade.js';
/**
* @param {ZCF} zcf
* @param {{
* localchain: ERef<LocalChain>;
* orchestrationService: ERef<OrchestrationService>;
* storageNode: ERef<StorageNode>;
* timerService: ERef<TimerService>;
* zone: Zone;
* }} privateArgs
*/
export const start = async (zcf, privateArgs) => {
const { orchestrationService, storageNode, timerService, zone } = privateArgs;
const { localchain, orchestrationService, storageNode, timerService, zone } =
privateArgs;

const { orchestrate } = makeOrchestrationFacade({
localchain,
zone,
timerService,
zcf,
Expand All @@ -37,11 +39,8 @@ export const start = async (zcf, privateArgs) => {
'LSTTia',
{ zcf },
// eslint-disable-next-line no-shadow -- this `zcf` is enclosed in a membrane
async (/** @type {Orchestrator} */ orch, { zcf }, seat, _offerArgs) => {
async (/** @type {Orchestrator} */ orch, { zcf }, _seat, _offerArgs) => {
console.log('zcf within the membrane', zcf);
const { give } = seat.getProposal();
!AmountMath.isEmpty(give.USDC.value) || Fail`Must provide USDC.`;

// We would actually alreaady have the account from the orchestrator
// ??? could these be passed in? It would reduce the size of this handler,
// keeping it focused on long-running operations.
Expand Down Expand Up @@ -70,7 +69,8 @@ export const start = async (zcf, privateArgs) => {
'Unbond and liquid stake',
undefined,
harden({
give: { USDC: AmountShape },
// Nothing to give; the funds come from undelegating
give: {},
want: {}, // XXX ChainAccount Ownable?
exit: M.any(),
}),
Expand Down
147 changes: 143 additions & 4 deletions packages/orchestration/src/facade.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,145 @@
/** @file Orchestration service */

import { E } from '@endo/far';

/**
* @import {Zone} from '@agoric/zone';
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {ERef} from '@endo/far';
* @import {OrchestrationService} from './service.js';
* @import {Orchestrator} from './types.js';
* @import {Chain, ChainInfo, OrchestrationAccount, Orchestrator} from './types.js';
*/

/** @type {any} */
const anyVal = null;

/**
* @param {ERef<LocalChain>} localchain
* @returns {Chain}
*/
const makeLocalChainFacade = localchain => {
return {
/** @returns {Promise<ChainInfo>} */
async getChainInfo() {
return {
allegedName: 'agoric',
allowedMessages: [],
allowedQueries: [],
chainId: 'agoric-3',
ibcConnectionInfo: anyVal,
ibcHooksEnabled: true,
icaEnabled: true,
icqEnabled: true,
pfmEnabled: true,
};
},
async makeAccount() {
const account = await E(localchain).makeAccount();

return {
deposit(payment) {
console.log('deposit got', payment);
// XXX yet again tripped up on remote methods looking local statically
return E(account).deposit(payment);
},
transferSteps(amount, msg) {
console.log('transferSteps got', amount, msg);
return Promise.resolve();
},
};
},
};
};

/**
* @template {string} C
* @param {C} name
* @returns {Chain}
*/
const makeRemoteChainFacade = name => {
const chainInfo = {
allegedName: name,
chainId: 'fixme',
};
return {
/** @returns {Promise<ChainInfo>} */
getChainInfo: async () => anyVal,
/** @returns {Promise<OrchestrationAccount<C>>} */
makeAccount: async () => {
console.log('makeAccount for', name);
// @ts-expect-error fake yet
return {
delegate(validator, amount) {
console.log('delegate got', validator, amount);
return Promise.resolve();
},
deposit(payment) {
console.log('deposit got', payment);
return Promise.resolve();
},
getAddress() {
return {
chainId: chainInfo.chainId,
address: 'an address!',
addressEncoding: 'bech32',
};
},
getBalance(_denom) {
console.error('getBalance not yet implemented');
return Promise.resolve({ denom: 'fixme', value: 0n });
},
getBalances() {
throw new Error('not yet implemented');
},
getDelegations() {
console.error('getDelegations not yet implemented');
return [];
},
getRedelegations() {
throw new Error('not yet implemented');
},
getUnbondingDelegations() {
throw new Error('not yet implemented');
},
liquidStake() {
console.error('liquidStake not yet implemented');
return 0n;
},
send(toAccount, amount) {
console.log('send got', toAccount, amount);
return Promise.resolve();
},
transfer(amount, destination, memo) {
console.log('transfer got', amount, destination, memo);
return Promise.resolve();
},
transferSteps(amount, msg) {
console.log('transferSteps got', amount, msg);
return Promise.resolve();
},
undelegate(validator, amount) {
console.log('undelegate got', validator, amount);
return Promise.resolve();
},
withdraw(amount) {
console.log('withdraw got', amount);
return Promise.resolve();
},
};
},
};
};

/**
*
* @param {{
* zone: Zone;
* timerService: ERef<TimerService>;
* zcf: ERef<ZCF>;
* zcf: ZCF;
* storageNode: ERef<StorageNode>;
* orchestrationService: ERef<OrchestrationService>;
* localchain: ERef<LocalChain>;
* }} powers
*/
export const makeOrchestrationFacade = ({
Expand All @@ -23,6 +148,7 @@ export const makeOrchestrationFacade = ({
zcf,
storageNode,
orchestrationService,
localchain,
}) => {
console.log('makeOrchestrationFacade got', {
zone,
Expand All @@ -42,8 +168,21 @@ export const makeOrchestrationFacade = ({
* @returns {(...args: Args) => Promise<unknown>}
*/
orchestrate(durableName, ctx, fn) {
console.log('orchestrate got', durableName, ctx, fn);
throw new Error('Not yet implemented');
/** @type {Orchestrator} */
const orc = {
async getChain(name) {
if (name === 'agoric') {
return makeLocalChainFacade(localchain);
}
return makeRemoteChainFacade(name);
},
makeLocalAccount() {
return E(localchain).makeAccount();
},
getBrandInfo: anyVal,
asAmount: anyVal,
};
return async (...args) => fn(orc, ctx, ...args);
},
};
};
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/utils/orc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import {AfterAction, SwapMaxSlippage, TransferMsg} from '../types.js' */
/** @import {AfterAction, SwapExact, SwapMaxSlippage, TransferMsg} from '../types.js' */

export const orcUtils = {
/**
Expand Down
Loading

0 comments on commit 8343a0c

Please sign in to comment.