-
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.
test: deposit to ICA from portfolioAccount
- creates a `PortfolioHolder` account that's comprised of a local and remote orchAccounts - uses `Deposit` and `Transfer` invitationMakers (via `Proxying`) to send funds to `remoteOrchAccount` - uses `Transfer` and `Withdraw` invitationMakers (via `Proxying`) to retrieve funds from `remoteOrchAccount`
- Loading branch information
1 parent
fec5d79
commit 6021289
Showing
1 changed file
with
247 additions
and
0 deletions.
There are no files selected for viewing
247 changes: 247 additions & 0 deletions
247
multichain-testing/test/deposit-withdraw-portfolio.test.ts
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,247 @@ | ||
import anyTest from '@endo/ses-ava/prepare-endo.js'; | ||
import type { TestFn } from 'ava'; | ||
import { AmountMath } from '@agoric/ertp'; | ||
import { makeDoOffer } from '../tools/e2e-tools.js'; | ||
import { makeQueryClient } from '../tools/query.js'; | ||
import { commonSetup, SetupContextWithWallets } from './support.js'; | ||
|
||
const test = anyTest as TestFn<SetupContextWithWallets>; | ||
|
||
const accounts = ['cosmoshub', 'osmosis']; | ||
|
||
const contractName = 'basicFlows'; | ||
const contractBuilder = | ||
'../packages/builders/scripts/orchestration/init-basic-flows.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 }; | ||
const { startContract } = rest; | ||
await startContract(contractName, contractBuilder); | ||
}); | ||
|
||
test.after(async t => { | ||
const { deleteTestKeys } = t.context; | ||
deleteTestKeys(accounts); | ||
}); | ||
|
||
const portfolioAccountScenario = test.macro({ | ||
title: (_, remoteChain: string) => | ||
`Deposit and withdraw to ICA on ${remoteChain} via Portfolio Account`, | ||
exec: async (t, chainName: string) => { | ||
const { | ||
wallets, | ||
provisionSmartWallet, | ||
vstorageClient, | ||
retryUntilCondition, | ||
useChain, | ||
} = t.context; | ||
|
||
const agoricAddr = wallets[chainName]; | ||
const wdUser = await provisionSmartWallet(agoricAddr, { | ||
BLD: 100n, | ||
IST: 1000n, | ||
}); | ||
t.log(`Provisioned Agoric smart wallet for ${agoricAddr}`); | ||
|
||
const doOffer = makeDoOffer(wdUser); | ||
|
||
// Create portfolio holder account for agoric and remoteChain | ||
const makePortfolioAcctOfferId = `makePortfolioAccount-${chainName}-${Date.now()}`; | ||
await doOffer({ | ||
id: makePortfolioAcctOfferId, | ||
invitationSpec: { | ||
source: 'agoricContract', | ||
instancePath: [contractName], | ||
callPipe: [['makePortfolioAccountInvitation']], | ||
}, | ||
offerArgs: { | ||
chainNames: ['agoric', chainName], | ||
}, | ||
proposal: {}, | ||
}); | ||
|
||
const { offerToPublicSubscriberPaths } = await retryUntilCondition( | ||
() => vstorageClient.queryData(`published.wallet.${agoricAddr}.current`), | ||
({ offerToPublicSubscriberPaths }) => | ||
Object.fromEntries(offerToPublicSubscriberPaths)[ | ||
makePortfolioAcctOfferId | ||
], | ||
'Portfolio account creation offer result is in vstorage', | ||
); | ||
|
||
const accountPaths = Object.fromEntries(offerToPublicSubscriberPaths)[ | ||
makePortfolioAcctOfferId | ||
]; | ||
t.truthy(accountPaths.agoric, 'Agoric account path returned'); | ||
t.truthy(accountPaths[chainName], `${chainName} account path returned`); | ||
|
||
const agoricLcaAddress = accountPaths.agoric.split('.').at(-1); | ||
const remoteIcaAddress = accountPaths[chainName].split('.').at(-1); | ||
t.truthy(agoricLcaAddress, 'Agoric LCA address is in storage path'); | ||
t.truthy(remoteIcaAddress, `${chainName} ICA address is in storage path`); | ||
|
||
// Get IST brand | ||
const brands = await vstorageClient.queryData( | ||
'published.agoricNames.brand', | ||
); | ||
const istBrand = Object.fromEntries(brands).IST; | ||
|
||
// Setup query clients | ||
const agoricApiUrl = await useChain('agoric').getRestEndpoint(); | ||
const agoricQueryClient = makeQueryClient(agoricApiUrl); | ||
const remoteChainInfo = useChain(chainName); | ||
const remoteQueryClient = makeQueryClient( | ||
await remoteChainInfo.getRestEndpoint(), | ||
); | ||
|
||
// Deposit funds to Agoric account | ||
const depositAmount = AmountMath.make(istBrand, 500n); | ||
const depositOfferId = `deposit-portfolio-${chainName}-${Date.now()}`; | ||
await doOffer({ | ||
id: depositOfferId, | ||
invitationSpec: { | ||
source: 'continuing', | ||
previousOffer: makePortfolioAcctOfferId, | ||
invitationMakerName: 'Proxying', | ||
invitationArgs: ['agoric', 'Deposit'], | ||
}, | ||
offerArgs: {}, | ||
proposal: { | ||
give: { Asset: depositAmount }, | ||
}, | ||
}); | ||
|
||
// Verify deposit | ||
const agoricAccountBalance = await retryUntilCondition( | ||
() => agoricQueryClient.queryBalance(agoricLcaAddress, 'uist'), | ||
({ balance }) => balance?.denom === 'uist' && balance?.amount === '500', | ||
'Deposit reflected in Agoric account balance', | ||
); | ||
t.deepEqual( | ||
agoricAccountBalance.balance, | ||
{ denom: 'uist', amount: '500' }, | ||
'Correct amount deposited to Agoric account', | ||
); | ||
|
||
// IBC Transfer funds to remoteChain account | ||
const remoteChainId = remoteChainInfo.chain.chain_id; | ||
const ibcTransferOfferId = `Transfer-to-${chainName}-${Date.now()}`; | ||
await doOffer({ | ||
id: ibcTransferOfferId, | ||
invitationSpec: { | ||
source: 'continuing', | ||
previousOffer: makePortfolioAcctOfferId, | ||
invitationMakerName: 'Proxying', | ||
invitationArgs: ['agoric', 'Transfer'], | ||
}, | ||
offerArgs: { | ||
amount: { denom: 'uist', value: 500n }, | ||
destination: { | ||
chainId: remoteChainId, | ||
value: remoteIcaAddress, | ||
encoding: 'bech32,', | ||
}, | ||
}, | ||
proposal: {}, | ||
}); | ||
|
||
const remoteBalances = await retryUntilCondition( | ||
() => remoteQueryClient.queryBalances(remoteIcaAddress), | ||
({ balances }) => balances.length > 0, | ||
`IBC transfer reflected in ${chainName} account balance`, | ||
); | ||
t.log(`${remoteIcaAddress} Balances`, remoteBalances.balances); | ||
// there are no other funds in the account, so we can safely assume its IST | ||
// consider looking up the expected denom | ||
t.like(remoteBalances, { | ||
balances: [ | ||
{ | ||
amount: '500', | ||
}, | ||
], | ||
}); | ||
|
||
// Transfer funds back to Agoric | ||
// TODO #9966 use IST brand and let contract perform denom lookup | ||
const istRemoteDenom = remoteBalances.balances[0].denom; | ||
const transferBackOfferId = `transfer-back-${chainName}-${Date.now()}`; | ||
await doOffer({ | ||
id: transferBackOfferId, | ||
invitationSpec: { | ||
source: 'continuing', | ||
previousOffer: makePortfolioAcctOfferId, | ||
invitationMakerName: 'Proxying', | ||
invitationArgs: [chainName, 'Transfer'], | ||
}, | ||
offerArgs: { | ||
// TODO #9966 use IST brand and let contract perform denom lookup | ||
amount: { denom: istRemoteDenom, value: 500n }, | ||
destination: { | ||
chainId: 'agoriclocal', | ||
value: agoricLcaAddress, | ||
encoding: 'bech32,', | ||
}, | ||
}, | ||
proposal: {}, | ||
}); | ||
|
||
// Verify funds are back in Agoric account | ||
const updatedAgoricAccountBalance = await retryUntilCondition( | ||
() => agoricQueryClient.queryBalance(agoricLcaAddress, 'uist'), | ||
({ balance }) => balance?.denom === 'uist' && balance?.amount === '500', | ||
'IBC transfer back reflected in Agoric account balance', | ||
); | ||
t.deepEqual( | ||
updatedAgoricAccountBalance.balance, | ||
{ denom: 'uist', amount: '500' }, | ||
'Correct amount transferred back to Agoric account', | ||
); | ||
|
||
// Withdraw funds from Agoric account | ||
const withdrawAmount = AmountMath.make(istBrand, 500n); | ||
const withdrawOfferId = `withdraw-${chainName}-${Date.now()}`; | ||
await doOffer({ | ||
id: withdrawOfferId, | ||
invitationSpec: { | ||
source: 'continuing', | ||
previousOffer: makePortfolioAcctOfferId, | ||
invitationMakerName: 'Proxying', | ||
invitationArgs: ['agoric', 'Withdraw'], | ||
}, | ||
offerArgs: {}, | ||
proposal: { | ||
want: { Asset: withdrawAmount }, | ||
}, | ||
}); | ||
|
||
// Verify withdrawal | ||
const finalAgoricAccountBalance = await agoricQueryClient.queryBalance( | ||
agoricLcaAddress, | ||
'uist', | ||
); | ||
t.deepEqual( | ||
finalAgoricAccountBalance.balance, | ||
{ denom: 'uist', amount: '0' }, | ||
'All funds withdrawn from Agoric account', | ||
); | ||
|
||
// Verify smart wallet balance | ||
// faucet - provision rebate - deposit + withdraw | ||
const driverExpectedBalance = 1_000_000_000n + 250_000n - 500n + 500n; | ||
const driverBalanceAfterWithdraw = await agoricQueryClient.queryBalance( | ||
agoricAddr, | ||
'uist', | ||
); | ||
t.deepEqual( | ||
driverBalanceAfterWithdraw.balance, | ||
{ denom: 'uist', amount: String(driverExpectedBalance) }, | ||
'All funds returned to smart wallet', | ||
); | ||
}, | ||
}); | ||
|
||
test(portfolioAccountScenario, 'osmosis'); | ||
test(portfolioAccountScenario, 'cosmoshub'); |