Skip to content

Commit

Permalink
[DX-2864] fix: IMX withdrawal ETH funcional tests (#1864)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeSchwert authored Jun 5, 2024
1 parent 156a461 commit 31930da
Show file tree
Hide file tree
Showing 9 changed files with 426 additions and 268 deletions.
5 changes: 5 additions & 0 deletions packages/x-client/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
/* eslint-disable max-len */
import { imx } from '@imtbl/generated-clients';

export { TransactionResponse } from '@ethersproject/providers';

/**
* Need to specifically export the classes and interfaces from the generated
* clients imx object for rollup to bundle them correctly.
Expand Down Expand Up @@ -35,6 +37,7 @@ export interface AcceptPrimarySaleUnauthorizedBody extends imx.AcceptPrimarySale
export interface AddMetadataSchemaToCollectionRequest extends imx.AddMetadataSchemaToCollectionRequest {}
export interface AssetsApiGetAssetRequest extends imx.AssetsApiGetAssetRequest {}
export interface AssetsApiListAssetsRequest extends imx.AssetsApiListAssetsRequest {}
export interface Balance extends imx.Balance {}
export interface BalancesApiGetBalanceRequest extends imx.BalancesApiGetBalanceRequest {}
export interface BalancesApiListBalancesRequest extends imx.BalancesApiListBalancesRequest {}
export interface CancelOrderResponse extends imx.CancelOrderResponse {}
Expand All @@ -51,6 +54,7 @@ export interface CreatePrimarySaleNotFoundBody extends imx.CreatePrimarySaleNotF
export interface CreatePrimarySaleUnauthorizedBody extends imx.CreatePrimarySaleUnauthorizedBody {}
export interface CreateTradeResponse extends imx.CreateTradeResponse {}
export interface CreateTransferResponseV1 extends imx.CreateTransferResponseV1 {}
export interface CreateWithdrawalResponse extends imx.CreateWithdrawalResponse {}
export interface DepositsApiGetDepositRequest extends imx.DepositsApiGetDepositRequest {}
export interface DepositsApiListDepositsRequest extends imx.DepositsApiListDepositsRequest {}
export interface ExchangesApiCreateExchangeRequest extends imx.ExchangesApiCreateExchangeRequest {}
Expand All @@ -62,6 +66,7 @@ export interface GetSignableTradeRequest extends imx.GetSignableTradeRequest {}
export interface MetadataApiGetMetadataSchemaRequest extends imx.MetadataApiGetMetadataSchemaRequest {}
export interface MetadataSchemaRequest extends imx.MetadataSchemaRequest {}
export interface MintFee extends imx.MintFee {}
export interface MintResultDetails extends imx.MintResultDetails {}
export interface MintRequest extends imx.MintRequest {}
export interface MintTokenDataV2 extends imx.MintTokenDataV2 {}
export interface MintTokensResponse extends imx.MintTokensResponse {}
Expand Down
1 change: 1 addition & 0 deletions tests/func-tests/imx/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sharedState.json
7 changes: 4 additions & 3 deletions tests/func-tests/imx/features/withdrawal.feature
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,24 @@ Feature: Withdrawal
And banker is registered
And banker has L2 balance "bankerBalance" of at least "0.00001"
And banker transfer "0.00001" eth to "user1"
And banker L1 ETH balance is at least "0.1"
# this step is required so the user has enough ETH on L1 to perform the complete withdrawal step
And banker transfer "0.1" eth to "user1" on L1
When user "user1" prepare withdrawal "withdrawal1" of ETH "0.00001"
Then ETH withdrawal "withdrawal1" should be in "success" status

@withdrawal @withdrawalETH @onchain @ethSignature @completeEthWithdrawal @skip
Scenario: Complete withdraw ETH
Given A new Eth wallet "user1"
Given A stored Eth wallet "user1"
And "user1" is registered
Then user "user1" completes withdrawal of ETH


# @withdrawal @completeWithdrawalNFT
# Scenario: Complete withdraw ERC721
# Given A new Eth wallet "user1"
# And "user1" is registered
# Then user "user1" completes withdrawal of a withdrawable NFT


# @withdrawal @completeERC20Withdrawal
# Scenario: Complete withdraw ERC20
# Given A new Eth wallet "user1"
Expand Down
7 changes: 7 additions & 0 deletions tests/func-tests/imx/step-definitions/deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export class DepositEth {
assert.ok(parseEther(response.balance!).gte(parseEther(amount)));
}

// check the banker's ETH balance on L1
public async checkBankerL1EthBalance(amount: string) {
const banker = await this.stepSharedState.getBanker();
const onChainBalance = await banker.ethSigner.getBalance();
assert.ok(onChainBalance.gte(parseEther(amount)));
}

// @then(
// 'banker should have balance {string} increased by {string} eth',
// undefined,
Expand Down
57 changes: 56 additions & 1 deletion tests/func-tests/imx/step-definitions/registration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'fs';
import { strict as assert } from 'assert';
import { Wallet } from '@ethersproject/wallet';
import {
Expand Down Expand Up @@ -37,28 +38,82 @@ const provider = getProvider(env.network, env.alchemyApiKey);
// },
// };

const sharedStateFile = 'sharedState.json';

type PersistedSharedState = {
users: {
[key: string]: {
ethPrivateKey: string;
starkPrivateKey: string;
};
};
};

export class Registration {
constructor(protected stepSharedState: StepSharedState) {}

providerConfig = new ProviderConfiguration({
baseConfig: configuration,
});

public async addNewWallet(addressVar: string) {
// eslint-disable-next-line class-methods-use-this
private async persistState(user: string, ethPrivateKey: string, starkPrivateKey: string) {
const state: PersistedSharedState = {
users: {
[user]: {
ethPrivateKey,
starkPrivateKey,
},
},
};
fs.writeFileSync(sharedStateFile, JSON.stringify(state, null, 2));
}

// eslint-disable-next-line class-methods-use-this
private async hydrateState(): Promise<PersistedSharedState | false> {
// check if file exists
if (!fs.existsSync(sharedStateFile)) {
return false;
}

const state = fs.readFileSync(sharedStateFile, 'utf8');
return JSON.parse(state);
}

public async addNewWallet(addressVar: string, persist?: boolean) {
// L1 credentials
const ethSigner = Wallet.createRandom().connect(provider);

// L2 credentials
const starkPrivateKey = generateStarkPrivateKey();
const starkSigner = createStarkSigner(starkPrivateKey);

if (persist) {
await this.persistState(addressVar, ethSigner.privateKey, starkPrivateKey);
}

this.stepSharedState.users[addressVar] = {
ethSigner,
starkSigner,
};
return ethSigner.publicKey;
}

public async restoreUserWallet(addressVar: string) {
const state = await this.hydrateState();
if (state) {
const user = state.users[addressVar];
const ethSigner = new Wallet(user.ethPrivateKey).connect(provider);
const starkSigner = createStarkSigner(user.starkPrivateKey);
this.stepSharedState.users[addressVar] = {
ethSigner,
starkSigner,
};
} else {
throw new Error('No persisted user state found');
}
}

public async register(addressVar: string) {
const user = this.stepSharedState.users[addressVar];

Expand Down
18 changes: 18 additions & 0 deletions tests/func-tests/imx/step-definitions/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ export class Transfer {
}
}

public async transferL1EthFromBanker(amount: string, userVar: string) {
try {
const banker = await this.stepSharedState.getBanker();
const receiver = await this.stepSharedState.users[
userVar
].ethSigner.getAddress();

await banker.ethSigner.sendTransaction({
to: receiver,
value: parseEther(amount),
});
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
throw e;
}
}

// cleanup - transfer eth back to banker
// @then('{string} transfer {string} eth to banker', undefined, 10000)
public async transferToBanker(userVar: string, amount: string) {
Expand Down
21 changes: 16 additions & 5 deletions tests/func-tests/imx/step-definitions/withdrawal.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Withdrawal } from './withdrawal';
import { DepositEth } from './deposit';
import { Transfer } from './transfer';

const feature = loadFeature('features/withdrawal.feature',{tagFilter: process.env.TAGS});
const feature = loadFeature('features/withdrawal.feature', { tagFilter: process.env.TAGS });

defineFeature(feature, (test) => {
test('Withdraw ETH', ({
Expand All @@ -20,7 +20,7 @@ defineFeature(feature, (test) => {
const depositETH = new DepositEth(sharedState);
const transfer = new Transfer(sharedState);
given(/^A new Eth wallet "(.*)"$/, async (addressVar) => {
await registration.addNewWallet(addressVar);
await registration.addNewWallet(addressVar, true);
});
and(/^"(.*)" is registered$/, async (addressVar) => {
await registration.register(addressVar);
Expand All @@ -37,24 +37,35 @@ defineFeature(feature, (test) => {
await transfer.transferFromBanker(amountVar, addressVar);
});

and(/^banker L1 ETH balance is at least "(.*)"$/, async (amountVar) => {
await depositETH.checkBankerL1EthBalance(amountVar);
});

and(/^banker transfer "(.*)" eth to "(.*)" on L1$/, async (amountVar, addressVar) => {
await transfer.transferL1EthFromBanker(amountVar, addressVar);
});

when(/^user "(.*)" prepare withdrawal "(.*)" of ETH "(.*)"$/, async (addressVar, withdrawalVar, ethVar) => {
const response = await withdrawal.prepareEthWithdrawal(addressVar, withdrawalVar, ethVar);
expect(response.withdrawal_id).toBeGreaterThan(0);
// eslint-disable-next-line max-len
console.log(`prepareEthWithdrawal transaction can be found here: https://sandbox.immutascan.io/tx/${response.withdrawal_id}`);
});

then(/^ETH withdrawal "(.*)" should be in "(.*)" status$/, async (withdrawalVar, statusVar) => {
await withdrawal.checkWithdrawableEthStatus(withdrawalVar, statusVar);
});
});

test('Complete withdraw ETH', ({ given, and, then }) => {
const sharedState = new StepSharedState();
const registration = new Registration(sharedState);
const withdrawal = new Withdrawal(sharedState);
given(/^A new Eth wallet "(.*)"$/, async (addressVar) => {
await registration.addNewWallet(addressVar);
given(/^A stored Eth wallet "(.*)"$/, async (addressVar) => {
await registration.restoreUserWallet(addressVar);
});
and(/^"(.*)" is registered$/, async (addressVar) => {
await registration.register(addressVar);
await registration.checkUserRegistrationOffchain(addressVar);
});
then(/^user "(.*)" completes withdrawal of ETH$/, async (addressVar) => {
await withdrawal.completeEthWithdrawal(addressVar);
Expand Down
30 changes: 20 additions & 10 deletions tests/func-tests/imx/step-definitions/withdrawal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,18 @@ export class Withdrawal {
withdrawalName: string,
ethAmount: string,
) {
const user = this.stepSharedState.users[userVar];
const imxProvider = new GenericIMXProvider(this.providerConfig, user.ethSigner, user.starkSigner);
const ethAmountInWei = parseUnits(ethAmount);
const result = await imxProvider.prepareWithdrawal({ type: 'ETH', amount: ethAmountInWei.toString() });

this.stepSharedState.withdrawals[withdrawalName] = result;
return result;
try {
const user = this.stepSharedState.users[userVar];
const imxProvider = new GenericIMXProvider(this.providerConfig, user.ethSigner, user.starkSigner);
const ethAmountInWei = parseUnits(ethAmount);
const result = await imxProvider.prepareWithdrawal({ type: 'ETH', amount: ethAmountInWei.toString() });

this.stepSharedState.withdrawals[withdrawalName] = result;
return result;
} catch (e) {
console.error(e);
throw e;
}
}

// because withdrawable status needs a larger timeout, we set 300 seconds here.
Expand Down Expand Up @@ -91,9 +96,14 @@ export class Withdrawal {
const user = this.stepSharedState.users[userVar];
const starkAddress = await user.starkSigner.getAddress();
const imxProvider = new GenericIMXProvider(this.providerConfig, user.ethSigner, user.starkSigner);
const result = await imxProvider.completeWithdrawal(starkAddress, { type: 'ETH' });
// eslint-disable-next-line no-console
console.log(`Eth withdrawal transaction complete. txHash: ${result.hash}`);
try {
const result = await imxProvider.completeWithdrawal(starkAddress, { type: 'ETH' });
// eslint-disable-next-line no-console
console.log(`Eth withdrawal transaction complete. txHash: ${result.hash}`);
} catch (e) {
console.error(e);
throw e;
}
}

// @when('user {string} completes withdrawal of NFT {string}', undefined, 60000)
Expand Down
Loading

0 comments on commit 31930da

Please sign in to comment.