-
Notifications
You must be signed in to change notification settings - Fork 208
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: multichain test of auto-stake-it
- auto-stake-it transfer tokens to an InterchainAccount and delegates them when received over IBC to a contract controlled account - includes patches for @cosmjs/stargate, since we are using it to execute IBC transfers from external accounts - refs: #9042
- Loading branch information
1 parent
315c42d
commit 4ce37d6
Showing
11 changed files
with
467 additions
and
15 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,44 @@ | ||
diff --git a/node_modules/axios/dist/node/axios.cjs b/node_modules/axios/dist/node/axios.cjs | ||
index 9099d87..7104f6e 100644 | ||
--- a/node_modules/axios/dist/node/axios.cjs | ||
+++ b/node_modules/axios/dist/node/axios.cjs | ||
@@ -370,9 +370,9 @@ function merge(/* obj1, obj2, obj3, ... */) { | ||
const extend = (a, b, thisArg, {allOwnKeys}= {}) => { | ||
forEach(b, (val, key) => { | ||
if (thisArg && isFunction(val)) { | ||
- a[key] = bind(val, thisArg); | ||
+ Object.defineProperty(a, key, {value: bind(val, thisArg)}); | ||
} else { | ||
- a[key] = val; | ||
+ Object.defineProperty(a, key, {value: val}); | ||
} | ||
}, {allOwnKeys}); | ||
return a; | ||
@@ -403,7 +403,9 @@ const stripBOM = (content) => { | ||
*/ | ||
const inherits = (constructor, superConstructor, props, descriptors) => { | ||
constructor.prototype = Object.create(superConstructor.prototype, descriptors); | ||
- constructor.prototype.constructor = constructor; | ||
+ Object.defineProperty(constructor, 'constructor', { | ||
+ value: constructor | ||
+ }); | ||
Object.defineProperty(constructor, 'super', { | ||
value: superConstructor.prototype | ||
}); | ||
@@ -565,12 +567,14 @@ const isRegExp = kindOfTest('RegExp'); | ||
|
||
const reduceDescriptors = (obj, reducer) => { | ||
const descriptors = Object.getOwnPropertyDescriptors(obj); | ||
- const reducedDescriptors = {}; | ||
+ let reducedDescriptors = {}; | ||
|
||
forEach(descriptors, (descriptor, name) => { | ||
let ret; | ||
if ((ret = reducer(descriptor, name, obj)) !== false) { | ||
- reducedDescriptors[name] = ret || descriptor; | ||
+ reducedDescriptors = {...reducedDescriptors, | ||
+ [name]: ret || descriptor | ||
+ }; | ||
} | ||
}); | ||
|
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,56 @@ | ||
diff --git a/node_modules/protobufjs/src/util/minimal.js b/node_modules/protobufjs/src/util/minimal.js | ||
index 7f62daa..8d60657 100644 | ||
--- a/node_modules/protobufjs/src/util/minimal.js | ||
+++ b/node_modules/protobufjs/src/util/minimal.js | ||
@@ -259,14 +259,9 @@ util.newError = newError; | ||
* @returns {Constructor<Error>} Custom error constructor | ||
*/ | ||
function newError(name) { | ||
- | ||
function CustomError(message, properties) { | ||
- | ||
if (!(this instanceof CustomError)) | ||
return new CustomError(message, properties); | ||
- | ||
- // Error.call(this, message); | ||
- // ^ just returns a new error instance because the ctor can be called as a function | ||
|
||
Object.defineProperty(this, "message", { get: function() { return message; } }); | ||
|
||
@@ -280,13 +275,31 @@ function newError(name) { | ||
merge(this, properties); | ||
} | ||
|
||
- (CustomError.prototype = Object.create(Error.prototype)).constructor = CustomError; | ||
+ // Create a new object with Error.prototype as its prototype | ||
+ const proto = Object.create(Error.prototype); | ||
|
||
- Object.defineProperty(CustomError.prototype, "name", { get: function() { return name; } }); | ||
+ // Define properties on the prototype | ||
+ Object.defineProperties(proto, { | ||
+ constructor: { | ||
+ value: CustomError, | ||
+ writable: true, | ||
+ configurable: true | ||
+ }, | ||
+ name: { | ||
+ get: function() { return name; }, | ||
+ configurable: true | ||
+ }, | ||
+ toString: { | ||
+ value: function toString() { | ||
+ return this.name + ": " + this.message; | ||
+ }, | ||
+ writable: true, | ||
+ configurable: true | ||
+ } | ||
+ }); | ||
|
||
- CustomError.prototype.toString = function toString() { | ||
- return this.name + ": " + this.message; | ||
- }; | ||
+ // Set the prototype of CustomError | ||
+ CustomError.prototype = proto; | ||
|
||
return CustomError; | ||
} |
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,231 @@ | ||
import anyTest from '@endo/ses-ava/prepare-endo.js'; | ||
import type { ExecutionContext, TestFn } from 'ava'; | ||
import { useChain } from 'starshipjs'; | ||
import type { ChainName, SetupContextWithWallets } from './support.js'; | ||
import { chainConfig, chainNames, commonSetup } from './support.js'; | ||
import { makeQueryClient } from '../tools/query.js'; | ||
import { makeDoOffer } from '../tools/e2e-tools.js'; | ||
import chainInfo from '../starship-chain-info.js'; | ||
import { | ||
createFundedWalletAndClient, | ||
makeIBCTransferMsg, | ||
} from '../tools/ibc-transfer.js'; | ||
|
||
const test = anyTest as TestFn<SetupContextWithWallets>; | ||
|
||
const accounts = ['admin1', 'user1', 'user2']; | ||
|
||
const contractName = 'autoAutoStakeIt'; | ||
const contractBuilder = | ||
'../packages/builders/scripts/testing/start-auto-stake-it.js'; | ||
|
||
test.before(async t => { | ||
const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); | ||
deleteTestKeys(accounts).catch(); | ||
const wallets = await setupTestKeys(accounts); | ||
t.context = { ...rest, wallets, deleteTestKeys }; | ||
|
||
t.log('bundle and install contract', contractName); | ||
await t.context.deployBuilder(contractBuilder); | ||
const vstorageClient = t.context.makeQueryTool(); | ||
await t.context.retryUntilCondition( | ||
() => vstorageClient.queryData(`published.agoricNames.instance`), | ||
res => contractName in Object.fromEntries(res), | ||
`${contractName} instance is available`, | ||
); | ||
}); | ||
|
||
test.after(async t => { | ||
const { deleteTestKeys } = t.context; | ||
deleteTestKeys(accounts); | ||
}); | ||
|
||
const makeFundAndTransfer = (t: ExecutionContext<SetupContextWithWallets>) => { | ||
const { retryUntilCondition } = t.context; | ||
return async (chainName: ChainName, agoricAddr: string, amount = 100n) => { | ||
const { staking } = useChain(chainName).chainInfo.chain; | ||
const denom = staking?.staking_tokens?.[0].denom; | ||
if (!denom) throw Error(`no denom for ${chainName}`); | ||
|
||
const { client, address, wallet } = await createFundedWalletAndClient( | ||
t, | ||
chainName, | ||
); | ||
const balancesResult = await retryUntilCondition( | ||
() => client.getAllBalances(address), | ||
coins => !!coins?.length, | ||
'Faucet balances found', | ||
); | ||
|
||
console.log('Balances:', balancesResult); | ||
|
||
const transferArgs = makeIBCTransferMsg( | ||
{ denom, value: amount }, | ||
{ address: agoricAddr, chainName: 'agoric' }, | ||
{ address: address, chainName }, | ||
Date.now(), | ||
); | ||
// TODO #9200 `sendIbcTokens` does not support `memo` | ||
// @ts-expect-error spread argument for concise code | ||
const txRes = await client.sendIbcTokens(...transferArgs); | ||
if (txRes && txRes.code !== 0) { | ||
console.error(txRes); | ||
throw Error(`failed to send funds to ${chainName}`); | ||
} | ||
const { events: _events, ...txRest } = txRes; | ||
console.log(txRest); | ||
t.is(txRes.code, 0, `Transaction succeeded`); | ||
t.log(`Funds transferred to ${agoricAddr}`); | ||
return { | ||
client, | ||
address, | ||
wallet, | ||
}; | ||
}; | ||
}; | ||
|
||
const autoStakeItScenario = test.macro({ | ||
title: (_, chainName: ChainName) => `auto-stake-it on ${chainName}`, | ||
exec: async (t, chainName: ChainName) => { | ||
const { | ||
wallets, | ||
makeQueryTool, | ||
provisionSmartWallet, | ||
retryUntilCondition, | ||
} = t.context; | ||
|
||
const fundAndTransfer = makeFundAndTransfer(t); | ||
|
||
// 1. Send initial tokens so denom is available (debatably necessary, but | ||
// allows us to trace the denom until we have ibc denoms in chainInfo) | ||
const agAdminAddr = wallets['admin1']; | ||
console.log('Sending tokens to', agAdminAddr, `from ${chainName}`); | ||
await fundAndTransfer(chainName, agAdminAddr); | ||
|
||
// 2. Find 'stakingDenom' denom on agoric | ||
const remoteChainInfo = useChain(chainName).chainInfo; | ||
const { portId, channelId } = | ||
chainInfo['agoric'].connections[remoteChainInfo.chain.chain_id] | ||
.transferChannel; | ||
const agoricQueryClient = makeQueryClient( | ||
useChain('agoric').getRestEndpoint(), | ||
); | ||
const { hash } = await retryUntilCondition( | ||
() => | ||
agoricQueryClient.queryDenom( | ||
`/${portId}/${channelId}`, | ||
chainConfig[chainName].denom, | ||
), | ||
denomTrace => !!denomTrace.hash, | ||
`local denom hash for ${chainConfig[chainName].denom} found`, | ||
); | ||
t.log(`found ibc denom hash for ${chainConfig[chainName].denom}:`, hash); | ||
|
||
// 3. Find a remoteChain validator to delegate to | ||
const remoteQueryClient = makeQueryClient( | ||
useChain(chainName).getRestEndpoint(), | ||
); | ||
const { validators } = await remoteQueryClient.queryValidators(); | ||
const validatorAddress = validators[0]?.operator_address; | ||
t.truthy( | ||
validatorAddress, | ||
`found a validator on ${chainName} to delegate to`, | ||
); | ||
t.log( | ||
{ validatorAddress }, | ||
`found a validator on ${chainName} to delegate to`, | ||
); | ||
|
||
// 4. Send an Offer to make the accounts and set up the transfer tap | ||
const agoricUserAddr = wallets[accounts[chainNames.indexOf(chainName)]]; | ||
const wdUser = await provisionSmartWallet(agoricUserAddr, { | ||
BLD: 100n, | ||
IST: 100n, | ||
}); | ||
const doOffer = makeDoOffer(wdUser); | ||
t.log(`${chainName} makeAccount offer`); | ||
const offerId = `${chainName}-makeAccountsInvitation-${Date.now()}`; | ||
|
||
await doOffer({ | ||
id: offerId, | ||
invitationSpec: { | ||
source: 'agoricContract', | ||
instancePath: [contractName], | ||
callPipe: [['makeAccountsInvitation']], | ||
}, | ||
offerArgs: { | ||
chainName, | ||
validator: { | ||
value: validatorAddress, | ||
encoding: 'bech32', | ||
chainId: remoteChainInfo.chain.chain_id, | ||
}, | ||
localDenom: `ibc/${hash}`, | ||
}, | ||
proposal: {}, | ||
}); | ||
|
||
// FIXME https://github.com/Agoric/agoric-sdk/issues/9643 | ||
const vstorageClient = makeQueryTool(); | ||
const currentWalletRecord = await retryUntilCondition( | ||
() => | ||
vstorageClient.queryData(`published.wallet.${agoricUserAddr}.current`), | ||
({ offerToPublicSubscriberPaths }) => | ||
Object.fromEntries(offerToPublicSubscriberPaths)[offerId], | ||
`${offerId} continuing invitation is in vstorage`, | ||
); | ||
|
||
const offerToPublicSubscriberMap = Object.fromEntries( | ||
currentWalletRecord.offerToPublicSubscriberPaths, | ||
); | ||
|
||
// 5. look up LOA address in vstorage | ||
console.log('offerToPublicSubscriberMap', offerToPublicSubscriberMap); | ||
const lcaAddress = offerToPublicSubscriberMap[offerId]?.agoric | ||
.split('.') | ||
.pop(); | ||
const icaAddress = offerToPublicSubscriberMap[offerId]?.[chainName] | ||
.split('.') | ||
.pop(); | ||
console.log({ lcaAddress, icaAddress }); | ||
t.regex(lcaAddress, /^agoric1/, 'LOA address is valid'); | ||
t.regex( | ||
icaAddress, | ||
new RegExp(`^${chainConfig[chainName].expectedAddressPrefix}1`), | ||
'COA address is valid', | ||
); | ||
|
||
// 6. transfer in some tokens over IBC | ||
const transferAmount = 99n; | ||
await fundAndTransfer(chainName, lcaAddress, transferAmount); | ||
|
||
// 7. verify the COA has active delegations | ||
if (chainName === 'cosmoshub') { | ||
// FIXME: delegations are not visible on cosmoshub | ||
return t.pass('skipping verifying delegations on cosmoshub'); | ||
} | ||
const { delegation_responses } = await retryUntilCondition( | ||
() => remoteQueryClient.queryDelegations(icaAddress), | ||
({ delegation_responses }) => !!delegation_responses.length, | ||
`delegations visible on ${chainName}`, | ||
); | ||
t.log('delegation balance', delegation_responses[0]?.balance); | ||
t.like( | ||
delegation_responses[0].balance, | ||
{ denom: chainConfig[chainName].denom, amount: String(transferAmount) }, | ||
'delegations balance', | ||
); | ||
t.log( | ||
`Orchestration Account Delegations on ${chainName}`, | ||
delegation_responses, | ||
); | ||
|
||
// XXX consider using PortfolioHolder continuing inv to undelegate | ||
|
||
// XXX how to test other tokens do not result in an attempted MsgTransfer or MsgDelegate? | ||
// query tx history of the LOA via an rpc node? | ||
}, | ||
}); | ||
|
||
test.serial(autoStakeItScenario, 'osmosis'); | ||
test.serial(autoStakeItScenario, 'cosmoshub'); |
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
Oops, something went wrong.