-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(orchestration): deposit ERTP payment to ICA #9342
Changes from all commits
5936a2d
aac8e1c
f22c3fd
6088aa1
37083e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -77,6 +77,11 @@ export const AmountShape = harden({ | |||||||||
value: AmountValueShape, | ||||||||||
}); | ||||||||||
|
||||||||||
export const NatAmountShape = harden({ | ||||||||||
brand: BrandShape, | ||||||||||
value: NatValueShape, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's a |
||||||||||
}); | ||||||||||
|
||||||||||
export const RatioShape = harden({ | ||||||||||
numerator: AmountShape, | ||||||||||
denominator: AmountShape, | ||||||||||
Comment on lines
86
to
87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,3 +1,4 @@ | ||||||
/* eslint-disable camelcase */ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not obvious why |
||||||
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; | ||||||
|
||||||
import type { TestFn } from 'ava'; | ||||||
|
@@ -150,6 +151,7 @@ test.serial('stakeAtom - smart wallet', async t => { | |||||
'agoric1testStakAtom', | ||||||
); | ||||||
|
||||||
// 1. Make Account | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider turning such comments into odd... |
||||||
await wd.executeOffer({ | ||||||
id: 'request-account', | ||||||
invitationSpec: { | ||||||
|
@@ -167,9 +169,131 @@ test.serial('stakeAtom - smart wallet', async t => { | |||||
t.like(wd.getLatestUpdateRecord(), { | ||||||
status: { id: 'request-account', numWantsSatisfied: 1 }, | ||||||
}); | ||||||
|
||||||
const { ATOM } = agoricNamesRemotes.brand; | ||||||
const { ATOM, DAI_axl, BLD } = agoricNamesRemotes.brand; | ||||||
ATOM || Fail`ATOM missing from agoricNames`; | ||||||
DAI_axl || Fail`DAI_axl missing from agoricNames`; | ||||||
BLD || Fail`BLD missing from agoricNames`; | ||||||
|
||||||
// 2. Deposit to Account | ||||||
await wd.executeOffer({ | ||||||
id: 'request-deposit-success', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. success is independent of the offer identity
Suggested change
reading further I see you have other variants. Still I think success or failure are independent of identity. E.g.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tripped over that name/id on 1st reading too. |
||||||
invitationSpec: { | ||||||
source: 'continuing', | ||||||
previousOffer: 'request-account', | ||||||
invitationMakerName: 'Deposit', | ||||||
}, | ||||||
proposal: { | ||||||
give: { | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I/we really should fix that. the IOU an issue, I guess. |
||||||
ATOM: { brand: ATOM, value: 100n }, | ||||||
}, | ||||||
exit: { waived: null }, | ||||||
}, | ||||||
}); | ||||||
t.like(wd.getLatestUpdateRecord(), { | ||||||
status: { id: 'request-deposit-success', numWantsSatisfied: 1 }, | ||||||
}); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. I'd like to see a check that the money got there. Can we capture calls across the bridge and check for a suitable one here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The best we can do in this environment is mock, which doesn't give the highest confidence or seem worth the cost. I am going to pursue better unit testing at the exo level per your suggestion to get more confidence around these failure cases |
||||||
|
||||||
await t.throwsAsync( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good to have these validation tests, but they don't need to integrate with smart-wallet. Spinning up a contract in isolation is a bit of work, I don't know that they even need to integrate with stakeAtom. Could these just be tests of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged and agree: #9342 (comment) |
||||||
wd.executeOffer({ | ||||||
id: 'request-deposit-failure-no-want-allowed', | ||||||
invitationSpec: { | ||||||
source: 'continuing', | ||||||
previousOffer: 'request-account', | ||||||
invitationMakerName: 'Deposit', | ||||||
}, | ||||||
proposal: { | ||||||
give: { | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
ATOM: { brand: ATOM, value: 100n }, | ||||||
}, | ||||||
want: { | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
BLD: { brand: BLD, value: 100n }, | ||||||
}, | ||||||
exit: { waived: null }, | ||||||
}, | ||||||
}), | ||||||
{ | ||||||
message: /proposal: want(.*?)Must be: {}/, | ||||||
}, | ||||||
); | ||||||
|
||||||
await t.throwsAsync( | ||||||
wd.executeOffer({ | ||||||
id: 'request-deposit-failure-two-give-amounts', | ||||||
invitationSpec: { | ||||||
source: 'continuing', | ||||||
previousOffer: 'request-account', | ||||||
invitationMakerName: 'Deposit', | ||||||
}, | ||||||
proposal: { | ||||||
give: { | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
ATOM: { brand: ATOM, value: 100n }, | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
BLD: { brand: BLD, value: 100n }, | ||||||
}, | ||||||
exit: { waived: null }, | ||||||
}, | ||||||
}), | ||||||
{ | ||||||
message: /proposal: give: Must not have more than 1 properties/, | ||||||
}, | ||||||
); | ||||||
|
||||||
await t.throwsAsync( | ||||||
wd.executeOffer({ | ||||||
id: 'request-deposit-failure-unknown-issuer', | ||||||
invitationSpec: { | ||||||
source: 'continuing', | ||||||
previousOffer: 'request-account', | ||||||
invitationMakerName: 'Deposit', | ||||||
}, | ||||||
proposal: { | ||||||
give: { | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
DAI_axl: { brand: DAI_axl, value: 100n }, | ||||||
}, | ||||||
exit: { waived: null }, | ||||||
}, | ||||||
}), | ||||||
{ | ||||||
message: /brand(.*?)not registered/, | ||||||
}, | ||||||
); | ||||||
|
||||||
await t.throwsAsync( | ||||||
wd.executeOffer({ | ||||||
id: 'request-deposit-failure-transfer-packet-timeout', | ||||||
invitationSpec: { | ||||||
source: 'continuing', | ||||||
previousOffer: 'request-account', | ||||||
invitationMakerName: 'Deposit', | ||||||
}, | ||||||
proposal: { | ||||||
give: { | ||||||
// @ts-expect-error BoardRemote is not assignable to Brand<any> | ||||||
ATOM: { brand: ATOM, value: 504n }, | ||||||
}, | ||||||
exit: { waived: null }, | ||||||
}, | ||||||
}), | ||||||
{ | ||||||
message: 'Deposit failed, payment returned.', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, it would be good to have evidence that the right vbank messages went over the bridge ah... but we do have |
||||||
}, | ||||||
); | ||||||
t.like(wd.getLatestUpdateRecord(), { | ||||||
status: { | ||||||
id: 'request-deposit-failure-transfer-packet-timeout', | ||||||
numWantsSatisfied: 1, | ||||||
payouts: { | ||||||
ATOM: { value: 504n }, | ||||||
}, | ||||||
}, | ||||||
}); | ||||||
|
||||||
// 3. Delegate from Account to Validator | ||||||
const validatorAddress: CosmosValidatorAddress = { | ||||||
address: 'cosmosvaloper1test', | ||||||
chainId: 'gaiatest', | ||||||
|
@@ -192,12 +316,12 @@ test.serial('stakeAtom - smart wallet', async t => { | |||||
status: { id: 'request-delegate-success', numWantsSatisfied: 1 }, | ||||||
}); | ||||||
|
||||||
// 4. Delegate Failure (invalid validator address or amount) | ||||||
const validatorAddressFail: CosmosValidatorAddress = { | ||||||
address: 'cosmosvaloper1fail', | ||||||
chainId: 'gaiatest', | ||||||
addressEncoding: 'bech32', | ||||||
}; | ||||||
|
||||||
await t.throwsAsync( | ||||||
wd.executeOffer({ | ||||||
id: 'request-delegate-fail', | ||||||
|
@@ -215,3 +339,5 @@ test.serial('stakeAtom - smart wallet', async t => { | |||||
'delegate fails with invalid validator', | ||||||
); | ||||||
}); | ||||||
|
||||||
test.todo('deposit to LCA fails, payment should be returned'); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently only simulating failed LCA -> ICA transfer. This captures the last nested block of the |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -295,6 +295,7 @@ export const makeSwingsetTestKit = async ( | |||||
|
||||||
let inbound; | ||||||
let ibcSequenceNonce = 0; | ||||||
let icaExecuteTxSequence = 0; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
const makeAckEvent = (obj: IBCMethod<'sendPacket'>, ack: string) => { | ||||||
ibcSequenceNonce += 1; | ||||||
|
@@ -429,9 +430,25 @@ export const makeSwingsetTestKit = async ( | |||||
switch (obj.type) { | ||||||
case 'VLOCALCHAIN_ALLOCATE_ADDRESS': | ||||||
return 'agoric1mockVlocalchainAddress'; | ||||||
case 'VLOCALCHAIN_EXECUTE_TX': | ||||||
// returns one empty object per message | ||||||
return obj.messages.map(() => ({})); | ||||||
case 'VLOCALCHAIN_EXECUTE_TX': { | ||||||
icaExecuteTxSequence += 1; | ||||||
// returns one empty object per message unless specified | ||||||
return obj.messages.map(message => { | ||||||
switch (message['@type']) { | ||||||
case '/ibc.applications.transfer.v1.MsgTransfer': { | ||||||
if (message.token.amount === '504') { | ||||||
throw Error('simulated MsgTransfer packet timeout'); | ||||||
} | ||||||
// like `JsonSafe<MsgTransferResponse>`, but bigints are converted to numbers | ||||||
return { | ||||||
sequence: icaExecuteTxSequence, | ||||||
}; | ||||||
} | ||||||
default: | ||||||
return {}; | ||||||
} | ||||||
}); | ||||||
} | ||||||
default: | ||||||
throw Error(`VLOCALCHAIN message of unknown type ${obj.type}`); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,16 @@ export const defaultProposalBuilder = async ( | |
) => { | ||
const { | ||
hostConnectionId = 'connection-1', | ||
controllerConnectionId = 'connection-0', | ||
controllerConnectionId = 'connection-1', | ||
bondDenom = 'uatom', | ||
bondDenomLocal = 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would be nice to get this from hashing... or to test that this matches what we get from hashing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree. Something i think we'll tackle as part of #9211 |
||
transferChannel = { | ||
counterpartyChannelId: 'channel-1', | ||
counterpartyPortId: 'transfer', | ||
sourceChannelId: 'channel-1', | ||
sourcePortId: 'transfer', | ||
}, | ||
icqEnabled = true, | ||
} = options; | ||
return harden({ | ||
sourceSpec: '@agoric/orchestration/src/proposals/start-stakeAtom.js', | ||
|
@@ -23,6 +31,9 @@ export const defaultProposalBuilder = async ( | |
hostConnectionId, | ||
controllerConnectionId, | ||
bondDenom, | ||
bondDenomLocal, | ||
transferChannel, | ||
icqEnabled, | ||
}, | ||
], | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ | |
"@endo/patterns": "^1.3.1" | ||
}, | ||
"devDependencies": { | ||
"@agoric/swingset-vat": "^0.32.2", | ||
"@cosmjs/amino": "^0.32.3", | ||
"@cosmjs/proto-signing": "^0.32.3", | ||
"@endo/ses-ava": "^1.2.1", | ||
|
@@ -60,7 +61,8 @@ | |
}, | ||
"files": [ | ||
"test/**/*.test.js", | ||
"test/**/*.test.ts" | ||
"test/**/*.test.ts", | ||
"test/**/test-*.js" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reminds me to revive #8653 |
||
], | ||
"nodeArguments": [ | ||
"--loader=tsx", | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,34 +11,57 @@ import { prepareStakingAccountKit } from '../exos/stakingAccountKit.js'; | |||||||||||||
|
||||||||||||||
const trace = makeTracer('StakeAtom'); | ||||||||||||||
/** | ||||||||||||||
* @import { Baggage } from '@agoric/vat-data'; | ||||||||||||||
* @import { IBCConnectionID } from '@agoric/vats'; | ||||||||||||||
* @import { ICQConnection, OrchestrationService } from '../types.js'; | ||||||||||||||
* @import {Baggage} from '@agoric/vat-data'; | ||||||||||||||
* @import {IBCConnectionID} from '@agoric/vats'; | ||||||||||||||
* @import {LocalChain} from '@agoric/vats/src/localchain.js'; | ||||||||||||||
* @import {IBCChannelInfo, OrchestrationService, BrandToIssuer} from '@agoric/orchestration'; | ||||||||||||||
* @import {TimerBrand, TimerService} from '@agoric/time' | ||||||||||||||
*/ | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* @typedef {{ | ||||||||||||||
* hostConnectionId: IBCConnectionID; | ||||||||||||||
* controllerConnectionId: IBCConnectionID; | ||||||||||||||
* bondDenom: string; | ||||||||||||||
* bondDenomLocal: string; | ||||||||||||||
* transferChannel: IBCChannelInfo; | ||||||||||||||
* icqEnabled: boolean; | ||||||||||||||
* chainTimerBrand: TimerBrand; | ||||||||||||||
* }} StakeAtomTerms | ||||||||||||||
*/ | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* | ||||||||||||||
* @param {ZCF<StakeAtomTerms>} zcf | ||||||||||||||
* @param {{ | ||||||||||||||
* localchain: LocalChain; | ||||||||||||||
* orchestration: OrchestrationService; | ||||||||||||||
* storageNode: StorageNode; | ||||||||||||||
* marshaller: Marshaller; | ||||||||||||||
* chainTimerService: TimerService; | ||||||||||||||
* }} privateArgs | ||||||||||||||
* @param {Baggage} baggage | ||||||||||||||
*/ | ||||||||||||||
export const start = async (zcf, privateArgs, baggage) => { | ||||||||||||||
// TODO #9063 this roughly matches what we'll get from Chain<C>.getChainInfo() | ||||||||||||||
const { hostConnectionId, controllerConnectionId, bondDenom } = | ||||||||||||||
zcf.getTerms(); | ||||||||||||||
const { orchestration, marshaller, storageNode } = privateArgs; | ||||||||||||||
const { | ||||||||||||||
hostConnectionId, | ||||||||||||||
controllerConnectionId, | ||||||||||||||
bondDenom, | ||||||||||||||
bondDenomLocal, | ||||||||||||||
transferChannel, | ||||||||||||||
issuers, | ||||||||||||||
brands, | ||||||||||||||
icqEnabled, | ||||||||||||||
chainTimerBrand, | ||||||||||||||
} = zcf.getTerms(); | ||||||||||||||
const { | ||||||||||||||
localchain, | ||||||||||||||
orchestration, | ||||||||||||||
marshaller, | ||||||||||||||
storageNode, | ||||||||||||||
chainTimerService, | ||||||||||||||
} = privateArgs; | ||||||||||||||
|
||||||||||||||
const zone = makeDurableZone(baggage); | ||||||||||||||
|
||||||||||||||
|
@@ -50,25 +73,40 @@ export const start = async (zcf, privateArgs, baggage) => { | |||||||||||||
zcf, | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
/** @type {BrandToIssuer} */ | ||||||||||||||
const brandToIssuer = zone.mapStore('brandToIssuer'); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a durable store is over-kill for this. it costs a syscall for each access. |
||||||||||||||
for (const [keyword, brand] of Object.entries(brands)) { | ||||||||||||||
brandToIssuer.init(brand, issuers[keyword]); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async function makeAccount() { | ||||||||||||||
const account = await E(orchestration).makeAccount( | ||||||||||||||
hostConnectionId, | ||||||||||||||
controllerConnectionId, | ||||||||||||||
); | ||||||||||||||
// #9212 TODO do not fail if host does not have `async-icq` module; | ||||||||||||||
// communicate to OrchestrationAccount that it can't send queries | ||||||||||||||
const icqConnection = await E(orchestration).provideICQConnection( | ||||||||||||||
controllerConnectionId, | ||||||||||||||
); | ||||||||||||||
const accountAddress = await E(account).getAddress(); | ||||||||||||||
trace('account address', accountAddress); | ||||||||||||||
const { holder, invitationMakers } = makeStakingAccountKit( | ||||||||||||||
|
||||||||||||||
// TODO #9063, #9212 this should come from Chain object | ||||||||||||||
const icqConnection = icqEnabled | ||||||||||||||
? await E(orchestration).provideICQConnection(controllerConnectionId) | ||||||||||||||
: undefined; | ||||||||||||||
Comment on lines
+89
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's ok by the new await safety style, but I've picked up an aversion to code that sometimes represents a turn boundary and sometimes doesn't.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also reminded to check for a clear commit point, as in the prepare / commit pattern. If |
||||||||||||||
|
||||||||||||||
const localAccount = await E(localchain).makeAccount(); | ||||||||||||||
const localAccountAddress = await E(localAccount).getAddress(); | ||||||||||||||
const chainAddress = await E(account).getAddress(); | ||||||||||||||
const { holder, invitationMakers } = makeStakingAccountKit({ | ||||||||||||||
account, | ||||||||||||||
localAccount, | ||||||||||||||
storageNode, | ||||||||||||||
accountAddress, | ||||||||||||||
chainAddress, | ||||||||||||||
localAccountAddress, | ||||||||||||||
icqConnection, | ||||||||||||||
bondDenom, | ||||||||||||||
); | ||||||||||||||
bondDenomLocal, | ||||||||||||||
transferChannel, | ||||||||||||||
brandToIssuer, | ||||||||||||||
chainTimerService, | ||||||||||||||
chainTimerBrand, | ||||||||||||||
}); | ||||||||||||||
return { | ||||||||||||||
publicSubscribers: holder.getPublicTopics(), | ||||||||||||||
invitationMakers, | ||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.