diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index 6a7571959a9..02994295e80 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -3,9 +3,8 @@ import { M } from '@endo/patterns'; /** * @import {TypedPattern} from '@agoric/internal'; - * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomDetail, DenomInfo, AmountArg, CosmosValidatorAddress} from './types.js'; + * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomInfo, AmountArg, CosmosValidatorAddress} from './types.js'; * @import {Any as Proto3Msg} from '@agoric/cosmic-proto/google/protobuf/any.js'; - * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; * @import {TypedJson} from '@agoric/cosmic-proto'; */ @@ -26,17 +25,17 @@ export const OutboundConnectionHandlerI = M.interface( ); /** @type {TypedPattern} */ -export const ChainAddressShape = { +export const ChainAddressShape = harden({ chainId: M.string(), encoding: M.string(), value: M.string(), -}; +}); /** @type {TypedPattern} */ -export const Proto3Shape = { +export const Proto3Shape = harden({ typeUrl: M.string(), value: M.string(), -}; +}); /** @internal */ export const IBCTransferOptionsShape = M.splitRecord( @@ -109,12 +108,12 @@ export const ChainInfoShape = M.splitRecord({ export const DenomShape = M.string(); /** @type {TypedPattern>} */ -export const DenomInfoShape = { +export const DenomInfoShape = harden({ chain: M.remotable('Chain'), base: M.remotable('Chain'), brand: M.or(M.remotable('Brand'), M.undefined()), baseDenom: M.string(), -}; +}); /** @type {TypedPattern} */ export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; @@ -164,7 +163,10 @@ export const chainFacadeMethods = harden({ * `seconds` is a big integer but since it goes through JSON it is encoded as * string */ -export const TimestampProtoShape = { seconds: M.string(), nanos: M.number() }; +export const TimestampProtoShape = harden({ + seconds: M.string(), + nanos: M.number(), +}); /** * see {@link TxBody} for more details diff --git a/packages/vats/package.json b/packages/vats/package.json index 96f8303d688..59e20bfd4d9 100644 --- a/packages/vats/package.json +++ b/packages/vats/package.json @@ -30,6 +30,7 @@ "@agoric/internal": "^0.3.2", "@agoric/network": "^0.1.0", "@agoric/notifier": "^0.6.2", + "@agoric/orchestration": "^0.1.0", "@agoric/store": "^0.9.2", "@agoric/swingset-vat": "^0.32.2", "@agoric/time": "^0.3.2", diff --git a/packages/vats/src/orch-purse/issuerKit.js b/packages/vats/src/orch-purse/issuerKit.js index 995a0f92c3b..64a12dd4421 100644 --- a/packages/vats/src/orch-purse/issuerKit.js +++ b/packages/vats/src/orch-purse/issuerKit.js @@ -2,7 +2,6 @@ import { assert, Fail } from '@endo/errors'; import { assertPattern } from '@endo/patterns'; -import { makeScalarBigMapStore } from '@agoric/vat-data'; import { makeDurableZone } from '@agoric/zone/durable.js'; import { AssetKind, assertAssetKind } from '@agoric/ertp'; @@ -109,7 +108,7 @@ const RECOVERY_SETS_STATE = 'recoverySetsState'; * and optional. See `RecoverySetsOption` for defaulting behavior. * @returns {IssuerKit} */ -export const upgradeIssuerKit = ( +const upgradeIssuerKit = ( issuerBaggage, optShutdownWithFailure = undefined, recoverySetsOption = undefined, @@ -143,7 +142,7 @@ harden(upgradeIssuerKit); * * @param {import('@agoric/vat-data').Baggage} baggage */ -export const hasIssuer = baggage => baggage.has(INSTANCE_KEY); +const hasIssuer = baggage => baggage.has(INSTANCE_KEY); /** * `elementShape`, may only be present for collection-style amounts. If present, @@ -195,7 +194,7 @@ export const hasIssuer = baggage => baggage.has(INSTANCE_KEY); * @param {IssuerOptionsRecord} [options] * @returns {IssuerKit} */ -export const makeDurableIssuerKit = ( +const makeDurableIssuerKit = ( issuerBaggage, name, // @ts-expect-error K could be instantiated with a different subtype of AssetKind @@ -292,55 +291,3 @@ export const prepareIssuerKit = ( } }; harden(prepareIssuerKit); - -/** - * Used _only_ to make a new issuerKit that is effectively non-durable. This is - * currently done by making a durable one in a baggage not reachable from - * anywhere. TODO Once rebuilt on zones, this should instead just build on the - * virtual zone. See https://github.com/Agoric/agoric-sdk/pull/7116 - * - * Currently used for testing only. Should probably continue to be used for - * testing only. - * - * @template {AssetKind} [K='nat'] The name becomes part of the brand in asset - * descriptions. The name is useful for debugging and double-checking - * assumptions, but should not be trusted wrt any external namespace. For - * example, anyone could create a new issuer kit with name 'BTC', but it is - * not bitcoin or even related. It is only the name according to that issuer - * and brand. - * - * The assetKind will be used to import a specific mathHelpers from the - * mathHelpers library. For example, natMathHelpers, the default, is used for - * basic fungible tokens. - * - * `displayInfo` gives information to the UI on how to display the amount. - * @param {string} name - * @param {K} [assetKind] - * @param {AdditionalDisplayInfo} [displayInfo] - * @param {ShutdownWithFailure} [optShutdownWithFailure] If this issuer fails in - * the middle of an atomic action (which btw should never happen), it - * potentially leaves its ledger in a corrupted state. If this function was - * provided, then the failed atomic action will call it, so that some larger - * unit of computation, like the enclosing vat, can be shutdown before - * anything else is corrupted by that corrupted state. See - * https://github.com/Agoric/agoric-sdk/issues/3434 - * @param {IssuerOptionsRecord} [options] - * @returns {IssuerKit} - */ -export const makeIssuerKit = ( - name, - // @ts-expect-error K could be instantiated with a different subtype of AssetKind - assetKind = AssetKind.NAT, - displayInfo = harden({}), - optShutdownWithFailure = undefined, - { elementShape = undefined, recoverySetsOption = undefined } = {}, -) => - makeDurableIssuerKit( - makeScalarBigMapStore('dropped issuer kit', { durable: true }), - name, - assetKind, - displayInfo, - optShutdownWithFailure, - { elementShape, recoverySetsOption }, - ); -harden(makeIssuerKit); diff --git a/packages/vats/src/orch-purse/mock-orch-chain.js b/packages/vats/src/orch-purse/mock-orch-chain.js index 15b9588fd07..189edd797fa 100644 --- a/packages/vats/src/orch-purse/mock-orch-chain.js +++ b/packages/vats/src/orch-purse/mock-orch-chain.js @@ -1,77 +1,131 @@ import { M } from '@endo/patterns'; +import { provideLazy } from '@agoric/store'; -import { AmountMath, AmountShape } from '@agoric/ertp'; - +import { AmountMath } from '@agoric/ertp'; +import { DenomAmountShape } from '@agoric/orchestration'; import { - MinOrchAccountAddressI, + MinChainI, + MinChainShape, + MinDenomInfoShape, MinOrchAccountI, - MinOrchChainI, + MinOrchAccountShape, + MinOrchestratorI, } from './typeGuards.js'; /** - * @import {Zone} from '@agoric/zone' - * - * @import {MinOrchAccountAddress} from './types.js' + * @import {Zone} from '@agoric/base-zone' */ +const makeMinDenomInfo = (chain, _denom) => { + const brand = {}; // TODO the whole thing + return harden({ + brand, + chain, + }); +}; + /** * @param {Zone} zone */ -export const prepareMockOrchChain = zone => { - let amp; - const makeAccountKit = zone.exoClassKit( - 'MockOrchAccount', - { - account: MinOrchAccountI, - address: MinOrchAccountAddressI, - incrFacet: M.interface('Incr', { - incr: M.call(AmountShape).returns(), - }), +export const prepareMinOrchestrator = zone => { + const makeMinOrchAccount = zone.exoClass( + 'MinOrchAccount', + MinOrchAccountI, + (chain, ledger) => { + const addrValue = `${ledger.size()}`; + return { chain, ledger, addrValue }; }, - brand => ({ - fullBalance: AmountMath.makeEmpty(brand), - }), { - account: { - async getFullBalance() { - return this.state.fullBalance; - }, - /** - * @param {MinOrchAccountAddress} dest - * @param {Amount} depositAmount - */ - async transfer(dest, depositAmount) { - const destIncrFacet = amp(dest).incrFacet; - this.state.fullBalance = AmountMath.subtract( - this.state.fullBalance, - depositAmount, - ); - destIncrFacet.incr(depositAmount); - }, - getAddress() { - return this.facets.address; - }, + getAddress() { + const { chain, addrValue: value } = this.state; + const { chainId } = chain.getChainInfo(); + return harden({ chainId, value }); }, - address: {}, - incrFacet: { - incr(amount) { - this.state.fullBalance = AmountMath.add( - this.state.fullBalance, - amount, - ); - }, + getBalances() { + const { ledger, addrValue } = this.state; + const { balances } = ledger.get(addrValue); + return [...balances.values()]; + }, + getBalance(denom) { + const { ledger, addrValue } = this.state; + const { balances } = ledger.get(addrValue); + return balances.get(denom); + }, + transfer(destAddr, denomAmount) { + }, }, + ); + + const makeMinChain = zone.exoClass( + 'MinChain', + MinChainI, + chainName => ({ + chainId: chainName, + denoms: zone.mapStore('denoms', { + keyShape: M.string(), // denom + valueShape: MinDenomInfoShape, + }), + ledger: zone.mapStore('accounts', { + keyShape: M.string(), // addrValue + valueShape: { + account: MinOrchAccountShape, + balances: M.remotable('balances'), + }, + }), + }), { - receiveAmplifier(a) { - amp = a; + getChainInfo() { + const { chainId } = this.state; + return harden({ chainId }); + }, + makeAccount() { + const { ledger } = this.state; + const { self } = this; + const account = makeMinOrchAccount(self, ledger); + const { value: addrValue } = account.getAddress(); + ledger.init( + addrValue, + harden({ + account, + balances: zone.mapStore('balances', { + keyShape: M.string(), // denom + valueShape: DenomAmountShape, + }), + }), + ); + return account; + }, + getDenomInfo(denom) { + const { denoms } = this.state; + const { self } = this; + return provideLazy(denoms, denom, d => makeMinDenomInfo(self, d)); + }, + asAmount(denomAmount) { + const { self } = this; + const { denom, value } = denomAmount; + const { brand } = self.getDenomInfo(denom); + return AmountMath.make(brand, value); }, }, ); - return zone.exo('MockOrchChain', MinOrchChainI, { - makeAccount(brand) { - return Promise.resolve(makeAccountKit(brand).account); + const makeMinOrchestrator = zone.exoClass( + 'MinOrchestrator', + MinOrchestratorI, + () => ({ + chains: zone.mapStore('chains', { + keyShape: M.string(), + valueShape: MinChainShape, + }), + }), + { + getChain(chainName) { + const { chains } = this.state; + return provideLazy(chains, chainName, makeMinChain); + }, }, - }); + ); + return makeMinOrchestrator; }; +harden(prepareMinOrchestrator); diff --git a/packages/vats/src/orch-purse/purse.js b/packages/vats/src/orch-purse/purse.js index 785720acc15..21645ac8017 100644 --- a/packages/vats/src/orch-purse/purse.js +++ b/packages/vats/src/orch-purse/purse.js @@ -7,6 +7,8 @@ import { makeTransientNotifierKit } from '@agoric/ertp/src/transientNotifier.js' /** * @import {AssetKind, RecoverySetsOption, Brand, Payment} from '@agoric/ertp' + * + * @import {MinOrchChain} from './types.js' */ const EMPTY_COPY_SET = makeCopySet([]); @@ -14,6 +16,7 @@ const EMPTY_COPY_SET = makeCopySet([]); // TODO Type InterfaceGuard better than InterfaceGuard /** * @param {import('@agoric/zone').Zone} issuerZone + * @param {MinOrchChain} orchChain * @param {string} name * @param {AssetKind} assetKind * @param {Brand} brand @@ -30,6 +33,7 @@ const EMPTY_COPY_SET = makeCopySet([]); */ export const prepareOrchPurseKind = ( issuerZone, + orchChain, name, assetKind, brand, @@ -84,13 +88,13 @@ export const prepareOrchPurseKind = ( `${name} OrchPurse`, OrchPurseIKit, () => { - const currentBalance = AmountMath.makeEmpty(brand, assetKind); + const orchAccount = orchChain.makeAccount(brand, assetKind); /** @type {SetStore} */ const recoverySet = issuerZone.detached().setStore('recovery set'); return { - currentBalance, + orchAccount, recoverySet, }; }, diff --git a/packages/vats/src/orch-purse/typeGuards.js b/packages/vats/src/orch-purse/typeGuards.js index 9f1e0033587..8ad2dc75e6e 100644 --- a/packages/vats/src/orch-purse/typeGuards.js +++ b/packages/vats/src/orch-purse/typeGuards.js @@ -1,6 +1,7 @@ // @jessie-check import { M, getInterfaceGuardPayload } from '@endo/patterns'; + import { AmountPatternShape, AmountShape, @@ -9,26 +10,72 @@ import { makeIssuerInterfaces, PaymentShape, } from '@agoric/ertp'; +import { + ChainInfoShape, + DenomShape, + DenomAmountShape, +} from '@agoric/orchestration'; + +// //////////////////////// Orchestration-like ///////////////////////////////// -export const MinOrchAccountAddressShape = M.remotable('MinOrchAccountAddress'); export const MinOrchAccountShape = M.remotable('MinOrchAccount'); -export const MinOrchChain = M.remotable('MinOrchChain'); +export const MinChainShape = M.remotable('MinChain'); +export const MinOrchestratorShape = M.remotable('MinOrchestrator'); -export const MinOrchAccountAddressI = M.interface('MinOrchAccountAddress', {}); +/** + * @see {ChainAddressShape} + * @see {MinChainAcctAddr} + */ +export const MinChainAcctAddrShape = harden({ + chainId: M.string(), + value: M.string(), +}); +/** + * @see {orchestrationAccountMethods} + * @see {MinOrchAccount} + */ export const MinOrchAccountI = M.interface('MinOrchAccount', { - getFullBalance: M.call().returns(M.eref(AmountShape)), - transfer: M.call(MinOrchAccountAddressShape, AmountShape).returns( + getAddress: M.call().returns(MinChainAcctAddrShape), + getBalances: M.call().returns(M.eref(M.arrayOf(DenomAmountShape))), + getBalance: M.call(M.string()).returns(M.eref(DenomAmountShape)), + transfer: M.call(MinChainAcctAddrShape, DenomAmountShape).returns( M.eref(M.undefined()), ), - getAddress: M.call().returns(MinOrchAccountAddressShape), }); -export const MinOrchChainI = M.interface('MinOrchChain', { - makeAccount: M.call(BrandShape).returns(M.eref(MinOrchAccountShape)), +/** + * @see {DenomInfoShape} + * @see {MinDenomInfo} + */ +export const MinDenomInfoShape = harden({ + brand: BrandShape, + chain: MinChainShape, +}); + +/** + * @see {chainFacadeMethods} + * @see {MinChain} + * @see {OrchestratorI} + */ +export const MinChainI = M.interface('MinChain', { + getChainInfo: M.call().returns(M.eref(ChainInfoShape)), + makeAccount: M.call().returns(M.eref(MinOrchAccountShape)), + + // In the real API, these are on OrchestratorI + getDenomInfo: M.call(DenomShape).returns(MinDenomInfoShape), + asAmount: M.call(DenomAmountShape).returns(AmountShape), +}); + +/** + * @see {OrchestratorI} + * @see {MinOrchestrator} + */ +export const MinOrchestratorI = M.interface('MinOrchestrator', { + getChain: M.call(M.string()).returns(M.eref(MinChainShape)), }); -// //////////////////////// Interfaces ///////////////////////////////////////// +// //////////////////////// ERTP-like ////////////////////////////////////////// /** * @param {Pattern} [brandShape] diff --git a/packages/vats/src/orch-purse/types.ts b/packages/vats/src/orch-purse/types.ts index b8a5da71449..41bb0e56627 100644 --- a/packages/vats/src/orch-purse/types.ts +++ b/packages/vats/src/orch-purse/types.ts @@ -1,24 +1,68 @@ -/* eslint-disable no-use-before-define */ import type { ERef } from '@endo/far'; -import type { RemotableObject } from '@endo/pass-style'; import type { CopySet, Key, Pattern } from '@endo/patterns'; import type { LatestTopic } from '@agoric/notifier'; -import type { AssetKind, Amount, Brand, Payment } from '@agoric/ertp'; +import type { Amount, Brand, Payment } from '@agoric/ertp'; +import type { Denom, DenomAmount, ChainAddress } from '@agoric/orchestration'; -export type MinOrchAccountAddress = {}; +// //////////////////////// Orchestration-like ///////////////////////////////// +/** + * @see {ChainAddress}, which actually names an account on a chain. + * Like a widely-held deposit-facet. + */ +export type MinChainAcctAddr = { + chainId: string; + value: string; +}; + +/** + * @see {OrchestrationAccount} + */ export type MinOrchAccount = { - getFullBalance(): ERef; - transfer(dest: MinOrchAccountAddress, depositAmount: Amount): ERef; - getAddress(): MinOrchAccountAddress; + getAddress(): MinChainAcctAddr; + getBalances(): ERef; + getBalance(denom: Denom): ERef; + transfer(destAddr: MinChainAcctAddr, denomAmount: DenomAmount): ERef; +}; + +/** + * @see {ChainInfo}, which is in the intersection of the existing + * `ChainInfo` possibilities. + */ +export type MinChainInfo = { + chainId: string; +}; + +/** + * @see {DenomInfo} + */ +export type MinDenomInfo = { + brand: Brand; + // eslint-disable-next-line no-use-before-define + chain: MinChain; +}; + +/** + * @see {Chain} + */ +export type MinChain = { + getChainInfo(): ERef; + makeAccount(): ERef; + + // In the real API, these are on Orchestrator + getDenomInfo(denom: Denom): MinDenomInfo; + asAmount(denomAmount: DenomAmount): Amount; }; -export type MinOrchChain = { - makeAccount(brand: Brand): ERef; +/** + * @see {Orchestrator} + */ +export type MinOrchestrator = { + getChain(chainName: string): ERef; }; -// //////////////////////////////////////////////////////////////////////////// +// //////////////////////// ERTP-like ////////////////////////////////////////// export type OrchDepositFacetReceive = ( payment: Payment, @@ -44,44 +88,30 @@ export type OrchDepositFacet = { * withdrawn from a Purse. The amount of digital assets in a purse can change * through the action of deposit() and withdraw(). */ -export type OrchPurse< - K extends AssetKind = AssetKind, - M extends Key = Key, -> = RemotableObject & OrchPurseMethods; - -/** - * The primary use for Purses and Payments is for - * currency-like and goods-like digital assets, but they can also be used to - * represent other kinds of rights, such as the right to participate in a - * particular contract. - */ -export type OrchPurseMethods< - K extends AssetKind = AssetKind, - M extends Key = Key, -> = { +export type OrchPurse = { /** * Get the alleged Brand for this * Purse */ - getAllegedBrand: () => Brand; + getAllegedBrand: () => Brand; /** * Get the amount contained in * this purse. */ - getCurrentAmount: () => ERef>; + getCurrentAmount: () => ERef; /** * Get the amount contained in * this purse + all payments still in the recoverySet at this moment */ - getCurrentFullBalance: () => ERef>; + getCurrentFullBalance: () => ERef; /** * Get a * lossy notifier for changes to this purse's balance. */ - getCurrentAmountNotifier: () => LatestTopic>; + getCurrentAmountNotifier: () => LatestTopic; /** * Deposit all the contents of payment into this purse, returning the amount. If @@ -90,10 +120,7 @@ export type OrchPurseMethods< * * If payment is a promise, throw an error. */ - deposit:

>( - payment: P, - optAmountShape?: Pattern, - ) => ERef>; + deposit: (payment: Payment, optAmountShape?: Pattern) => ERef; /** * Return an object whose @@ -105,7 +132,7 @@ export type OrchPurseMethods< * Withdraw amount * from this purse into a new Payment. */ - withdraw: (amount: Amount) => Payment; + withdraw: (amount: Amount) => Payment; /** * The set of payments @@ -119,7 +146,7 @@ export type OrchPurseMethods< * * Returns an empty set if this issuer does not support recovery sets. */ - getRecoverySet: () => CopySet>; + getRecoverySet: () => CopySet; /** * For use in emergencies, such as @@ -129,5 +156,5 @@ export type OrchPurseMethods< * * Returns an empty amount if this issuer does not support recovery sets. */ - recoverAll: () => Amount; + recoverAll: () => Amount; };