From 39f68fe1aa46e3fc7272421958093e1c1c948262 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 21 May 2024 15:42:26 -0500 Subject: [PATCH 01/15] fix(unbondExample): don't try to take zone from privateArgs make zone from baggage --- .../src/examples/unbondExample.contract.js | 10 ++++++---- .../orchestration/test/examples/unbondExample.test.ts | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index ff2beac44d0..272d4899bf4 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -1,14 +1,15 @@ import { Far } from '@endo/far'; import { M } from '@endo/patterns'; +import { makeDurableZone } from '@agoric/zone/durable.js'; import { makeOrchestrationFacade } from '../facade.js'; /** * @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js' * @import {TimerService} from '@agoric/time'; + * @import {Baggage} from '@agoric/vat-data'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from '../service.js'; - * @import {Zone} from '@agoric/zone'; */ /** @@ -18,12 +19,13 @@ import { makeOrchestrationFacade } from '../facade.js'; * orchestrationService: Remote; * storageNode: Remote; * timerService: Remote; - * zone: Zone; * }} privateArgs + * @param {Baggage} baggage */ -export const start = async (zcf, privateArgs) => { - const { localchain, orchestrationService, storageNode, timerService, zone } = +export const start = async (zcf, privateArgs, baggage) => { + const { localchain, orchestrationService, storageNode, timerService } = privateArgs; + const zone = makeDurableZone(baggage); const { orchestrate } = makeOrchestrationFacade({ localchain, diff --git a/packages/orchestration/test/examples/unbondExample.test.ts b/packages/orchestration/test/examples/unbondExample.test.ts index dc874ca5f33..06cf5f9f0d8 100644 --- a/packages/orchestration/test/examples/unbondExample.test.ts +++ b/packages/orchestration/test/examples/unbondExample.test.ts @@ -30,7 +30,6 @@ test('start', async t => { orchestrationService: null as any, storageNode: bootstrap.storage.rootNode, timerService: bootstrap.timer, - zone: bootstrap.rootZone, }, ); From ffa2f929df5ab557bec74b2ae12227c598064153 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 30 May 2024 11:39:15 -0700 Subject: [PATCH 02/15] refactor: redundant setWakeup --- packages/zoe/tools/manualTimer.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/zoe/tools/manualTimer.js b/packages/zoe/tools/manualTimer.js index bd70da99bdb..50e07512734 100644 --- a/packages/zoe/tools/manualTimer.js +++ b/packages/zoe/tools/manualTimer.js @@ -118,15 +118,10 @@ export const buildZoeManualTimer = ( } }; - const setWakeup = (when, handler, cancelToken) => { - return timerService.setWakeup(when, handler, cancelToken); - }; - return Far('ManualTimer', { ...bindAllMethods(timerService), tick, tickN, - setWakeup, }); }; harden(buildZoeManualTimer); From f2c8875246b2fb0521ea088cac2f681f43a89a01 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 13:29:06 -0700 Subject: [PATCH 03/15] refactor: for loop --- packages/vats/tools/bank-utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vats/tools/bank-utils.js b/packages/vats/tools/bank-utils.js index eff6af2fbde..cf077e6807d 100644 --- a/packages/vats/tools/bank-utils.js +++ b/packages/vats/tools/bank-utils.js @@ -22,11 +22,11 @@ export const makeFakeBankKit = issuerKits => { const purses = makeScalarMapStore(); // XXX setup purses without publishing - issuerKits.forEach(kit => issuers.init(kit.brand, kit.issuer)); - issuerKits.forEach(kit => { + for (const kit of issuerKits) { assert(kit.issuer); + issuers.init(kit.brand, kit.issuer); purses.init(kit.brand, E(kit.issuer).makeEmptyPurse()); - }); + } /** * @type {SubscriptionRecord< From 1906a7689dce773dc3a28e41336908dd4cd82f41 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 13:48:32 -0700 Subject: [PATCH 04/15] test: factor out LCA/stakeBld --- packages/boot/test/bootstrapTests/lca.test.ts | 89 +++++++++++++++++++ .../test/bootstrapTests/orchestration.test.ts | 84 ++--------------- 2 files changed, 96 insertions(+), 77 deletions(-) create mode 100644 packages/boot/test/bootstrapTests/lca.test.ts diff --git a/packages/boot/test/bootstrapTests/lca.test.ts b/packages/boot/test/bootstrapTests/lca.test.ts new file mode 100644 index 00000000000..a2ead29c8aa --- /dev/null +++ b/packages/boot/test/bootstrapTests/lca.test.ts @@ -0,0 +1,89 @@ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import type { TestFn } from 'ava'; + +import { Fail } from '@agoric/assert'; +import { AmountMath } from '@agoric/ertp'; +import type { start as stakeBldStart } from '@agoric/orchestration/src/examples/stakeBld.contract.js'; +import type { Instance } from '@agoric/zoe/src/zoeService/utils.js'; +import { M, matches } from '@endo/patterns'; +import { makeWalletFactoryContext } from './walletFactory.ts'; + +type DefaultTestContext = Awaited>; + +const test: TestFn = anyTest; + +test.before(async t => (t.context = await makeWalletFactoryContext(t))); +test.after.always(t => t.context.shutdown?.()); + +test.serial('stakeBld', async t => { + const { + agoricNamesRemotes, + buildProposal, + evalProposal, + refreshAgoricNamesRemotes, + } = t.context; + // TODO move into a vm-config for u15 + await evalProposal( + buildProposal('@agoric/builders/scripts/vats/init-localchain.js'), + ); + // start-stakeBld depends on this. Sanity check in case the context changes. + const { BLD } = agoricNamesRemotes.brand; + BLD || Fail`BLD missing from agoricNames`; + await evalProposal( + buildProposal('@agoric/builders/scripts/orchestration/init-stakeBld.js'), + ); + // update now that stakeBld is instantiated + refreshAgoricNamesRemotes(); + const stakeBld = agoricNamesRemotes.instance.stakeBld as Instance< + typeof stakeBldStart + >; + t.truthy(stakeBld); + + const wd = await t.context.walletFactoryDriver.provideSmartWallet( + 'agoric1testStakeBld', + ); + + await wd.executeOffer({ + id: 'request-stake', + invitationSpec: { + source: 'agoricContract', + instancePath: ['stakeBld'], + callPipe: [['makeStakeBldInvitation']], + }, + proposal: { + give: { + // @ts-expect-error XXX BoardRemote + In: { brand: BLD, value: 10n }, + }, + }, + }); + + const current = await wd.getCurrentWalletRecord(); + const latest = await wd.getLatestUpdateRecord(); + t.like(current, { + offerToPublicSubscriberPaths: [ + // TODO publish something useful + ['request-stake', { account: 'published.stakeBld' }], + ], + }); + t.like(latest, { + status: { id: 'request-stake', numWantsSatisfied: 1 }, + }); + + await wd.executeOffer({ + id: 'request-delegate', + invitationSpec: { + source: 'continuing', + previousOffer: 'request-stake', + invitationMakerName: 'Delegate', + invitationArgs: ['agoric1validator1', { brand: BLD, value: 10n }], + }, + proposal: { + give: { + // @ts-expect-error XXX BoardRemote + In: { brand: BLD, value: 10n }, + }, + }, + }); +}); diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index c94ef0a595b..17a2ddc18a8 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -4,10 +4,10 @@ import type { TestFn } from 'ava'; import { Fail } from '@agoric/assert'; import { AmountMath } from '@agoric/ertp'; -import type { start as stakeBldStart } from '@agoric/orchestration/src/examples/stakeBld.contract.js'; import type { Instance } from '@agoric/zoe/src/zoeService/utils.js'; import { M, matches } from '@endo/patterns'; import type { CosmosValidatorAddress } from '@agoric/orchestration'; +import type { start as startStakeAtom } from '@agoric/orchestration/src/examples/stakeAtom.contract.js'; import { makeWalletFactoryContext } from './walletFactory.ts'; type DefaultTestContext = Awaited>; @@ -17,85 +17,13 @@ const test: TestFn = anyTest; test.before(async t => (t.context = await makeWalletFactoryContext(t))); test.after.always(t => t.context.shutdown?.()); -test.serial('stakeBld', async t => { - const { - agoricNamesRemotes, - buildProposal, - evalProposal, - refreshAgoricNamesRemotes, - } = t.context; - // TODO move into a vm-config for u15 - await evalProposal( - buildProposal('@agoric/builders/scripts/vats/init-localchain.js'), - ); - // start-stakeBld depends on this. Sanity check in case the context changes. - const { BLD } = agoricNamesRemotes.brand; - BLD || Fail`BLD missing from agoricNames`; - await evalProposal( - buildProposal('@agoric/builders/scripts/orchestration/init-stakeBld.js'), - ); - // update now that stakeBld is instantiated - refreshAgoricNamesRemotes(); - const stakeBld = agoricNamesRemotes.instance.stakeBld as Instance< - typeof stakeBldStart - >; - t.truthy(stakeBld); - - const wd = await t.context.walletFactoryDriver.provideSmartWallet( - 'agoric1testStakeBld', - ); - - await wd.executeOffer({ - id: 'request-stake', - invitationSpec: { - source: 'agoricContract', - instancePath: ['stakeBld'], - callPipe: [['makeStakeBldInvitation']], - }, - proposal: { - give: { - // @ts-expect-error XXX BoardRemote - In: { brand: BLD, value: 10n }, - }, - }, - }); - - const current = await wd.getCurrentWalletRecord(); - const latest = await wd.getLatestUpdateRecord(); - t.like(current, { - offerToPublicSubscriberPaths: [ - // TODO publish something useful - ['request-stake', { account: 'published.stakeBld' }], - ], - }); - t.like(latest, { - status: { id: 'request-stake', numWantsSatisfied: 1 }, - }); - - await wd.executeOffer({ - id: 'request-delegate', - invitationSpec: { - source: 'continuing', - previousOffer: 'request-stake', - invitationMakerName: 'Delegate', - invitationArgs: ['agoric1validator1', { brand: BLD, value: 10n }], - }, - proposal: { - give: { - // @ts-expect-error XXX BoardRemote - In: { brand: BLD, value: 10n }, - }, - }, - }); -}); - test.serial('stakeAtom - repl-style', async t => { const { buildProposal, evalProposal, runUtils: { EV }, } = t.context; - // TODO move into a vm-config for u15 + // TODO move into a vm-config await evalProposal( buildProposal('@agoric/builders/scripts/vats/init-network.js'), ); @@ -107,10 +35,12 @@ test.serial('stakeAtom - repl-style', async t => { ); const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames'); - const instance = await EV(agoricNames).lookup('instance', 'stakeAtom'); + const instance: Instance = await EV( + agoricNames, + ).lookup('instance', 'stakeAtom'); t.truthy(instance, 'stakeAtom instance is available'); - const zoe = await EV.vat('bootstrap').consumeItem('zoe'); + const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); const publicFacet = await EV(zoe).getPublicFacet(instance); t.truthy(publicFacet, 'stakeAtom publicFacet is available'); @@ -122,7 +52,7 @@ test.serial('stakeAtom - repl-style', async t => { 'account is a remotable', ); - const atomBrand = await EV(agoricNames).lookup('brand', 'ATOM'); + const atomBrand: Brand = await EV(agoricNames).lookup('brand', 'ATOM'); const atomAmount = AmountMath.make(atomBrand, 10n); const validatorAddress: CosmosValidatorAddress = { From 713e8ce69efc59385cb9cac7e06fd4c70f196cbf Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 13:48:45 -0700 Subject: [PATCH 05/15] docs: jsdoc --- packages/orchestration/src/examples/stakeBld.contract.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index 964f4646762..4bb8fc8eeba 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -75,6 +75,9 @@ export const start = async (zcf, privateArgs, baggage) => { makeStakeBldInvitation: M.callWhen().returns(InvitationShape), }), { + /** + * Invitation to make an account, initialized with the give's BLD + */ makeStakeBldInvitation() { return zcf.makeInvitation( async seat => { @@ -104,6 +107,9 @@ export const start = async (zcf, privateArgs, baggage) => { const { holder } = await makeLocalAccountKit(); return holder; }, + /** + * Invitation to make an account, without any funds + */ makeAccountInvitationMaker() { trace('makeCreateAccountInvitation'); return zcf.makeInvitation(async seat => { From c758c7294239d34c3e62b83e41517b128a3f4205 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 30 May 2024 11:35:40 -0700 Subject: [PATCH 06/15] docs: typo --- packages/time/src/types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/time/src/types.d.ts b/packages/time/src/types.d.ts index ec211522ecb..42e8544f36f 100644 --- a/packages/time/src/types.d.ts +++ b/packages/time/src/types.d.ts @@ -121,7 +121,7 @@ export interface TimerServiceI { cancelToken?: CancelToken, ) => TimestampRecord; /** - * Create and return a promise that will resolve after the absolte + * Create and return a promise that will resolve after the absolute * time has passed. */ wakeAt: ( From 735c7e346f540ea691e75593fb09684bc1f050d5 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 14:32:38 -0700 Subject: [PATCH 07/15] test: use commonSetup --- .../test/exos/local-chain-account-kit.test.ts | 62 ++++--------------- 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-chain-account-kit.test.ts index 3fce7fd55aa..36f1ab5e378 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-chain-account-kit.test.ts @@ -1,56 +1,22 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { AmountMath, makeIssuerKit } from '@agoric/ertp'; + +import { AmountMath } from '@agoric/ertp'; import { makeMockChainStorageRoot } from '@agoric/internal/src/storage-test-utils.js'; -import { M, makeScalarBigMapStore } from '@agoric/vat-data'; -import { prepareLocalChainTools } from '@agoric/vats/src/localchain.js'; -import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; -import { buildRootObject as buildBankVatRoot } from '@agoric/vats/src/vat-bank.js'; +import { makeScalarBigMapStore } from '@agoric/vat-data'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; -import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; -import { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js'; -import { makeHeapZone } from '@agoric/zone'; import { E, Far } from '@endo/far'; -import { makeFakeLocalchainBridge } from '../supports.js'; import { prepareLocalChainAccountKit } from '../../src/exos/local-chain-account-kit.js'; -import { prepareMockChainInfo } from '../../src/utils/mockChainInfo.js'; import { ChainAddress } from '../../src/orchestration-api.js'; +import { prepareMockChainInfo } from '../../src/utils/mockChainInfo.js'; import { NANOSECONDS_PER_SECOND } from '../../src/utils/time.js'; +import { commonSetup } from '../supports.js'; -test('localChainAccountKit - transfer', async t => { - const bootstrap = async () => { - const zone = makeHeapZone(); - const issuerKit = makeIssuerKit('BLD'); - const stake = withAmountUtils(issuerKit); - - const bankManager = await buildBankVatRoot( - undefined, - undefined, - zone.mapStore('bankManager'), - ).makeBankManager(); - - await E(bankManager).addAsset('ubld', 'BLD', 'Staking Token', issuerKit); - const localchainBridge = makeFakeLocalchainBridge(zone); - const localchain = prepareLocalChainTools( - zone.subZone('localchain'), - ).makeLocalChain({ - bankManager, - system: localchainBridge, - }); - const timer = buildZoeManualTimer(t.log); - const marshaller = makeFakeBoard().getReadonlyMarshaller(); - - return { - timer, - localchain, - marshaller, - stake, - issuerKit, - rootZone: zone, - }; - }; +test('transfer', async t => { + const { bootstrap, brands, utils } = await commonSetup(t); + + const { bld: stake } = brands; - const { timer, localchain, stake, marshaller, issuerKit, rootZone } = - await bootstrap(); + const { timer, localchain, marshaller, rootZone } = bootstrap; t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); @@ -84,13 +50,11 @@ test('localChainAccountKit - transfer', async t => { t.truthy(account, 'account is returned'); t.regex(await E(account).getAddress(), /agoric1/); - const oneHundredStakeAmt = stake.make(1_000_000_000n); - const oneHundredStakePmt = issuerKit.mint.mintPayment(oneHundredStakeAmt); - const oneStakeAmt = stake.make(1_000_000n); + const oneHundredStakePmt = await utils.pourPayment(stake.units(100)); t.log('deposit 100 bld to account'); const depositResp = await E(account).deposit(oneHundredStakePmt); - t.true(AmountMath.isEqual(depositResp, oneHundredStakeAmt), 'deposit'); + t.true(AmountMath.isEqual(depositResp, stake.units(100)), 'deposit'); const destination: ChainAddress = { chainId: 'cosmoslocal', @@ -100,7 +64,7 @@ test('localChainAccountKit - transfer', async t => { // TODO #9211, support ERTP amounts t.log('ERTP Amounts not yet supported for AmountArg'); - await t.throwsAsync(() => E(account).transfer(oneStakeAmt, destination), { + await t.throwsAsync(() => E(account).transfer(stake.units(1), destination), { message: 'ERTP Amounts not yet supported', }); From 878f15129c90855ac7d1c4cf697d7f3f7a2be3e3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 14:36:49 -0700 Subject: [PATCH 08/15] refactor: zone for LCAKit --- .../src/examples/stakeBld.contract.js | 2 +- .../src/exos/local-chain-account-kit.js | 11 +++++------ .../test/exos/local-chain-account-kit.test.ts | 16 +++++++--------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index 4bb8fc8eeba..de8f15c94b5 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -48,7 +48,7 @@ export const start = async (zcf, privateArgs, baggage) => { const agoricChainInfo = prepareMockChainInfo(zone); const makeLocalChainAccountKit = prepareLocalChainAccountKit( - baggage, + zone, makeRecorderKit, zcf, privateArgs.timerService, diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index b03bbb31715..c255d4f7503 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -3,7 +3,7 @@ import { NonNullish } from '@agoric/assert'; import { typedJson } from '@agoric/cosmic-proto/vatsafe'; import { AmountShape, PaymentShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; -import { M, prepareExoClassKit } from '@agoric/vat-data'; +import { M } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { E } from '@endo/far'; import { @@ -17,7 +17,7 @@ import { makeTimestampHelper } from '../utils/time.js'; * @import {LocalChainAccount} from '@agoric/vats/src/localchain.js'; * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, CosmosChainInfo} from '@agoric/orchestration'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. - * @import {Baggage} from '@agoric/vat-data'; + * @import {Zone} from '@agoric/zone'; * @import {TimerService, TimerBrand} from '@agoric/time'; * @import {TimestampHelper} from '../utils/time.js'; */ @@ -60,7 +60,7 @@ const PUBLIC_TOPICS = { }; /** - * @param {Baggage} baggage + * @param {Zone} zone * @param {MakeRecorderKit} makeRecorderKit * @param {ZCF} zcf * @param {TimerService} timerService @@ -68,7 +68,7 @@ const PUBLIC_TOPICS = { * @param {AgoricChainInfo} agoricChainInfo */ export const prepareLocalChainAccountKit = ( - baggage, + zone, makeRecorderKit, zcf, timerService, @@ -79,8 +79,7 @@ export const prepareLocalChainAccountKit = ( /** * Make an object wrapping an LCA with Zoe interfaces. */ - const makeLocalChainAccountKit = prepareExoClassKit( - baggage, + const makeLocalChainAccountKit = zone.exoClassKit( 'LCA Kit', { holder: HolderI, diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-chain-account-kit.test.ts index 36f1ab5e378..7d18d916dec 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-chain-account-kit.test.ts @@ -1,8 +1,6 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { AmountMath } from '@agoric/ertp'; -import { makeMockChainStorageRoot } from '@agoric/internal/src/storage-test-utils.js'; -import { makeScalarBigMapStore } from '@agoric/vat-data'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { E, Far } from '@endo/far'; import { prepareLocalChainAccountKit } from '../../src/exos/local-chain-account-kit.js'; @@ -16,18 +14,18 @@ test('transfer', async t => { const { bld: stake } = brands; - const { timer, localchain, marshaller, rootZone } = bootstrap; + const { timer, localchain, marshaller, rootZone, storage } = bootstrap; t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); t.log('exo setup - prepareLocalChainAccountKit'); - const baggage = makeScalarBigMapStore('baggage', { - durable: true, - }); - const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); + const { makeRecorderKit } = prepareRecorderKitMakers( + rootZone.mapStore('recorder'), + marshaller, + ); const makeLocalChainAccountKit = prepareLocalChainAccountKit( - baggage, + rootZone, makeRecorderKit, // @ts-expect-error mocked zcf. use `stake-bld.contract.test.ts` to test LCA with offer Far('MockZCF', {}), @@ -44,7 +42,7 @@ test('transfer', async t => { const { holder: account } = makeLocalChainAccountKit({ account: lca, address, - storageNode: makeMockChainStorageRoot().makeChildNode('lcaKit'), + storageNode: storage.rootNode.makeChildNode('lcaKit'), }); t.truthy(account, 'account is returned'); From 086984f6609ee64e37ae2a49652e9d5b11c52a63 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 14:46:00 -0700 Subject: [PATCH 09/15] test: withdrawal --- .../test/exos/local-chain-account-kit.test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-chain-account-kit.test.ts index 7d18d916dec..2fb4ebaccf8 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-chain-account-kit.test.ts @@ -9,6 +9,74 @@ import { prepareMockChainInfo } from '../../src/utils/mockChainInfo.js'; import { NANOSECONDS_PER_SECOND } from '../../src/utils/time.js'; import { commonSetup } from '../supports.js'; +test('deposit, withdraw', async t => { + const { bootstrap, brands, utils } = await commonSetup(t); + + const { bld: stake } = brands; + + const { timer, localchain, marshaller, rootZone, storage } = bootstrap; + + t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); + const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); + + t.log('exo setup - prepareLocalChainAccountKit'); + const { makeRecorderKit } = prepareRecorderKitMakers( + rootZone.mapStore('recorder'), + marshaller, + ); + const makeLocalChainAccountKit = prepareLocalChainAccountKit( + rootZone, + makeRecorderKit, + // @ts-expect-error mocked zcf. use `stake-bld.contract.test.ts` to test LCA with offer + Far('MockZCF', {}), + timer, + timer.getTimerBrand(), + agoricChainInfo, + ); + + t.log('request account from vat-localchain'); + const lca = await E(localchain).makeAccount(); + const address = await E(lca).getAddress(); + + t.log('make a LocalChainAccountKit'); + const { holder: account } = makeLocalChainAccountKit({ + account: lca, + address, + storageNode: storage.rootNode.makeChildNode('lcaKit'), + }); + + t.regex(await E(account).getAddress(), /agoric1/); + + const oneHundredStakePmt = await utils.pourPayment(stake.units(100)); + + t.log('deposit 100 bld to account'); + const depositResp = await E(account).deposit(oneHundredStakePmt); + t.true(AmountMath.isEqual(depositResp, stake.units(100)), 'deposit'); + + const withdrawal1 = await E(account).withdraw(stake.units(50)); + t.true( + AmountMath.isEqual( + await stake.issuer.getAmountOf(withdrawal1), + stake.units(50), + ), + ); + + await t.throwsAsync( + E(account).withdraw(stake.units(51)), + undefined, + 'fails to overwithdraw', + ); + await t.notThrowsAsync( + E(account).withdraw(stake.units(50)), + 'succeeeds at exactly empty', + ); + await t.throwsAsync( + E(account).withdraw(stake.make(1n)), + undefined, + 'fails to overwithdraw', + ); +}); + test('transfer', async t => { const { bootstrap, brands, utils } = await commonSetup(t); From a104ce39923e534b48706404cb25db938c4cee56 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 21 May 2024 12:55:49 -0500 Subject: [PATCH 10/15] chore(stakingAccountKit): baggage -> zone --- .../src/examples/stakeAtom.contract.js | 2 +- .../src/exos/stakingAccountKit.js | 15 +++++---- .../orchestration/test/staking-ops.test.ts | 31 +++++++------------ 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index c0d4c8c7fef..57ea9e193c5 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -59,7 +59,7 @@ export const start = async (zcf, privateArgs, baggage) => { const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const makeStakingAccountKit = prepareStakingAccountKit( - baggage, + zone, makeRecorderKit, zcf, ); diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index 01e64032e1d..18efe4882be 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -17,7 +17,7 @@ import { import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { AmountShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; -import { M, prepareExoClassKit } from '@agoric/vat-data'; +import { M } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { decodeBase64, encodeBase64 } from '@endo/base64'; @@ -37,10 +37,10 @@ export const maxClockSkew = 10n * 60n; /** * @import {AmountArg, IcaAccount, ChainAddress, ChainAmount, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount} from '../types.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'; - * @import {Baggage} from '@agoric/swingset-liveslots'; - * @import { Coin } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; - * @import { Delegation } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; + * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; + * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {TimerService} from '@agoric/time'; + * @import {Zone} from '@agoric/zone'; */ const trace = makeTracer('StakingAccountHolder'); @@ -124,13 +124,12 @@ export const tryDecodeResponse = (ackStr, fromProtoMsg) => { const toDenomAmount = c => ({ denom: c.denom, value: BigInt(c.amount) }); /** - * @param {Baggage} baggage + * @param {Zone} zone * @param {MakeRecorderKit} makeRecorderKit * @param {ZCF} zcf */ -export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { - const makeStakingAccountKit = prepareExoClassKit( - baggage, +export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { + const makeStakingAccountKit = zone.exoClassKit( 'Staking Account Holder', { helper: M.interface('helper', { diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index 3f87373de49..99248ed72a0 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -15,6 +15,7 @@ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import type { Coin } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; import type { TimestampRecord, TimestampValue } from '@agoric/time'; import type { AnyJson } from '@agoric/cosmic-proto'; +import { makeDurableZone } from '@agoric/zone/durable.js'; import { prepareStakingAccountKit, encodeTxResponse, @@ -177,6 +178,7 @@ const makeScenario = () => { const makeRecorderKit = () => harden({}) as any; const baggage = makeScalarBigMapStore('b1') as Baggage; + const zone = makeDurableZone(baggage); const { delegations, startTime } = configStaking; @@ -193,6 +195,7 @@ const makeScenario = () => { }); return { baggage, + zone, makeRecorderKit, ...mockAccount(undefined, delegations), storageNode, @@ -205,8 +208,8 @@ const makeScenario = () => { test('withdrawRewards() on StakingAccountHolder formats message correctly', async t => { const s = makeScenario(); const { account, calls, timer } = s; - const { baggage, makeRecorderKit, storageNode, zcf, icqConnection } = s; - const make = prepareStakingAccountKit(baggage, makeRecorderKit, zcf); + const { makeRecorderKit, storageNode, zcf, icqConnection, zone } = s; + const make = prepareStakingAccountKit(zone, makeRecorderKit, zcf); // Higher fidelity tests below use invitationMakers. const { holder } = make(account.getAddress(), 'uatom', { @@ -228,13 +231,9 @@ test('withdrawRewards() on StakingAccountHolder formats message correctly', asyn test(`delegate; redelegate using invitationMakers`, async t => { const s = makeScenario(); const { account, calls, timer } = s; - const { baggage, makeRecorderKit, storageNode, zcf, zoe, icqConnection } = s; + const { makeRecorderKit, storageNode, zcf, zoe, icqConnection, zone } = s; const aBrand = Far('Token') as Brand<'nat'>; - const makeAccountKit = prepareStakingAccountKit( - baggage, - makeRecorderKit, - zcf, - ); + const makeAccountKit = prepareStakingAccountKit(zone, makeRecorderKit, zcf); const { invitationMakers } = makeAccountKit(account.getAddress(), 'uatom', { account, @@ -298,12 +297,8 @@ test(`delegate; redelegate using invitationMakers`, async t => { test(`withdraw rewards using invitationMakers`, async t => { const s = makeScenario(); const { account, calls, timer } = s; - const { baggage, makeRecorderKit, storageNode, zcf, zoe, icqConnection } = s; - const makeAccountKit = prepareStakingAccountKit( - baggage, - makeRecorderKit, - zcf, - ); + const { makeRecorderKit, storageNode, zcf, zoe, icqConnection, zone } = s; + const makeAccountKit = prepareStakingAccountKit(zone, makeRecorderKit, zcf); const { invitationMakers } = makeAccountKit(account.getAddress(), 'uatom', { account, @@ -328,12 +323,8 @@ test(`withdraw rewards using invitationMakers`, async t => { test(`undelegate waits for unbonding period`, async t => { const s = makeScenario(); const { account, calls, timer } = s; - const { baggage, makeRecorderKit, storageNode, zcf, zoe, icqConnection } = s; - const makeAccountKit = prepareStakingAccountKit( - baggage, - makeRecorderKit, - zcf, - ); + const { makeRecorderKit, storageNode, zcf, zoe, icqConnection, zone } = s; + const makeAccountKit = prepareStakingAccountKit(zone, makeRecorderKit, zcf); const { invitationMakers } = makeAccountKit(account.getAddress(), 'uatom', { account, From a39ff64df3c648e5f4c6b322ce019e2362d2cee3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 30 May 2024 11:07:01 -0700 Subject: [PATCH 11/15] refactor: extract cosmos utils --- .../src/exos/stakingAccountKit.js | 47 +++++-------------- packages/orchestration/src/utils/cosmos.js | 37 +++++++++++++++ .../orchestration/test/staking-ops.test.ts | 3 +- .../orchestration/test/tx-encoding.test.ts | 2 +- 4 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 packages/orchestration/src/utils/cosmos.js diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index 18efe4882be..dbd2722d7c0 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -1,4 +1,9 @@ /** @file Use-object for the owner of a staking account */ +import { toRequestQueryJson } from '@agoric/cosmic-proto'; +import { + QueryBalanceRequest, + QueryBalanceResponse, +} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; import { MsgWithdrawDelegatorReward, MsgWithdrawDelegatorRewardResponse, @@ -10,19 +15,14 @@ import { MsgUndelegate, MsgUndelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; -import { - QueryBalanceRequest, - QueryBalanceResponse, -} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { AmountShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { M } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; -import { decodeBase64, encodeBase64 } from '@endo/base64'; +import { decodeBase64 } from '@endo/base64'; import { E } from '@endo/far'; -import { toRequestQueryJson } from '@agoric/cosmic-proto'; import { AmountArgShape, ChainAddressShape, @@ -30,12 +30,14 @@ import { CoinShape, DelegationShape, } from '../typeGuards.js'; - -/** maximum clock skew, in seconds, for unbonding time reported from other chain */ -export const maxClockSkew = 10n * 60n; +import { + encodeTxResponse, + maxClockSkew, + tryDecodeResponse, +} from '../utils/cosmos.js'; /** - * @import {AmountArg, IcaAccount, ChainAddress, ChainAmount, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount} from '../types.js'; + * @import {AmountArg, IcaAccount, ChainAddress, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount} from '../types.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'; * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; @@ -84,14 +86,6 @@ const PUBLIC_TOPICS = { account: ['Staking Account holder status', M.any()], }; -export const encodeTxResponse = (response, toProtoMsg) => { - const protoMsg = toProtoMsg(response); - const any1 = Any.fromPartial(protoMsg); - const any2 = Any.fromPartial({ value: Any.encode(any1).finish() }); - const ackStr = encodeBase64(Any.encode(any2).finish()); - return ackStr; -}; - export const trivialDelegateResponse = encodeTxResponse( {}, MsgDelegateResponse.toProtoMsg, @@ -103,23 +97,6 @@ const expect = (actual, expected, message) => { } }; -/** - * @template T - * @param {string} ackStr - * @param {(p: {typeUrl: string, value: Uint8Array}) => T} fromProtoMsg - */ -export const tryDecodeResponse = (ackStr, fromProtoMsg) => { - try { - const any = Any.decode(decodeBase64(ackStr)); - const protoMsg = Any.decode(any.value); - - const msg = fromProtoMsg(protoMsg); - return msg; - } catch (cause) { - throw assert.error(`bad response: ${ackStr}`, undefined, { cause }); - } -}; - /** @type {(c: { denom: string, amount: string }) => DenomAmount} */ const toDenomAmount = c => ({ denom: c.denom, value: BigInt(c.amount) }); diff --git a/packages/orchestration/src/utils/cosmos.js b/packages/orchestration/src/utils/cosmos.js new file mode 100644 index 00000000000..f5bfc7f1d04 --- /dev/null +++ b/packages/orchestration/src/utils/cosmos.js @@ -0,0 +1,37 @@ +import { assert } from '@agoric/assert'; +import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; +import { decodeBase64, encodeBase64 } from '@endo/base64'; + +/** maximum clock skew, in seconds, for unbonding time reported from other chain */ +export const maxClockSkew = 10n * 60n; + +/** + * + * @param {unknown} response + * @param {(msg: any) => Any} toProtoMsg + * @returns {string} + */ +export const encodeTxResponse = (response, toProtoMsg) => { + const protoMsg = toProtoMsg(response); + const any1 = Any.fromPartial(protoMsg); + const any2 = Any.fromPartial({ value: Any.encode(any1).finish() }); + const ackStr = encodeBase64(Any.encode(any2).finish()); + return ackStr; +}; + +/** + * @template T + * @param {string} ackStr + * @param {(p: {typeUrl: string, value: Uint8Array}) => T} fromProtoMsg + */ +export const tryDecodeResponse = (ackStr, fromProtoMsg) => { + try { + const any = Any.decode(decodeBase64(ackStr)); + const protoMsg = Any.decode(any.value); + + const msg = fromProtoMsg(protoMsg); + return msg; + } catch (cause) { + throw assert.error(`bad response: ${ackStr}`, undefined, { cause }); + } +}; diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index 99248ed72a0..c0bba8c3001 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -18,10 +18,9 @@ import type { AnyJson } from '@agoric/cosmic-proto'; import { makeDurableZone } from '@agoric/zone/durable.js'; import { prepareStakingAccountKit, - encodeTxResponse, trivialDelegateResponse, } from '../src/exos/stakingAccountKit.js'; - +import { encodeTxResponse } from '../src/utils/cosmos.js'; import type { IcaAccount, ChainAddress, ICQConnection } from '../src/types.js'; const { Fail } = assert; diff --git a/packages/orchestration/test/tx-encoding.test.ts b/packages/orchestration/test/tx-encoding.test.ts index e26ee203b4f..414094c00cd 100644 --- a/packages/orchestration/test/tx-encoding.test.ts +++ b/packages/orchestration/test/tx-encoding.test.ts @@ -7,7 +7,7 @@ import { import { MsgDelegateResponse } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { decodeBase64, encodeBase64 } from '@endo/base64'; -import { tryDecodeResponse } from '../src/exos/stakingAccountKit.js'; +import { tryDecodeResponse } from '../src/utils/cosmos.js'; const test = anyTest; From 8623b8d2eaffa659e6e8a388f64bee930e6046d6 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 30 May 2024 13:17:59 -0700 Subject: [PATCH 12/15] chore(types): fix executeTx type --- packages/vats/src/localchain.js | 4 ++-- packages/vats/tools/fake-bridge.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/vats/src/localchain.js b/packages/vats/src/localchain.js index 6630f4c44e8..b2fbefc48b9 100644 --- a/packages/vats/src/localchain.js +++ b/packages/vats/src/localchain.js @@ -6,7 +6,7 @@ import { AmountShape, BrandShape, PaymentShape } from '@agoric/ertp'; const { Fail } = assert; /** - * @import {TypedJson, ResponseTo} from '@agoric/cosmic-proto'; + * @import {TypedJson, ResponseTo, JsonSafe} from '@agoric/cosmic-proto'; * @import {BankManager, Bank} from './vat-bank.js'; * @import {ScopedBridgeManager} from './types.js'; */ @@ -89,7 +89,7 @@ const prepareLocalChainAccount = zone => * @template {TypedJson[]} MT messages tuple (use const with multiple * elements or it will be a mixed array) * @param {MT} messages - * @returns {Promise<{ [K in keyof MT]: ResponseTo }>} + * @returns {Promise<{ [K in keyof MT]: JsonSafe> }>} */ async executeTx(messages) { const { address, system } = this.state; diff --git a/packages/vats/tools/fake-bridge.js b/packages/vats/tools/fake-bridge.js index 5117d6e7579..cb8c2e3221d 100644 --- a/packages/vats/tools/fake-bridge.js +++ b/packages/vats/tools/fake-bridge.js @@ -5,7 +5,8 @@ import { makeWhen } from '@agoric/vow/src/when.js'; import { Nat } from '@endo/nat'; /** - * @import {MsgDelegateResponse} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; + * @import {JsonSafe} from '@agoric/cosmic-proto'; + * @import {MsgDelegateResponse, MsgUndelegateResponse} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; * @import {BridgeHandler, ScopedBridgeManager} from '../src/types.js'; * @import {Remote} from '@agoric/vow'; */ @@ -189,7 +190,8 @@ export const makeFakeLocalchainBridge = (zone, onToBridge = () => {}) => { }; } case '/cosmos.staking.v1beta1.MsgDelegate': { - return /** @type {MsgDelegateResponse} */ {}; + return /** @type {JsonSafe} */ ({}); + } } // returns one empty object per message unless specified default: From be9df898ef058704ad144d6898edc5cde15ef582 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 30 May 2024 11:31:19 -0700 Subject: [PATCH 13/15] fix(staking): completion time parsing --- .../orchestration/src/exos/local-chain-account-kit.js | 1 - packages/orchestration/src/exos/stakingAccountKit.js | 4 ++-- packages/orchestration/src/utils/time.js | 9 +++++++++ packages/orchestration/test/utils/time.test.ts | 9 +++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index c255d4f7503..8e750b075a1 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -19,7 +19,6 @@ import { makeTimestampHelper } from '../utils/time.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Zone} from '@agoric/zone'; * @import {TimerService, TimerBrand} from '@agoric/time'; - * @import {TimestampHelper} from '../utils/time.js'; */ // partial until #8879 diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index dbd2722d7c0..2b04f3b4089 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -35,6 +35,7 @@ import { maxClockSkew, tryDecodeResponse, } from '../utils/cosmos.js'; +import { dateInSeconds } from '../utils/time.js'; /** * @import {AmountArg, IcaAccount, ChainAddress, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount} from '../types.js'; @@ -384,9 +385,8 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { ); trace('undelegate response', response); const { completionTime } = response; - const endTime = BigInt(completionTime.getTime() / 1000); - await E(timer).wakeAt(endTime + maxClockSkew); + await E(timer).wakeAt(dateInSeconds(completionTime) + maxClockSkew); }, }, }, diff --git a/packages/orchestration/src/utils/time.js b/packages/orchestration/src/utils/time.js index 91f491d700e..ed500e1f042 100644 --- a/packages/orchestration/src/utils/time.js +++ b/packages/orchestration/src/utils/time.js @@ -36,3 +36,12 @@ export function makeTimestampHelper(timer, timerBrand) { } /** @typedef {Awaited>} TimestampHelper */ + +/** + * Convert a Date from a Cosmos message, which has millisecond precision, + * to a BigInt for number of seconds since epoch, for use in a timer. + * + * @param {Date} date + * @returns {bigint} + */ +export const dateInSeconds = date => BigInt(Math.floor(date.getTime() / 1000)); diff --git a/packages/orchestration/test/utils/time.test.ts b/packages/orchestration/test/utils/time.test.ts index 0325fb0355b..b1da14af958 100644 --- a/packages/orchestration/test/utils/time.test.ts +++ b/packages/orchestration/test/utils/time.test.ts @@ -2,6 +2,7 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js'; import { TimeMath } from '@agoric/time'; import { + dateInSeconds, makeTimestampHelper, NANOSECONDS_PER_SECOND, SECONDS_PER_MINUTE, @@ -38,3 +39,11 @@ test('makeTimestampHelper - getCurrentTimestamp', async t => { 'timestamp is 4 seconds since unix epoch, in nanoseconds', ); }); + +test('dateInSeconds', t => { + t.is(dateInSeconds(new Date(1)), 0n); + t.is(dateInSeconds(new Date(999)), 0n); + t.is(dateInSeconds(new Date(1000)), 1n); + + t.is(dateInSeconds(new Date('2025-12-17T12:23:45Z')), 1765974225n); +}); From be880ccdd6bb2f1b984bcb66925738e8552c50a0 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 30 May 2024 10:58:50 -0700 Subject: [PATCH 14/15] chore: remove invitation makers from holder --- .../src/exos/local-chain-account-kit.js | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index 8e750b075a1..3d0ef0d49e1 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -5,13 +5,14 @@ import { AmountShape, PaymentShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { M } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; +import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { E } from '@endo/far'; import { AmountArgShape, ChainAddressShape, IBCTransferOptionsShape, } from '../typeGuards.js'; -import { makeTimestampHelper } from '../utils/time.js'; +import { dateInSeconds, makeTimestampHelper } from '../utils/time.js'; /** * @import {LocalChainAccount} from '@agoric/vats/src/localchain.js'; @@ -42,8 +43,7 @@ const { Fail } = assert; const HolderI = M.interface('holder', { getPublicTopics: M.call().returns(TopicsRecordShape), - makeDelegateInvitation: M.call(M.string(), AmountShape).returns(M.promise()), - makeCloseAccountInvitation: M.call().returns(M.promise()), + delegate: M.call(M.string(), AmountShape).returns(M.promise()), deposit: M.callWhen(PaymentShape).returns(AmountShape), withdraw: M.callWhen(AmountShape).returns(PaymentShape), transfer: M.call(AmountArgShape, ChainAddressShape) @@ -83,8 +83,8 @@ export const prepareLocalChainAccountKit = ( { holder: HolderI, invitationMakers: M.interface('invitationMakers', { - Delegate: HolderI.payload.methodGuards.makeDelegateInvitation, - CloseAccount: HolderI.payload.methodGuards.makeCloseAccountInvitation, + Delegate: M.callWhen(M.string(), AmountShape).returns(InvitationShape), + CloseAccount: M.call().returns(M.promise()), }), }, /** @@ -104,14 +104,22 @@ export const prepareLocalChainAccountKit = ( }, { invitationMakers: { - Delegate(validatorAddress, amount) { - return this.facets.holder.makeDelegateInvitation( - validatorAddress, - amount, - ); + /** + * + * @param {string} validatorAddress + * @param {Amount<'nat'>} ertpAmount + */ + async Delegate(validatorAddress, ertpAmount) { + trace('Delegate', validatorAddress, ertpAmount); + + return zcf.makeInvitation(async seat => { + // TODO should it allow delegating more BLD? + seat.exit(); + return this.facets.holder.delegate(validatorAddress, ertpAmount); + }, 'Delegate'); }, CloseAccount() { - return this.facets.holder.makeCloseAccountInvitation(); + throw Error('not yet implemented'); }, }, holder: { @@ -130,35 +138,25 @@ export const prepareLocalChainAccountKit = ( * @param {string} validatorAddress * @param {Amount<'nat'>} ertpAmount */ - async makeDelegateInvitation(validatorAddress, ertpAmount) { - trace('makeDelegateInvitation', validatorAddress, ertpAmount); - + async delegate(validatorAddress, ertpAmount) { // TODO #9211 lookup denom from brand const amount = { amount: String(ertpAmount.value), denom: 'ubld', }; - - return zcf.makeInvitation(async seat => { - // TODO should it allow delegating more BLD? - seat.exit(); - const { account: lca } = this.state; - trace('lca', lca); - const delegatorAddress = await E(lca).getAddress(); - trace('delegatorAddress', delegatorAddress); - const [result] = await E(lca).executeTx([ - typedJson('/cosmos.staking.v1beta1.MsgDelegate', { - amount, - validatorAddress, - delegatorAddress, - }), - ]); - trace('got result', result); - return result; - }, 'Delegate'); - }, - makeCloseAccountInvitation() { - throw Error('not yet implemented'); + const { account: lca } = this.state; + trace('lca', lca); + const delegatorAddress = await E(lca).getAddress(); + trace('delegatorAddress', delegatorAddress); + const [result] = await E(lca).executeTx([ + typedJson('/cosmos.staking.v1beta1.MsgDelegate', { + amount, + validatorAddress, + delegatorAddress, + }), + ]); + trace('got result', result); + return result; }, /** * Starting a transfer revokes the account holder. The associated updater From a18d21ce5bf539099171f2e8da3bbab6d33a352e Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 29 May 2024 15:20:48 -0700 Subject: [PATCH 15/15] feat(lca): undelegate --- packages/cosmic-proto/src/helpers.ts | 5 ++ .../src/exos/local-chain-account-kit.js | 50 ++++++++++++++++++ .../test/exos/local-chain-account-kit.test.ts | 51 +++++++++++++++++++ packages/vats/tools/fake-bridge.js | 5 ++ 4 files changed, 111 insertions(+) diff --git a/packages/cosmic-proto/src/helpers.ts b/packages/cosmic-proto/src/helpers.ts index 9a2a8170fd8..49d75c0696c 100644 --- a/packages/cosmic-proto/src/helpers.ts +++ b/packages/cosmic-proto/src/helpers.ts @@ -10,6 +10,8 @@ import type { import type { MsgDelegate, MsgDelegateResponse, + MsgUndelegate, + MsgUndelegateResponse, } from './codegen/cosmos/staking/v1beta1/tx.js'; import type { Any } from './codegen/google/protobuf/any.js'; import { @@ -33,6 +35,8 @@ export type Proto3Shape = { '/cosmos.bank.v1beta1.QueryAllBalancesResponse': QueryAllBalancesResponse; '/cosmos.staking.v1beta1.MsgDelegate': MsgDelegate; '/cosmos.staking.v1beta1.MsgDelegateResponse': MsgDelegateResponse; + '/cosmos.staking.v1beta1.MsgUndelegate': MsgUndelegate; + '/cosmos.staking.v1beta1.MsgUndelegateResponse': MsgUndelegateResponse; '/ibc.applications.transfer.v1.MsgTransfer': MsgTransfer; '/ibc.applications.transfer.v1.MsgTransferResponse': MsgTransferResponse; }; @@ -43,6 +47,7 @@ type ResponseMap = { '/cosmos.bank.v1beta1.QueryAllBalancesRequest': '/cosmos.bank.v1beta1.QueryAllBalancesResponse'; '/cosmos.staking.v1beta1.MsgDelegate': '/cosmos.staking.v1beta1.MsgDelegateResponse'; '/ibc.applications.transfer.v1.MsgTransfer': '/ibc.applications.transfer.v1.MsgTransferResponse'; + '/cosmos.staking.v1beta1.MsgUndelegate': '/cosmos.staking.v1beta1.MsgUndelegateResponse'; }; /** diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index 3d0ef0d49e1..0c6a7b8d053 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -12,6 +12,7 @@ import { ChainAddressShape, IBCTransferOptionsShape, } from '../typeGuards.js'; +import { maxClockSkew } from '../utils/cosmos.js'; import { dateInSeconds, makeTimestampHelper } from '../utils/time.js'; /** @@ -44,6 +45,7 @@ const { Fail } = assert; const HolderI = M.interface('holder', { getPublicTopics: M.call().returns(TopicsRecordShape), delegate: M.call(M.string(), AmountShape).returns(M.promise()), + undelegate: M.call(M.string(), AmountShape).returns(M.promise()), deposit: M.callWhen(PaymentShape).returns(AmountShape), withdraw: M.callWhen(AmountShape).returns(PaymentShape), transfer: M.call(AmountArgShape, ChainAddressShape) @@ -84,6 +86,9 @@ export const prepareLocalChainAccountKit = ( holder: HolderI, invitationMakers: M.interface('invitationMakers', { Delegate: M.callWhen(M.string(), AmountShape).returns(InvitationShape), + Undelegate: M.callWhen(M.string(), AmountShape).returns( + InvitationShape, + ), CloseAccount: M.call().returns(M.promise()), }), }, @@ -118,6 +123,20 @@ export const prepareLocalChainAccountKit = ( return this.facets.holder.delegate(validatorAddress, ertpAmount); }, 'Delegate'); }, + + /** + * @param {string} validatorAddress + * @param {Amount<'nat'>} ertpAmount + */ + async Undelegate(validatorAddress, ertpAmount) { + trace('Undelegate', validatorAddress, ertpAmount); + + return zcf.makeInvitation(async seat => { + // TODO should it allow delegating more BLD? + seat.exit(); + return this.facets.holder.undelegate(validatorAddress, ertpAmount); + }, 'Undelegate'); + }, CloseAccount() { throw Error('not yet implemented'); }, @@ -158,6 +177,37 @@ export const prepareLocalChainAccountKit = ( trace('got result', result); return result; }, + /** + * + * @param {string} validatorAddress + * @param {Amount<'nat'>} ertpAmount + * @returns {Promise} + */ + async undelegate(validatorAddress, ertpAmount) { + // TODO #9211 lookup denom from brand + const amount = { + amount: String(ertpAmount.value), + denom: 'ubld', + }; + const { account: lca } = this.state; + trace('lca', lca); + const delegatorAddress = await E(lca).getAddress(); + trace('delegatorAddress', delegatorAddress); + const [response] = await E(lca).executeTx([ + typedJson('/cosmos.staking.v1beta1.MsgUndelegate', { + amount, + validatorAddress, + delegatorAddress, + }), + ]); + trace('undelegate response', response); + const { completionTime } = response; + + await E(timerService).wakeAt( + // TODO clean up date handling once we have real data + dateInSeconds(new Date(completionTime)) + maxClockSkew, + ); + }, /** * Starting a transfer revokes the account holder. The associated updater * will get a special notification that the account is being transferred. diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-chain-account-kit.test.ts index 2fb4ebaccf8..0147054f5b1 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-chain-account-kit.test.ts @@ -77,6 +77,57 @@ test('deposit, withdraw', async t => { ); }); +test('delegate, undelegate', async t => { + const { bootstrap, brands, utils } = await commonSetup(t); + + const { bld } = brands; + + const { timer, localchain, marshaller, rootZone, storage } = bootstrap; + + t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); + const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); + + t.log('exo setup - prepareLocalChainAccountKit'); + const { makeRecorderKit } = prepareRecorderKitMakers( + rootZone.mapStore('recorder'), + marshaller, + ); + const makeLocalChainAccountKit = prepareLocalChainAccountKit( + rootZone, + makeRecorderKit, + // @ts-expect-error mocked zcf. use `stake-bld.contract.test.ts` to test LCA with offer + Far('MockZCF', {}), + timer, + timer.getTimerBrand(), + agoricChainInfo, + ); + + t.log('request account from vat-localchain'); + const lca = await E(localchain).makeAccount(); + const address = await E(lca).getAddress(); + + t.log('make a LocalChainAccountKit'); + const { holder: account } = makeLocalChainAccountKit({ + account: lca, + address, + storageNode: storage.rootNode.makeChildNode('lcaKit'), + }); + + t.regex(await E(account).getAddress(), /agoric1/); + + await E(account).deposit(await utils.pourPayment(bld.units(100))); + + const validatorAddress = 'agoric1validator1'; + + // Because the bridge is fake, + // 1. these succeed even if funds aren't available + // 2. there are no return values + // 3. there are no side-effects such as assets being locked + await E(account).delegate(validatorAddress, bld.units(999)); + // TODO get the timer to fire so that this promise resolves + void E(account).undelegate(validatorAddress, bld.units(999)); +}); + test('transfer', async t => { const { bootstrap, brands, utils } = await commonSetup(t); diff --git a/packages/vats/tools/fake-bridge.js b/packages/vats/tools/fake-bridge.js index cb8c2e3221d..4ebc6257e8d 100644 --- a/packages/vats/tools/fake-bridge.js +++ b/packages/vats/tools/fake-bridge.js @@ -192,6 +192,11 @@ export const makeFakeLocalchainBridge = (zone, onToBridge = () => {}) => { case '/cosmos.staking.v1beta1.MsgDelegate': { return /** @type {JsonSafe} */ ({}); } + case '/cosmos.staking.v1beta1.MsgUndelegate': { + // @ts-expect-error XXX JsonSafe doesn't handle Date + return /** @type {JsonSafe} */ ({ + completionTime: new Date().toJSON(), + }); } // returns one empty object per message unless specified default: