-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
449 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; | ||
|
||
import type { TestFn } from 'ava'; | ||
|
||
import { Fail } from '@agoric/assert'; | ||
import type { start as stakeBldStart } from '@agoric/orchestration/src/contracts/stakeBld.contract.js'; | ||
import type { Instance } from '@agoric/zoe/src/zoeService/utils.js'; | ||
import { makeWalletFactoryContext } from './walletFactory.ts'; | ||
|
||
type DefaultTestContext = Awaited<ReturnType<typeof makeWalletFactoryContext>>; | ||
|
||
const test: TestFn<DefaultTestContext> = anyTest; | ||
|
||
test.before(async t => (t.context = await makeWalletFactoryContext(t))); | ||
test.after.always(t => t.context.shutdown?.()); | ||
|
||
test('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(); | ||
console.log({ current, latest }); | ||
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 }, | ||
}, | ||
}, | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// @ts-check | ||
import { makeHelpers } from '@agoric/deploy-script-support'; | ||
|
||
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ | ||
export const defaultProposalBuilder = async ({ publishRef, install }) => | ||
harden({ | ||
sourceSpec: '@agoric/orchestration/src/proposals/start-stakeBld.js', | ||
getManifestCall: [ | ||
'getManifestForStakeBld', | ||
{ | ||
installKeys: { | ||
stakeBld: publishRef( | ||
install('@agoric/orchestration/src/contracts/stakeBld.contract.js'), | ||
), | ||
}, | ||
}, | ||
], | ||
}); | ||
|
||
export default async (homeP, endowments) => { | ||
const { writeCoreProposal } = await makeHelpers(homeP, endowments); | ||
await writeCoreProposal('start-stakeBld', defaultProposalBuilder); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
packages/orchestration/src/contracts/localchainAccountHolder.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// @ts-check | ||
/** @file Use-object for the owner of a localchain account */ | ||
import { AmountShape } from '@agoric/ertp'; | ||
import { makeTracer } from '@agoric/internal'; | ||
import { UnguardedHelperI } from '@agoric/internal/src/typeGuards.js'; | ||
import { M, prepareExoClassKit } from '@agoric/vat-data'; | ||
import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; | ||
import { E } from '@endo/far'; | ||
|
||
const trace = makeTracer('LCAH'); | ||
|
||
const { Fail } = assert; | ||
/** | ||
* @typedef {object} LocalChainAccountNotification | ||
* @property {string} address | ||
*/ | ||
|
||
/** | ||
* @typedef {{ | ||
* topicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<LocalChainAccountNotification>; | ||
* account: import('@agoric/vats/src/localchain.js').LocalChainAccount | null; | ||
* }} State | ||
*/ | ||
|
||
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()), | ||
makeTransferAccountInvitation: M.call().returns(M.promise()), | ||
}); | ||
|
||
/** @type {{ [name: string]: [description: string, valueShape: Pattern] }} */ | ||
const PUBLIC_TOPICS = { | ||
account: ['Account holder status', M.any()], | ||
}; | ||
|
||
/** | ||
* @param {import('@agoric/ertp').Baggage} baggage | ||
* @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit} makeRecorderKit | ||
* @param {ZCF} zcf | ||
*/ | ||
export const prepareAccountHolder = (baggage, makeRecorderKit, zcf) => { | ||
const makeAccountHolderKit = prepareExoClassKit( | ||
baggage, | ||
'Account Holder', | ||
{ | ||
helper: UnguardedHelperI, | ||
holder: HolderI, | ||
invitationMakers: M.interface('invitationMakers', { | ||
Delegate: HolderI.payload.methodGuards.makeDelegateInvitation, | ||
CloseAccount: HolderI.payload.methodGuards.makeCloseAccountInvitation, | ||
TransferAccount: | ||
HolderI.payload.methodGuards.makeTransferAccountInvitation, | ||
}), | ||
}, | ||
/** | ||
* @param {import('@agoric/vats/src/localchain.js').LocalChainAccount} account | ||
* @param {StorageNode} storageNode | ||
* @returns {State} | ||
*/ | ||
(account, storageNode) => { | ||
// must be the fully synchronous maker because the kit is held in durable state | ||
const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); | ||
|
||
return { account, topicKit }; | ||
}, | ||
{ | ||
helper: { | ||
/** @throws if this holder no longer owns the account */ | ||
owned() { | ||
const { account } = this.state; | ||
if (!account) { | ||
throw Fail`Using account holder after transfer`; | ||
} | ||
return account; | ||
}, | ||
getUpdater() { | ||
return this.state.topicKit.recorder; | ||
}, | ||
}, | ||
invitationMakers: { | ||
Delegate(validatorAddress, amount) { | ||
return this.facets.holder.makeDelegateInvitation( | ||
validatorAddress, | ||
amount, | ||
); | ||
}, | ||
CloseAccount() { | ||
return this.facets.holder.makeCloseAccountInvitation(); | ||
}, | ||
TransferAccount() { | ||
return this.facets.holder.makeTransferAccountInvitation(); | ||
}, | ||
}, | ||
holder: { | ||
getPublicTopics() { | ||
const { topicKit } = this.state; | ||
return harden({ | ||
account: { | ||
description: PUBLIC_TOPICS.account[0], | ||
subscriber: topicKit.subscriber, | ||
storagePath: topicKit.recorder.getStoragePath(), | ||
}, | ||
}); | ||
}, | ||
/** | ||
* | ||
* @param {string} validatorAddress | ||
* @param {Amount<'nat'>} ertpAmount | ||
*/ | ||
async makeDelegateInvitation(validatorAddress, ertpAmount) { | ||
trace('makeDelegateInvitation', validatorAddress, ertpAmount); | ||
|
||
// FIXME get values from proposal or args | ||
// FIXME brand handling and amount scaling | ||
const amount = { | ||
amount: ertpAmount.value, | ||
denom: 'ubld', | ||
}; | ||
|
||
return zcf.makeInvitation(async seat => { | ||
// TODO should it allow delegating more BLD? | ||
seat.exit(); | ||
const lca = this.facets.helper.owned(); | ||
trace('lca', lca); | ||
const delegatorAddress = await E(lca).getAddress(); | ||
trace('delegatorAddress', delegatorAddress); | ||
const result = await E(lca).executeTx([ | ||
{ | ||
'@type': '/cosmos.staking.v1beta1.MsgDelegate', | ||
amount, | ||
validatorAddress, | ||
delegatorAddress, | ||
}, | ||
]); | ||
trace('got result', result); | ||
return result; | ||
}, 'Delegate'); | ||
}, | ||
makeCloseAccountInvitation() { | ||
throw Error('not yet implemented'); | ||
}, | ||
/** | ||
* Starting a transfer revokes the account holder. The associated updater | ||
* will get a special notification that the account is being transferred. | ||
*/ | ||
makeTransferAccountInvitation() { | ||
throw Error('not yet implemented'); | ||
}, | ||
}, | ||
}, | ||
); | ||
return makeAccountHolderKit; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// @ts-check | ||
/** | ||
* @file Stake BLD contract | ||
* | ||
*/ | ||
|
||
import { makeTracer } from '@agoric/internal'; | ||
import { makeDurableZone } from '@agoric/zone/durable.js'; | ||
import { M } from '@endo/patterns'; | ||
import { E } from '@endo/far'; | ||
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; | ||
import { atomicTransfer } from '@agoric/zoe/src/contractSupport/atomicTransfer.js'; | ||
import { prepareAccountHolder } from './localchainAccountHolder.js'; | ||
|
||
const trace = makeTracer('StakeBld'); | ||
|
||
/** | ||
* | ||
* @param {ZCF} zcf | ||
* @param {{ | ||
* localchain: import('@agoric/vats/src/localchain.js').LocalChain; | ||
* marshaller: Marshaller; | ||
* storageNode: StorageNode; | ||
* }} privateArgs | ||
* @param {import("@agoric/vat-data").Baggage} baggage | ||
*/ | ||
export const start = async (zcf, privateArgs, baggage) => { | ||
const { BLD } = zcf.getTerms().brands; | ||
|
||
const bldAmountShape = await E(BLD).getAmountShape(); | ||
|
||
const zone = makeDurableZone(baggage); | ||
|
||
const { makeRecorderKit } = prepareRecorderKitMakers( | ||
baggage, | ||
privateArgs.marshaller, | ||
); | ||
const makeAccountHolderKit = prepareAccountHolder( | ||
baggage, | ||
makeRecorderKit, | ||
zcf, | ||
); | ||
|
||
const publicFacet = zone.exo('StakeBld', undefined, { | ||
makeStakeBldInvitation() { | ||
return zcf.makeInvitation( | ||
async seat => { | ||
const { give } = seat.getProposal(); | ||
trace('makeStakeBldInvitation', give); | ||
// XXX type appears local but it's remote | ||
const account = await E(privateArgs.localchain).createAccount(); | ||
const lcaSeatKit = zcf.makeEmptySeatKit(); | ||
atomicTransfer(zcf, seat, lcaSeatKit.zcfSeat, give); | ||
seat.exit(); | ||
trace('makeStakeBldInvitation tryExit lca userSeat'); | ||
await E(lcaSeatKit.userSeat).tryExit(); | ||
trace('awaiting payouts'); | ||
const payouts = await E(lcaSeatKit.userSeat).getPayouts(); | ||
const { holder, invitationMakers } = makeAccountHolderKit( | ||
account, | ||
privateArgs.storageNode, | ||
); | ||
trace('awaiting deposit'); | ||
await E(account).deposit(await payouts.In); | ||
|
||
return { | ||
publicSubscribers: holder.getPublicTopics(), | ||
invitationMakers, | ||
account: holder, | ||
}; | ||
}, | ||
'wantStake', | ||
undefined, | ||
M.splitRecord({ | ||
give: { In: bldAmountShape }, | ||
}), | ||
); | ||
}, | ||
}); | ||
|
||
return { publicFacet }; | ||
}; |
Oops, something went wrong.