Skip to content

Commit

Permalink
feat: generic chain with sendAnywhere demo contract (#9460)
Browse files Browse the repository at this point in the history
closes #9187

## Description

DRAFT until

- [x] what to do about the way `transfer()` maps chainId to
transferChannel?
 - [x] clean up git history
 - [x] update other facade callers
- [x] integrate more fully with well-known chains: hot-new-chain gets
added and "Nothing breaks."
- [x] what to do with facade contract upgrade test, now that chainInfos
is a caller thing?

### 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

<!-- What aspects of this PR are relevant to upgrading live production
systems, and how should they be addressed? -->
  • Loading branch information
mergify[bot] committed Jun 10, 2024
2 parents 721b875 + 8d90251 commit 96c19f5
Show file tree
Hide file tree
Showing 17 changed files with 713 additions and 174 deletions.
24 changes: 8 additions & 16 deletions packages/orchestration/src/chain-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

/** @file temporary static lookup of chain info */

import { E } from '@endo/far';
import { registerChain } from './utils/chainHub.js';

/**
* @import {CosmosChainInfo, EthChainInfo} from './types.js';
*/
/** @import {CosmosChainInfo, EthChainInfo} from './types.js'; */

/** @typedef {CosmosChainInfo | EthChainInfo} ChainInfo */

Expand Down Expand Up @@ -83,7 +81,7 @@ export const wellKnownChainInfo =
stakingTokens: [{ denom: 'ustride' }],
},
cosmos: {
chainId: 'cosmoshub-4',
chainId: 'cosmoslocal',
connections: {},
icaEnabled: true,
icqEnabled: true,
Expand All @@ -101,7 +99,7 @@ export const wellKnownChainInfo =
stakingTokens: [{ denom: 'utia' }],
},
osmosis: {
chainId: 'osmosis-1',
chainId: 'osmosislocal',
connections: {},
icaEnabled: true,
icqEnabled: true,
Expand All @@ -117,14 +115,8 @@ export const wellKnownChainInfo =
* @param {(...messages: string[]) => void} log
*/
export const registerChainNamespace = async (agoricNamesAdmin, log) => {
const { nameAdmin } = await E(agoricNamesAdmin).provideChild('chain');

const registrationPromises = Object.entries(wellKnownChainInfo).map(
async ([name, info]) => {
log(`registering chain ${name}`);
return E(nameAdmin).update(name, info);
},
);

await Promise.all(registrationPromises);
for await (const [name, info] of Object.entries(wellKnownChainInfo)) {
log(`registering agoricNames chain.${name}`);
await registerChain(agoricNamesAdmin, name, info);
}
};
168 changes: 168 additions & 0 deletions packages/orchestration/src/examples/sendAnywhere.contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { makeDurableZone } from '@agoric/zone/durable.js';
import { M, mustMatch } from '@endo/patterns';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { E } from '@endo/far';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';

import { AmountShape } from '@agoric/ertp';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { CosmosChainInfoShape } from '../typeGuards.js';
import { makeOrchestrationFacade } from '../facade.js';
import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js';
import { makeChainHub } from '../utils/chainHub.js';

const { entries } = Object;
const { Fail } = assert;

/**
* @import {Baggage} from '@agoric/vat-data';
* @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api';
* @import {TimerService, TimerBrand} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {OrchestrationService} from '../service.js';
* @import {NameHub} from '@agoric/vats';
* @import {Remote} from '@agoric/vow';
*/

/**
* @typedef {{
* localchain: Remote<LocalChain>;
* orchestrationService: Remote<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* timerService: Remote<TimerService>;
* agoricNames: Remote<NameHub>;
* }} OrchestrationPowers
*/

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

/**
* @param {ZCF} zcf
* @param {OrchestrationPowers & {
* marshaller: Marshaller;
* }} privateArgs
* @param {Baggage} baggage
*/
export const start = async (zcf, privateArgs, baggage) => {
const zone = makeDurableZone(baggage);

const chainHub = makeChainHub(privateArgs.agoricNames);

// TODO once durability is settled, provide some helpers to reduce boilerplate
const { marshaller, ...orchPowers } = privateArgs;
const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);
const makeLocalChainAccountKit = prepareLocalChainAccountKit(
zone,
makeRecorderKit,
zcf,
privateArgs.timerService,
chainHub,
);
const { orchestrate } = makeOrchestrationFacade({
zcf,
zone,
chainHub,
makeLocalChainAccountKit,
...orchPowers,
});

let contractAccount;

const findBrandInVBank = async brand => {
const assets = await E(
E(privateArgs.agoricNames).lookup('vbankAsset'),
).values();
const it = assets.find(a => a.brand === brand);
it || Fail`brand ${brand} not in agoricNames.vbankAsset`;
return it;
};

/** @type {OfferHandler} */
const sendIt = orchestrate(
'sendIt',
{ zcf },
// eslint-disable-next-line no-shadow -- this `zcf` is enclosed in a membrane
async (orch, { zcf }, seat, offerArgs) => {
mustMatch(
offerArgs,
harden({ chainName: M.scalar(), destAddr: M.string() }),
);
const { chainName, destAddr } = offerArgs;
const { give } = seat.getProposal();
const [[kw, amt]] = entries(give);
const { denom } = await findBrandInVBank(amt.brand);
const chain = await orch.getChain(chainName);

// XXX ok to use a heap var crossing the membrane scope this way?
if (!contractAccount) {
const agoricChain = await orch.getChain('agoric');
contractAccount = await agoricChain.makeAccount();
}

const info = await chain.getChainInfo();
const { chainId } = info;
const { [kw]: pmtP } = await withdrawFromSeat(zcf, seat, give);
await E.when(pmtP, pmt => contractAccount.deposit(pmt, amt));
await contractAccount.transfer(
{ denom, value: amt.value },
{
address: destAddr,
addressEncoding: 'bech32',
chainId,
},
);
},
);

const publicFacet = zone.exo(
'Send PF',
M.interface('Send PF', {
makeSendInvitation: M.callWhen().returns(InvitationShape),
}),
{
makeSendInvitation() {
return zcf.makeInvitation(
sendIt,
'send',
undefined,
M.splitRecord({ give: SingleAmountRecord }),
);
},
},
);

let nonce = 0n;
const ConnectionInfoShape = M.record(); // TODO
const creatorFacet = zone.exo(
'Send CF',
M.interface('Send CF', {
addChain: M.callWhen(CosmosChainInfoShape, ConnectionInfoShape).returns(
M.scalar(),
),
}),
{
/**
* @param {CosmosChainInfo} chainInfo
* @param {IBCConnectionInfo} connectionInfo
*/
async addChain(chainInfo, connectionInfo) {
const chainKey = `${chainInfo.chainId}-${(nonce += 1n)}`;
const agoricChainInfo = await chainHub.getChainInfo('agoric');
chainHub.registerChain(chainKey, chainInfo);
chainHub.registerConnection(
agoricChainInfo.chainId,
chainInfo.chainId,
connectionInfo,
);
return chainKey;
},
},
);

return { publicFacet, creatorFacet };
};
14 changes: 3 additions & 11 deletions packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { E } from '@endo/far';
import { deeplyFulfilled } from '@endo/marshal';
import { M } from '@endo/patterns';
import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js';
import { makeChainHub } from '../utils/chainHub.js';

