@cardano-sdk/e2e 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.env.example +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +5 -1
  4. package/dist/cjs/environment.d.ts +7 -2
  5. package/dist/cjs/environment.d.ts.map +1 -1
  6. package/dist/cjs/environment.js +18 -0
  7. package/dist/cjs/environment.js.map +1 -1
  8. package/dist/cjs/factories.d.ts +3 -1
  9. package/dist/cjs/factories.d.ts.map +1 -1
  10. package/dist/cjs/factories.js +18 -2
  11. package/dist/cjs/factories.js.map +1 -1
  12. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  13. package/dist/esm/environment.d.ts +7 -2
  14. package/dist/esm/environment.d.ts.map +1 -1
  15. package/dist/esm/environment.js +18 -0
  16. package/dist/esm/environment.js.map +1 -1
  17. package/dist/esm/factories.d.ts +3 -1
  18. package/dist/esm/factories.d.ts.map +1 -1
  19. package/dist/esm/factories.js +17 -1
  20. package/dist/esm/factories.js.map +1 -1
  21. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  22. package/docker-compose.yml +2 -2
  23. package/package.json +20 -33
  24. package/src/environment.ts +26 -0
  25. package/src/factories.ts +30 -3
  26. package/test/blockfrost/StakePoolCompare.test.ts +6 -12
  27. package/test/k6/endpoints/asset/get-assets.test.js +278 -0
  28. package/test/local-network/register-pool.test.ts +2 -2
  29. package/test/pg-boss/stake-pool-metrics.test.ts +44 -0
  30. package/test/providers/StakePoolProvider.test.ts +2 -2
  31. package/test/wallet/PersonalWallet/delegationDistribution.test.ts +242 -0
  32. package/test/wallet/PersonalWallet/multiAddress.test.ts +12 -16
  33. package/test/web-extension/.env.example +2 -0
  34. package/test/web-extension/extension/manifest.json +1 -1
  35. package/test/web-extension/webpack.config.base.js +7 -1
