Skip to content

Commit

Permalink
test: deposit to ICA from portfolioAccount
Browse files Browse the repository at this point in the history
- 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
0xpatrickdev committed Oct 3, 2024
1 parent fec5d79 commit 6021289
Showing 1 changed file with 247 additions and 0 deletions.
247 changes: 247 additions & 0 deletions multichain-testing/test/deposit-withdraw-portfolio.test.ts
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');

0 comments on commit 6021289

Please sign in to comment.