/**
* @import {NameHub} from '@agoric/vats';
* @import {Remote} from '@agoric/internal';
* @import {TimerBrand, TimerService} from '@agoric/time';
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
*/

Expand All @@ -28,7 +29,6 @@ const trace = makeTracer('StakeBld');
* marshaller: Marshaller;
* storageNode: StorageNode;
* timerService: TimerService;
* timerBrand: TimerBrand;
* }} privateArgs
* @param {import('@agoric/vat-data').Baggage} baggage
*/
Expand All @@ -45,20 +45,12 @@ export const start = async (zcf, privateArgs, baggage) => {
privateArgs.marshaller,
);

// FIXME in a second incarnation we can't make a remote call before defining all kinds
// UNTIL https://github.com/Agoric/agoric-sdk/issues/8879
const agoricChainInfo = await E(privateArgs.agoricNames).lookup(
'chain',
'agoric',
);

const makeLocalChainAccountKit = prepareLocalChainAccountKit(
zone,
makeRecorderKit,
zcf,
privateArgs.timerService,
privateArgs.timerBrand,
agoricChainInfo,
makeChainHub(privateArgs.agoricNames),
);

async function makeLocalAccountKit() {
Expand Down
19 changes: 18 additions & 1 deletion packages/orchestration/src/examples/swapExample.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { makeDurableZone } from '@agoric/zone/durable.js';
import { Far } from '@endo/far';
import { deeplyFulfilled } from '@endo/marshal';
import { M, objectMap } from '@endo/patterns';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { makeOrchestrationFacade } from '../facade.js';
import { orcUtils } from '../utils/orc.js';
import { makeChainHub } from '../utils/chainHub.js';
import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js';

/**
* @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js'
Expand All @@ -25,6 +28,7 @@ export const meta = {
localchain: M.remotable('localchain'),
orchestrationService: M.or(M.remotable('orchestration'), null),
storageNode: StorageNodeShape,
marshaller: M.remotable('marshaller'),
timerService: M.or(TimerServiceShape, null),
},
upgradability: 'canUpgrade',
Expand All @@ -48,6 +52,7 @@ export const makeNatAmountShape = (brand, min) =>
* orchestrationService: Remote<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* timerService: Remote<TimerService>;
* marshaller: Marshaller;
* }} privateArgs
* @param {Baggage} baggage
*/
Expand All @@ -62,16 +67,28 @@ export const start = async (zcf, privateArgs, baggage) => {
orchestrationService,
storageNode,
timerService,
marshaller,
} = privateArgs;

const chainHub = makeChainHub(agoricNames);
const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);
const makeLocalChainAccountKit = prepareLocalChainAccountKit(
zone,
makeRecorderKit,
zcf,
timerService,
chainHub,
);