@@ -0,0 +1,44 @@
1
+ import { Pool } from 'pg';
2
+ import { STAKE_POOL_METRICS_UPDATE } from '@cardano-sdk/projection-typeorm';
3
+ import { getEnv } from '../../src';
4
+
5
+ const selectRowsCount = async (db: Pool, table: string) => {
6
+ const query = `SELECT COUNT(*) FROM ${table}`;
7
+ const { rows } = await db.query(query);
8
+
9
+ return rows;
10
+ };
11
+
12
+ describe('stake-pool-metrics', () => {
13
+ const key = 'STAKE_POOL_CONNECTION_STRING';
14
+ const connectionString = getEnv([key])[key];
15
+ const db = new Pool({ connectionString });
16
+
17
+ it('populates current_pool_metrics table', async () => {
18
+ let jobState: string[] | undefined;
19
+
20
+ // Wait until at least one job completed
21
+ while (!jobState?.filter((_) => _ === 'completed').length) {
22
+ // If it is not the first iteration, wait for a while
23
+ if (jobState) await new Promise((resolve) => setTimeout(resolve, 5000));
24
+
25
+ try {
26
+ const query = 'SELECT state FROM pgboss.job WHERE name = $1';
27
+ const { rows } = await db.query<{ state: string }>(query, [STAKE_POOL_METRICS_UPDATE]);
28
+
29
+ jobState = rows.map(({ state }) => state);
30
+ } catch (error) {
31
+ const allowedErrors = ['database "projection" does not exist', 'relation "pgboss.job" does not exist'];
32
+
33
+ // In case the projection service has not yet created the database or the schema, simulate an empty result to wait
34
+ if (error instanceof Error && allowedErrors.includes(error.message)) jobState = [];
35
+ else throw error;
36
+ }
37
+ }
38
+
39
+ const metricsRows = await selectRowsCount(db, 'current_pool_metrics');
40
+ const poolsRows = await selectRowsCount(db, 'stake_pool');
41
+
42
+ expect(metricsRows).toEqual(poolsRows);
43
+ });
44
+ });
@@ -260,13 +260,13 @@ describe('StakePoolProvider', () => {
260
260
  it('with pledgeMet false', async () => {
261
261
  const { pageResults } = await query({ pledgeMet: false });
262
262
 
263
- for (const pool of pageResults) expect(pool.pledge > pool.metrics.livePledge).toBeTruthy();
263
+ for (const pool of pageResults) expect(pool.pledge > pool.metrics!.livePledge).toBeTruthy();
264
264
  });
265
265
 
266
266
  it('with pledgeMet true', async () => {
267
267
  const { pageResults } = await query({ pledgeMet: true });
268
268
 
269
- for (const pool of pageResults) expect(pool.pledge <= pool.metrics.livePledge).toBeTruthy();
269
+ for (const pool of pageResults) expect(pool.pledge <= pool.metrics!.livePledge).toBeTruthy();
270
270
  });
271
271
 
272
272
  it('pools number = meeting pools number + not meeting pools number', async () => {
@@ -0,0 +1,242 @@
1
+ import { Cardano } from '@cardano-sdk/core';
2
+ import { DelegatedStake, PersonalWallet, createUtxoBalanceByAddressTracker } from '@cardano-sdk/wallet';
3
+ import { MINUTE, getWallet } from '../../../src';
4
+ import { Observable, filter, firstValueFrom, map, tap } from 'rxjs';
5
+ import { Percent } from '@cardano-sdk/util';
6
+ import { createLogger } from '@cardano-sdk/util-dev';
7
+ import { firstValueFromTimed, submitAndConfirm, walletReady } from '../../util';
8
+ import { getEnv, walletVariables } from '../../../src/environment';
9
+
10
+ const env = getEnv(walletVariables);
11
+ const logger = createLogger();
12
+ const TEST_FUNDS = 1_000_000_000n;
13
+
14
+ /** Distribute the wallet funds evenly across all its addresses */
15
+ const distributeFunds = async (wallet: PersonalWallet) => {
16
+ await walletReady(wallet, 0n);
17
+ const addresses = await firstValueFrom(wallet.addresses$);
18
+ expect(addresses.length).toBeGreaterThan(1);
19
+
20
+ // Check that we have enough funds. Otherwise, fund it from wallet account at index 0
21
+ let { coins: totalCoins } = await firstValueFrom(wallet.balance.utxo.available$);
22
+
23
+ const coinDeficit = TEST_FUNDS - totalCoins;
24
+ if (coinDeficit > 10_000_000n) {
25
+ logger.debug(
26
+ `Insufficient funds in wallet account index 1. Missing ${coinDeficit}. Transferring from wallet account index 0`
27
+ );
28
+ const fundingWallet = (await getWallet({ env, idx: 0, logger, name: 'WalletAcct0', polling: { interval: 50 } }))
29
+ .wallet;
30
+ await walletReady(fundingWallet);
31
+ const fundingTxBuilder = fundingWallet.createTxBuilder();
32
+ const { tx } = await fundingTxBuilder
33
+ .addOutput(fundingTxBuilder.buildOutput().address(addresses[0].address).coin(coinDeficit).toTxOut())
34
+ .build()
35
+ .sign();
36
+ await submitAndConfirm(fundingWallet, tx);
37
+ await walletReady(wallet);
38
+ totalCoins = (await firstValueFrom(wallet.balance.utxo.available$)).coins;
39
+ }
40
+
41
+ const coinsPerAddress = totalCoins / BigInt(addresses.length);
42
+
43
+ const txBuilder = wallet.createTxBuilder();
44
+
45
+ logger.debug(`Sending ${coinsPerAddress} to the ${addresses.length - 1} derived addresses`);
46
+ // The first one was generated when the wallet was created.
47
+ for (let i = 1; i < addresses.length; ++i) {
48
+ const derivedAddress = addresses[i];
49
+ txBuilder.addOutput(txBuilder.buildOutput().address(derivedAddress.address).coin(coinsPerAddress).toTxOut());
50
+ }
51
+
52
+ const { tx: signedTx } = await txBuilder.build().sign();
53
+ await submitAndConfirm(wallet, signedTx);
54
+ };
55
+
56
+ /** await for rewardAccounts$ to be registered, unregistered, as defined in states */
57
+ const rewardAccountStatuses = async (
58
+ rewardAccounts$: Observable<Cardano.RewardAccountInfo[]>,
59
+ statuses: Cardano.StakeKeyStatus[]
60
+ ) =>
61
+ firstValueFromTimed(
62
+ rewardAccounts$.pipe(
63
+ tap((accts) => accts.map(({ address, keyStatus }) => logger.debug(address, keyStatus))),
64
+ map((accts) => accts.map(({ keyStatus }) => keyStatus)),
65
+ filter((statusArr) => statusArr.every((s) => statuses.includes(s)))
66
+ ),
67
+ `Timeout waiting for all reward accounts stake keys to be one of ${statuses.join('|')}`,
68
+ MINUTE
69
+ );
70
+
71
+ /** Create stakeKey deregistration transaction for all reward accounts */
72
+ const deregisterAllStakeKeys = async (wallet: PersonalWallet): Promise<void> => {
73
+ const txBuilder = wallet.createTxBuilder();
74
+ txBuilder.delegate();
75
+ const { tx: deregTx } = await txBuilder.build().sign();
76
+ await submitAndConfirm(wallet, deregTx);
77
+
78
+ await rewardAccountStatuses(wallet.delegation.rewardAccounts$, [
79
+ Cardano.StakeKeyStatus.Unregistered,
80
+ Cardano.StakeKeyStatus.Unregistering
81
+ ]);
82
+ logger.debug('Deregistered all stake keys');
83
+ };
84
+
85
+ const createStakeKeyRegistrationCert = (rewardAccount: Cardano.RewardAccount): Cardano.Certificate => ({
86
+ __typename: Cardano.CertificateType.StakeKeyRegistration,
87
+ stakeKeyHash: Cardano.RewardAccount.toHash(rewardAccount)
88
+ });
89
+
90
+ const createDelegationCert = (rewardAccount: Cardano.RewardAccount, poolId: Cardano.PoolId): Cardano.Certificate => ({
91
+ __typename: Cardano.CertificateType.StakeDelegation,
92
+ poolId,
93
+ stakeKeyHash: Cardano.RewardAccount.toHash(rewardAccount)
94
+ });
95
+
96
+ const getPoolIds = async (wallet: PersonalWallet, count: number): Promise<Cardano.StakePool[]> => {
97
+ const activePools = await wallet.stakePoolProvider.queryStakePools({
98
+ filters: { status: [Cardano.StakePoolStatus.Active] },
99
+ pagination: { limit: count, startAt: 0 }
100
+ });
101
+ expect(activePools.pageResults.length).toBeGreaterThanOrEqual(count);
102
+ return Array.from({ length: count }).map((_, index) => activePools.pageResults[index]);
103
+ };
104
+
105
+ const delegateToMultiplePools = async (wallet: PersonalWallet) => {
106
+ // Delegating to multiple pools should be added in TxBuilder. Doing it manually for now.
107
+ // Prepare stakeKey registration certificates
108
+ const rewardAccounts = await firstValueFrom(wallet.delegation.rewardAccounts$);
109
+ const stakeKeyRegCertificates = rewardAccounts.map(({ address }) => createStakeKeyRegistrationCert(address));
110
+
111
+ const poolIds = await getPoolIds(wallet, rewardAccounts.length);
112
+ const delegationCertificates = rewardAccounts.map(({ address }, index) =>
113
+ createDelegationCert(address, poolIds[index].id)
114
+ );
115
+
116
+ logger.debug(
117
+ `Delegating to pools ${poolIds.map(({ id }) => id)} and registering ${stakeKeyRegCertificates.length} stake keys`
118
+ );
119
+
120
+ const txBuilder = wallet.createTxBuilder();
121
+ // Artificially add the certificates in TxBuilder. An api improvement will make the UX better
122
+ txBuilder.partialTxBody.certificates = [...stakeKeyRegCertificates, ...delegationCertificates];
123
+ const { tx } = await txBuilder.build().sign();
124
+ await submitAndConfirm(wallet, tx);
125
+ return poolIds;
126
+ };
127
+
128
+ describe('PersonalWallet/delegationDistribution', () => {
129
+ let wallet: PersonalWallet;
130
+
131
+ beforeAll(async () => {
132
+ wallet = (await getWallet({ env, idx: 1, logger, name: 'Wallet', polling: { interval: 50 } })).wallet;
133
+ await distributeFunds(wallet);
134
+ await deregisterAllStakeKeys(wallet);
135
+ });
136
+
137
+ afterAll(() => {
138
+ wallet.shutdown();
139
+ });
140
+
141
+ it('reports observable wallet multi delegation as delegationDistribution by pool', async () => {
142
+ await walletReady(wallet);
143
+ const walletAddresses = await firstValueFromTimed(wallet.addresses$);
144
+ const rewardAccounts = await firstValueFrom(wallet.delegation.rewardAccounts$);
145
+
146
+ // No stake distribution initially
147
+ const delegationDistribution = await firstValueFrom(wallet.delegation.distribution$);
148
+ expect(delegationDistribution).toEqual(new Map());
149
+
150
+ const poolIds = await delegateToMultiplePools(wallet);
151
+
152
+ // Check that reward addresses were delegated
153
+ await walletReady(wallet);
154
+ await rewardAccountStatuses(wallet.delegation.rewardAccounts$, [
155
+ Cardano.StakeKeyStatus.Registering,
156
+ Cardano.StakeKeyStatus.Registered
157
+ ]);
158
+ logger.debug('Delegations successfully done');
159
+
160
+ const totalBalance = await firstValueFrom(wallet.balance.utxo.total$);
161
+ const perAddrBalance = await Promise.all(
162
+ rewardAccounts.map((_, index) => {
163
+ const address = walletAddresses[index].address;
164
+ return firstValueFrom(
165
+ createUtxoBalanceByAddressTracker(wallet.utxo, [address]).utxo.total$.pipe(map(({ coins }) => coins))
166
+ );
167
+ })
168
+ );
169
+
170
+ // Check delegation.delegationDistribution$ has the delegation information
171
+ const expectedDelegationDistribution: DelegatedStake[] = rewardAccounts.map(({ address }, index) => ({
172
+ percentage: Percent(Number(perAddrBalance[index]) / Number(totalBalance.coins)),
173
+ pool: expect.objectContaining({ id: poolIds[index].id }),
174
+ rewardAccounts: [address],
175
+ stake: perAddrBalance[index]
176
+ }));
177
+ const actualDelegationDistribution = await firstValueFrom(wallet.delegation.distribution$);
178
+
179
+ expect([...actualDelegationDistribution.values()]).toEqual(expectedDelegationDistribution);
180
+
181
+ // Send all coins to the last address. Check that stake distribution is 100 for that address and 0 for the rest
182
+ const { coins: totalCoins } = await firstValueFrom(wallet.balance.utxo.total$);
183
+ let txBuilder = wallet.createTxBuilder();
184
+ const { tx: txMoveFunds } = await txBuilder
185
+ .addOutput(
186
+ txBuilder
187
+ .buildOutput()
188
+ .address(walletAddresses[walletAddresses.length - 1].address)
189
+ .coin(totalCoins - 2_000_000n) // leave some behind for fees
190
+ .toTxOut()
191
+ )
192
+ .build()
193
+ .sign();
194
+ await submitAndConfirm(wallet, txMoveFunds);
195
+
196
+ let simplifiedDelegationDistribution: Partial<DelegatedStake>[] = await firstValueFrom(
197
+ wallet.delegation.distribution$.pipe(
198
+ map((delegatedStake) =>
199
+ [...delegatedStake.values()].map(({ pool, percentage }) => ({
200
+ id: pool.id,
201
+ name: pool.metadata?.name,
202
+ percentage
203
+ }))
204
+ )
205
+ )
206
+ );
207
+ expect(simplifiedDelegationDistribution).toEqual(
208
+ rewardAccounts.map((_, index) => ({
209
+ id: poolIds[index].id,
210
+ name: poolIds[index].metadata?.name,
211
+ // Expect approx 100% allocation to the last address
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ percentage: (expect as any).closeTo(index === walletAddresses.length - 1 ? 1 : 0)
214
+ }))
215
+ );
216
+
217
+ // Delegate all reward accounts to the same pool. delegationDistribution$ should have 1 entry with 100% distribution
218
+ txBuilder = wallet.createTxBuilder();
219
+ const { tx: txDelegateTo1Pool } = await txBuilder.delegate(poolIds[0].id).build().sign();
220
+ await submitAndConfirm(wallet, txDelegateTo1Pool);
221
+ simplifiedDelegationDistribution = await firstValueFrom(
222
+ wallet.delegation.distribution$.pipe(
223
+ map((distribution) =>
224
+ [...distribution.values()].map((delegatedStake) => ({
225
+ id: delegatedStake.pool.id,
226
+ name: delegatedStake.pool.metadata?.name,
227
+ percentage: delegatedStake.percentage,
228
+ rewardAccounts: delegatedStake.rewardAccounts
229
+ }))
230
+ )
231
+ )
232
+ );
233
+ expect(simplifiedDelegationDistribution).toEqual([
234
+ {
235
+ id: poolIds[0].id,
236
+ name: poolIds[0].metadata?.name,
237
+ percentage: Percent(1),
238
+ rewardAccounts: rewardAccounts.map(({ address }) => address)
239
+ }
240
+ ]);
241
+ });
242
+ });
@@ -65,17 +65,13 @@ describe('PersonalWallet/multiAddress', () => {
65
65
  );
66
66
 
67
67
  addressesToBeDiscovered.push(address);
68
-
69
- const txOutput = await txBuilder.buildOutput().address(address.address).coin(3_000_000n).build();
70
- txBuilder.addOutput(txOutput);
68
+ txBuilder.addOutput(txBuilder.buildOutput().address(address.address).coin(3_000_000n).toTxOut());
71
69
  }
72
70
 
73
71
  // Remove duplicates
74
72
  addressesToBeDiscovered = [...new Set(addressesToBeDiscovered)];
75
73
 
76
- const unsignedTx = txBuilder.build();
77
-
78
- const { tx: signedTx } = await unsignedTx.sign();
74
+ const { tx: signedTx } = await txBuilder.build().sign();
79
75
 
80
76
  await wallet.submitTx(signedTx);
81
77
 
@@ -125,17 +121,17 @@ describe('PersonalWallet/multiAddress', () => {
125
121
  txBuilder = newWallet.wallet.createTxBuilder();
126
122
 
127
123
  const fundingWalletAddresses = await firstValueFromTimed(wallet.addresses$);
128
- const returnAdaOutput = await txBuilder
129
- .buildOutput()
130
- .address(fundingWalletAddresses[0].address)
131
- .coin(totalBalance.coins - 1_500_000n) // Let's leave some behind for fees.
132
- .build();
133
-
134
- txBuilder.addOutput(returnAdaOutput);
135
124
 
136
- const returnAdaUnsignedTx = await txBuilder.build();
137
-
138
- const { tx: returnAdaSignedTx } = await returnAdaUnsignedTx.sign();
125
+ const { tx: returnAdaSignedTx } = await txBuilder
126
+ .addOutput(
127
+ txBuilder
128
+ .buildOutput()
129
+ .address(fundingWalletAddresses[0].address)
130
+ .coin(totalBalance.coins - 1_500_000n) // Let's leave some behind for fees.
131
+ .toTxOut()
132
+ )
133
+ .build()
134
+ .sign();
139
135
  await newWallet.wallet.submitTx(returnAdaSignedTx);
140
136
 
141
137
  // Search chain history to see if the transaction is there.
@@ -16,6 +16,8 @@ UTXO_PROVIDER=http
16
16
  UTXO_PROVIDER_PARAMS='{"baseUrl":"http://localhost:4000/utxo"}'
17
17
  STAKE_POOL_PROVIDER=stub
18
18
  STAKE_POOL_PROVIDER_PARAMS='{"baseUrl":"http://localhost:4000/stake-pool"}'
19
+ HANDLE_PROVIDER=handleProvider
20
+ HANDLE_PROVIDER_PARAMS='{"serverUrl":"http://localhost:4000","policyId":""}'
19
21
 
20
22
  # Test Environment
21
23
  NETWORK_ID=0
@@ -9,7 +9,7 @@
9
9
  "unlimitedStorage"
10
10
  ],
11
11
  "content_security_policy": {
12
- "extension_pages": "default-src 'self' http://localhost:3000; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src data: http://localhost:8080 https://api.coingecko.com http://167.235.156.245:4000 http://localhost:3000 ws://localhost:3000 wss://localhost:3000 http://localhost:4000 ws://localhost:4000 wss://localhost:4000 http://testnet-dev-backend.dev.lw.iog.io:80 http://localhost:4567 https://testnet-dev-backend.dev.lw.iog.io:443 https://preprod-api.v2.prod.lw.iog.io; style-src * 'unsafe-inline'; img-src * data:; font-src https://fonts.gstatic.com;"
12
+ "extension_pages": "default-src 'self' http://localhost:3000; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src data: http://localhost:8080 https://backend.live-preprod.eks.lw.iog.io https://api.coingecko.com http://167.235.156.245:4000 http://localhost:3000 ws://localhost:3000 wss://localhost:3000 http://localhost:4000 ws://localhost:4000 wss://localhost:4000 http://testnet-dev-backend.dev.lw.iog.io:80 http://localhost:4567 https://testnet-dev-backend.dev.lw.iog.io:443 https://preprod-api.v2.prod.lw.iog.io; style-src * 'unsafe-inline'; img-src * data:; font-src https://fonts.gstatic.com;"
13
13
  },
14
14
  "web_accessible_resources": [
15
15
  {
@@ -9,7 +9,8 @@ require('dotenv').config({ path: path.join(__dirname, '../../', '.env') });
9
9
 
10
10
  module.exports = {
11
11
  baseConfig: {
12
- devtool: 'inline-source-map',
12
+ devtool: 'source-map',
13
+ ignoreWarnings: [/Failed to parse source map/],
13
14
  mode: 'development',
14
15
  module: {
15
16
  // configuration regarding modules
@@ -18,6 +19,11 @@ module.exports = {
18
19
  test: /docker\.js$/,
19
20
  use: 'null-loader'
20
21
  },
22
+ {
23
+ enforce: 'pre',
24
+ test: /\.js$/,
25
+ use: ['source-map-loader']
26
+ },
21
27
  {
22
28
  exclude: /node_modules/,
23
29
  resolve: {