-
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
1 parent
6dd673e
commit 50ac778
Showing
8 changed files
with
613 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import anyTest from '@endo/ses-ava/prepare-endo.js'; | ||
import type { TestFn } from 'ava'; | ||
import { makeDoOffer } from '../tools/e2e-tools.js'; | ||
import { commonSetup, type SetupContextWithWallets } from './support.js'; | ||
import { | ||
createFundedWalletAndClient, | ||
DEFAULT_TIMEOUT_NS, | ||
} from '../tools/ibc-transfer.js'; | ||
import { createWallet } from '../tools/wallet.js'; | ||
import type { ChainAddress } from '@agoric/orchestration'; | ||
import { MsgGrant } from '@agoric/cosmic-proto/cosmos/authz/v1beta1/tx.js'; | ||
import { SendAuthorization } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/authz.js'; | ||
import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; | ||
|
||
const test = anyTest as TestFn<SetupContextWithWallets>; | ||
|
||
const accounts = ['alice']; | ||
|
||
const contractName = 'authzExample'; | ||
const contractBuilder = | ||
'../packages/builders/scripts/orchestration/init-authz-example.js'; | ||
|
||
test.before(async t => { | ||
const { setupTestKeys, ...common } = await commonSetup(t); | ||
const { commonBuilderOpts, deleteTestKeys, startContract } = common; | ||
deleteTestKeys(accounts).catch(); | ||
const wallets = await setupTestKeys(accounts); | ||
t.context = { ...common, wallets }; | ||
await startContract(contractName, contractBuilder, commonBuilderOpts); | ||
}); | ||
|
||
test('authz-example', async t => { | ||
const { | ||
provisionSmartWallet, | ||
retryUntilCondition, | ||
useChain, | ||
vstorageClient, | ||
wallets, | ||
} = t.context; | ||
|
||
const toChainAddress = ( | ||
value: string, | ||
chainName = 'cosmoshub', | ||
): ChainAddress => | ||
harden({ | ||
encoding: 'bech32', | ||
value, | ||
chainId: useChain(chainName).chain.chain_id, | ||
}); | ||
|
||
// set up "existing account", which will issue MsgGrant | ||
const keplrAccount = await createFundedWalletAndClient( | ||
t.log, | ||
'cosmoshub', | ||
useChain, | ||
); | ||
const keplrAddress = toChainAddress(keplrAccount.address); | ||
t.log('existing account address', keplrAccount.address); | ||
|
||
// provision agoric smart wallet to submit offers | ||
// unrelated to wallet above, but we can consider them the same entity | ||
const wdUser = await provisionSmartWallet(wallets['alice'], { | ||
BLD: 100n, | ||
IST: 100n, | ||
}); | ||
const doOffer = makeDoOffer(wdUser); | ||
|
||
// request an orchestration account | ||
const offerId = `cosmoshub-makeAccount-${Date.now()}`; | ||
doOffer({ | ||
id: offerId, | ||
invitationSpec: { | ||
source: 'agoricContract', | ||
instancePath: [contractName], | ||
callPipe: [['makeAccount']], | ||
}, | ||
offerArgs: { chainName: 'cosmoshub' }, | ||
proposal: {}, | ||
}); | ||
const currentWalletRecord = await retryUntilCondition( | ||
() => | ||
vstorageClient.queryData(`published.wallet.${wallets['alice']}.current`), | ||
({ offerToPublicSubscriberPaths }) => | ||
Object.fromEntries(offerToPublicSubscriberPaths)[offerId], | ||
`${offerId} continuing invitation is in vstorage`, | ||
); | ||
const offerToPublicSubscriberMap = Object.fromEntries( | ||
currentWalletRecord.offerToPublicSubscriberPaths, | ||
); | ||
const address = offerToPublicSubscriberMap[offerId]?.account.split('.').pop(); | ||
t.log('Got orch account address:', address); | ||
|
||
// generate MsgGrant with SendAuthorization for orch account address and broadcast | ||
const grantMsg = MsgGrant.toProtoMsg({ | ||
grantee: address, | ||
granter: keplrAddress.value, | ||
grant: { | ||
// @ts-expect-error the types don't like this, but i think its right | ||
authorization: SendAuthorization.toProtoMsg({ | ||
spendLimit: [ | ||
{ | ||
denom: 'uatom', | ||
amount: '10', | ||
}, | ||
], | ||
}), | ||
expiration: { | ||
nanos: 0, | ||
// TODO calculate something more realistic; this is really far in the future | ||
seconds: DEFAULT_TIMEOUT_NS, | ||
}, | ||
}, | ||
}); | ||
|
||
const msg = TxBody.encode( | ||
TxBody.fromPartial({ | ||
messages: [grantMsg], | ||
// todo, use actual block height | ||
// timeoutHeight: 9999999999n, | ||
}), | ||
).finish(); | ||
|
||
// FIXME, not working. suspicions: | ||
// 1. MsgExec is not registered in the stargate clients proto registry | ||
// 2. grantMsg, TxBody, are not formed correctly | ||
const res = await keplrAccount.client.broadcastTx(msg); | ||
This comment has been minimized.
Sorry, something went wrong. |
||
console.log('res', res); | ||
t.is(res.code, 0); | ||
|
||
// create a wallet to be our recipient | ||
const exchangeWallet = await createWallet('cosmos'); | ||
const exchangeAddress = toChainAddress( | ||
(await exchangeWallet.getAccounts())[0].address, | ||
); | ||
|
||
// submit MsgExec([MsgSend]) | ||
await doOffer({ | ||
id: `exec-send-${Date.now()}`, | ||
invitationSpec: { | ||
source: 'continuing', | ||
previousOffer: offerId, | ||
invitationMakerName: 'ExecSend', | ||
invitationArgs: [ | ||
[{ amount: 10n, denom: 'uatom' }], | ||
exchangeAddress, | ||
keplrAddress, | ||
], | ||
}, | ||
proposal: {}, | ||
}); | ||
|
||
// TODO verify exchangeAddress balance increases by 10 uatom | ||
}); |
67 changes: 67 additions & 0 deletions
67
packages/builders/scripts/orchestration/init-authz-example.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,67 @@ | ||
import { makeHelpers } from '@agoric/deploy-script-support'; | ||
import { startAuthzExample } from '@agoric/orchestration/src/proposals/start-authz-example.js'; | ||
import { parseArgs } from 'node:util'; | ||
|
||
/** | ||
* @import {ParseArgsConfig} from 'node:util' | ||
*/ | ||
|
||
/** @type {ParseArgsConfig['options']} */ | ||
const parserOpts = { | ||
chainInfo: { type: 'string' }, | ||
assetInfo: { type: 'string' }, | ||
}; | ||
|
||
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ | ||
export const defaultProposalBuilder = async ( | ||
{ publishRef, install }, | ||
options, | ||
) => { | ||
return harden({ | ||
sourceSpec: '@agoric/orchestration/src/proposals/start-authz-example.js', | ||
getManifestCall: [ | ||
'getManifestForContract', | ||
{ | ||
installKeys: { | ||
authzExample: publishRef( | ||
install( | ||
'@agoric/orchestration/src/examples/authz-example.contract.js', | ||
), | ||
), | ||
}, | ||
options, | ||
}, | ||
], | ||
}); | ||
}; | ||
|
||
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ | ||
export default async (homeP, endowments) => { | ||
const { scriptArgs } = endowments; | ||
|
||
const { | ||
values: { chainInfo, assetInfo }, | ||
} = parseArgs({ | ||
args: scriptArgs, | ||
options: parserOpts, | ||
}); | ||
|
||
const parseChainInfo = () => { | ||
if (typeof chainInfo !== 'string') return undefined; | ||
return JSON.parse(chainInfo); | ||
}; | ||
const parseAssetInfo = () => { | ||
if (typeof assetInfo !== 'string') return undefined; | ||
return JSON.parse(assetInfo); | ||
}; | ||
const opts = harden({ | ||
chainInfo: parseChainInfo(), | ||
assetInfo: parseAssetInfo(), | ||
}); | ||
|
||
const { writeCoreEval } = await makeHelpers(homeP, endowments); | ||
|
||
await writeCoreEval(startAuthzExample.name, utils => | ||
defaultProposalBuilder(utils, opts), | ||
); | ||
}; |
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
134 changes: 134 additions & 0 deletions
134
packages/orchestration/src/examples/authz-example.contract.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,134 @@ | ||
/** | ||
* @file This contract demonstrates using AuthZ to control an existing account | ||
* with an orchestration account. | ||
* | ||
* `MsgExec` is only part of the story, please see | ||
* {@link ../../../../multichain-testing/test/auth-z-example.test.ts} for an | ||
* example of client usage which involves `MsgGrant` txs from the grantee. | ||
*/ | ||
import { M } from '@endo/patterns'; | ||
import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js'; | ||
import { CosmosOrchestrationInvitationMakersI } from '../exos/cosmos-orchestration-account.js'; | ||
import { AmountArgShape, ChainAddressShape } from '../typeGuards.js'; | ||
import { withOrchestration } from '../utils/start-helper.js'; | ||
import * as flows from './authz-example.flows.js'; | ||
import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js'; | ||
|
||
/** | ||
* @import {GuestInterface} from '@agoric/async-flow'; | ||
* @import {Zone} from '@agoric/zone'; | ||
* @import {OrchestrationTools, OrchestrationPowers} from '../utils/start-helper.js'; | ||
* @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; | ||
* @import {AmountArg, ChainAddress, CosmosChainInfo, Denom, DenomDetail} from '../types.js'; | ||
*/ | ||
|
||
const emptyOfferShape = harden({ | ||
// Nothing to give; the funds are deposited offline | ||
give: {}, | ||
want: {}, // UNTIL https://github.com/Agoric/agoric-sdk/issues/2230 | ||
exit: M.any(), | ||
}); | ||
|
||
/** | ||
* Orchestration contract to be wrapped by withOrchestration for Zoe. | ||
* | ||
* @param {ZCF} zcf | ||
* @param {OrchestrationPowers & { | ||
* marshaller: Marshaller; | ||
* chainInfo: Record<string, CosmosChainInfo>; | ||
* assetInfo: [Denom, DenomDetail & { brandKey?: string }][]; | ||
* }} privateArgs | ||
* @param {Zone} zone | ||
* @param {OrchestrationTools} tools | ||
*/ | ||
const contract = async ( | ||
zcf, | ||
privateArgs, | ||
zone, | ||
{ orchestrateAll, zoeTools, chainHub }, | ||
) => { | ||
const AuthzExampleInvitationMakersI = M.interface( | ||
'AuthzExampleInvitationMakersI', | ||
{ | ||
ExecSend: M.call( | ||
M.arrayOf(AmountArgShape), | ||
ChainAddressShape, | ||
ChainAddressShape, | ||
).returns(M.promise()), | ||
}, | ||
); | ||
|
||
/** @type {any} XXX async membrane */ | ||
const makeExtraInvitationMaker = zone.exoClass( | ||
'AuthzExampleInvitationMakers', | ||
AuthzExampleInvitationMakersI, | ||
/** | ||
* @param {GuestInterface<CosmosOrchestrationAccount>} account | ||
* @param {string} chainName | ||
*/ | ||
(account, chainName) => { | ||
return { account, chainName }; | ||
}, | ||
{ | ||
/** | ||
* @param {AmountArg[]} amounts | ||
* @param {ChainAddress} destination | ||
* @param {ChainAddress} grantee | ||
*/ | ||
ExecSend(amounts, destination, grantee) { | ||
const { account } = this.state; | ||
|
||
return zcf.makeInvitation( | ||
seat => | ||
orchFns.execSend(account, seat, { | ||
amounts, | ||
destination, | ||
grantee, | ||
}), | ||
'Exec Send', | ||
undefined, | ||
emptyOfferShape, | ||
); | ||
}, | ||
}, | ||
); | ||
|
||
/** @type {any} XXX async membrane */ | ||
const makeCombineInvitationMakers = prepareCombineInvitationMakers( | ||
zone, | ||
CosmosOrchestrationInvitationMakersI, | ||
AuthzExampleInvitationMakersI, | ||
); | ||
|
||
const orchFns = orchestrateAll(flows, { | ||
chainHub, | ||
makeCombineInvitationMakers, | ||
makeExtraInvitationMaker, | ||
flows, | ||
zoeTools, | ||
}); | ||
|
||
/** | ||
* Provide invitations to contract deployer for registering assets and chains | ||
* in the local ChainHub for this contract. | ||
*/ | ||
const creatorFacet = prepareChainHubAdmin(zone, chainHub); | ||
|
||
const publicFacet = zone.exo('publicFacet', undefined, { | ||
makeAccount() { | ||
return zcf.makeInvitation( | ||
orchFns.makeAccount, | ||
'Make an Orchestration account', | ||
undefined, | ||
emptyOfferShape, | ||
); | ||
}, | ||
}); | ||
|
||
return harden({ publicFacet, creatorFacet }); | ||
}; | ||
|
||
export const start = withOrchestration(contract); | ||
harden(start); | ||
|
||
/** @typedef {typeof start} AuthzExampleSF */ |
Oops, something went wrong.
broadcastTx
was giving us issues withMsgTransfer
in earlier work. Maybe there's a better story here after #9200.