const { orchestrate } = makeOrchestrationFacade({
agoricNames,
localchain,
orchestrationService,
storageNode,
timerService,
zcf,
zone,
chainHub,
makeLocalChainAccountKit,
});

/** deprecated historical example */
Expand Down
17 changes: 16 additions & 1 deletion packages/orchestration/src/examples/unbondExample.contract.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { makeDurableZone } from '@agoric/zone/durable.js';
import { Far } from '@endo/far';
import { M } from '@endo/patterns';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { makeOrchestrationFacade } from '../facade.js';
import { makeChainHub } from '../utils/chainHub.js';
import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js';

/**
* @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js'
Expand All @@ -20,6 +23,7 @@ import { makeOrchestrationFacade } from '../facade.js';
* localchain: Remote<LocalChain>;
* orchestrationService: Remote<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* marshaller: Marshaller;
* timerService: Remote<TimerService>;
* }} privateArgs
* @param {Baggage} baggage
Expand All @@ -30,18 +34,29 @@ export const start = async (zcf, privateArgs, baggage) => {
localchain,
orchestrationService,
storageNode,
marshaller,
timerService,
} = privateArgs;
const zone = makeDurableZone(baggage);

const chainHub = makeChainHub(agoricNames);
const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);
const makeLocalChainAccountKit = prepareLocalChainAccountKit(
zone,
makeRecorderKit,
zcf,
privateArgs.timerService,
chainHub,
);
const { orchestrate } = makeOrchestrationFacade({
agoricNames,
localchain,
orchestrationService,
storageNode,
timerService,
zcf,
zone,
chainHub: makeChainHub(agoricNames),
makeLocalChainAccountKit,
});

/** @type {OfferHandler} */
Expand Down
Loading

0 comments on commit 96c19f5

Please sign in to comment.