@argonprotocol/testing 1.4.3 → 1.4.4

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.
package/lib/index.js CHANGED
@@ -6,23 +6,114 @@ var getDirname = () => path.dirname(getFilename());
6
6
  var __dirname = /* @__PURE__ */ getDirname();
7
7
 
8
8
  // src/index.ts
9
- import { Keyring as Keyring3, TxSubmitter as TxSubmitter2 } from "@argonprotocol/mainchain";
10
- import * as process4 from "process";
11
- import HttpProxy from "http-proxy";
12
- import * as child_process4 from "child_process";
13
- import * as http from "http";
14
- import * as url from "url";
15
- import * as Path6 from "path";
9
+ import { Keyring as Keyring4, TxSubmitter as TxSubmitter4 } from "@argonprotocol/mainchain";
16
10
 
17
11
  // src/TestNotary.ts
18
12
  import { customAlphabet } from "nanoid";
19
13
  import pg from "pg";
20
- import * as child_process from "child_process";
14
+ import * as child_process2 from "child_process";
21
15
  import { Keyring, TxSubmitter } from "@argonprotocol/mainchain";
22
16
  import * as fs from "fs";
23
17
  import * as readline from "readline";
18
+
19
+ // src/support.ts
24
20
  import * as process2 from "process";
21
+ import HttpProxy from "http-proxy";
22
+ import * as child_process from "child_process";
23
+ import * as http from "http";
24
+ import * as url from "url";
25
25
  import * as Path from "path";
26
+ var toTeardown = [];
27
+ var proxy = null;
28
+ var proxyServer = null;
29
+ var SKIP_E2E = process2.env.SKIP_E2E === "true" || process2.env.SKIP_E2E === "1";
30
+ async function getProxy() {
31
+ if (!proxy) {
32
+ proxy = HttpProxy.createProxyServer({
33
+ changeOrigin: true,
34
+ ws: true,
35
+ autoRewrite: true
36
+ });
37
+ proxy.on("error", () => null);
38
+ proxyServer = http.createServer(function(req, res) {
39
+ const queryData = url.parse(req.url, true).query;
40
+ if (!queryData.target) {
41
+ res.writeHead(500, { "Content-Type": "text/plain" });
42
+ res.end("Target parameter is required");
43
+ return;
44
+ }
45
+ console.log("Proxying http request", queryData.target);
46
+ proxy?.web(req, res, { target: queryData.target });
47
+ });
48
+ proxyServer.on("upgrade", function(req, clientSocket, head) {
49
+ const queryData = url.parse(req.url, true).query;
50
+ const target = url.parse(queryData.target);
51
+ proxy?.ws(req, clientSocket, head, {
52
+ target: target.href,
53
+ ws: true
54
+ });
55
+ clientSocket.on("error", console.error);
56
+ });
57
+ await new Promise((resolve3) => proxyServer.listen(0, resolve3));
58
+ toTeardown.push({
59
+ teardown: () => new Promise((resolve3) => {
60
+ proxy?.close();
61
+ proxyServer?.close((_) => null);
62
+ proxy = null;
63
+ proxyServer = null;
64
+ resolve3();
65
+ })
66
+ });
67
+ }
68
+ const port2 = proxyServer.address().port;
69
+ return `ws://host.docker.internal:${port2}`;
70
+ }
71
+ function projectRoot() {
72
+ if (process2.env.ARGON_PROJECT_ROOT) {
73
+ return Path.join(process2.env.ARGON_PROJECT_ROOT);
74
+ }
75
+ return Path.join(__dirname, `../../..`);
76
+ }
77
+ async function runTestScript(relativePath) {
78
+ const scriptPath = Path.resolve(projectRoot(), relativePath);
79
+ return child_process.execSync(scriptPath, { encoding: "utf8" }).trim();
80
+ }
81
+ async function getDockerPortMapping(containerName, port2) {
82
+ return child_process.execSync(`docker port ${containerName} ${port2}`, { encoding: "utf8" }).trim().split(":").pop();
83
+ }
84
+ async function teardown() {
85
+ for (const t of toTeardown) {
86
+ try {
87
+ await t.teardown().catch(console.error);
88
+ } catch {
89
+ }
90
+ }
91
+ toTeardown.length = 0;
92
+ }
93
+ function cleanHostForDocker(host, replacer = "host.docker.internal") {
94
+ if (process2.env.ARGON_USE_DOCKER_BINS) {
95
+ return host.replace("localhost", replacer).replace("127.0.0.1", replacer).replace("0.0.0.0", replacer);
96
+ }
97
+ return host;
98
+ }
99
+ function addTeardown(teardownable) {
100
+ toTeardown.push(teardownable);
101
+ }
102
+ function runOnTeardown(teardown2) {
103
+ addTeardown({ teardown: teardown2 });
104
+ }
105
+ function closeOnTeardown(closeable) {
106
+ addTeardown({ teardown: () => closeable.close() });
107
+ return closeable;
108
+ }
109
+ function disconnectOnTeardown(closeable) {
110
+ addTeardown({ teardown: () => closeable.disconnect() });
111
+ return closeable;
112
+ }
113
+
114
+ // src/TestNotary.ts
115
+ import * as process3 from "process";
116
+ import * as Path2 from "path";
26
117
  var { Client: PgClient } = pg;
27
118
  var nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 4);
28
119
  function createUid() {
@@ -48,7 +139,7 @@ var TestNotary = class {
48
139
  return `ws://${this.ip}:${this.port}`;
49
140
  }
50
141
  constructor(dbConnectionString) {
51
- this.#dbConnectionString = dbConnectionString ?? process2.env.NOTARY_DB_URL ?? "postgres://postgres:postgres@localhost:5432";
142
+ this.#dbConnectionString = dbConnectionString ?? process3.env.NOTARY_DB_URL ?? "postgres://postgres:postgres@localhost:5432";
52
143
  addTeardown(this);
53
144
  }
54
145
  /**
@@ -60,10 +151,10 @@ var TestNotary = class {
60
151
  this.registeredPublicKey = new Keyring({ type: "ed25519" }).createFromUri(
61
152
  "//Ferdie//notary"
62
153
  ).publicKey;
63
- let notaryPath = pathToNotaryBin ?? Path.join(projectRoot(), "target/debug/argon-notary");
64
- if (process2.env.ARGON_USE_DOCKER_BINS) {
154
+ let notaryPath = pathToNotaryBin ?? Path2.join(projectRoot(), "target/debug/argon-notary");
155
+ if (process3.env.ARGON_USE_DOCKER_BINS) {
65
156
  this.containerName = "notary_" + uuid;
66
- const addHost = process2.env.ADD_DOCKER_HOST ? ` --add-host=host.docker.internal:host-gateway` : "";
157
+ const addHost = process3.env.ADD_DOCKER_HOST ? ` --add-host=host.docker.internal:host-gateway` : "";
67
158
  notaryPath = `docker run --rm -p=0:9925${addHost} --name=${this.containerName} -e RUST_LOG=warn ghcr.io/argonprotocol/argon-notary:dev`;
68
159
  this.#dbConnectionString = cleanHostForDocker(this.#dbConnectionString);
69
160
  } else if (!fs.existsSync(notaryPath)) {
@@ -86,7 +177,7 @@ var TestNotary = class {
86
177
  } finally {
87
178
  await client.end();
88
179
  }
89
- const result = child_process.execSync(
180
+ const result = child_process2.execSync(
90
181
  `${notaryPath} migrate --db-url ${this.#dbConnectionString}/${this.#dbName}`,
91
182
  {
92
183
  encoding: "utf-8"
@@ -109,18 +200,18 @@ var TestNotary = class {
109
200
  `--archive-bucket=${bucketName}`,
110
201
  `--operator-address=${this.operator.address}`
111
202
  ];
112
- if (process2.env.ARGON_USE_DOCKER_BINS) {
113
- process2.env.AWS_S3_ENDPOINT = "http://host.docker.internal:9000";
203
+ if (process3.env.ARGON_USE_DOCKER_BINS) {
204
+ process3.env.AWS_S3_ENDPOINT = "http://host.docker.internal:9000";
114
205
  execArgs.unshift(...notaryPath.replace("docker run", "run").split(" "));
115
206
  execArgs.push("-b=0.0.0.0:9925");
116
207
  notaryPath = "docker";
117
208
  }
118
- if (process2.env.AWS_S3_ENDPOINT) {
119
- execArgs.push(`--archive-endpoint=${process2.env.AWS_S3_ENDPOINT}`);
209
+ if (process3.env.AWS_S3_ENDPOINT) {
210
+ execArgs.push(`--archive-endpoint=${process3.env.AWS_S3_ENDPOINT}`);
120
211
  }
121
- this.#childProcess = child_process.spawn(notaryPath, execArgs, {
212
+ this.#childProcess = child_process2.spawn(notaryPath, execArgs, {
122
213
  stdio: ["ignore", "pipe", "pipe"],
123
- env: { ...process2.env, RUST_LOG: "warn" }
214
+ env: { ...process3.env, RUST_LOG: "warn" }
124
215
  });
125
216
  this.#childProcess.stdout.setEncoding("utf8");
126
217
  this.#childProcess.stderr.setEncoding("utf8");
@@ -177,7 +268,7 @@ var TestNotary = class {
177
268
  }
178
269
  if (this.containerName) {
179
270
  try {
180
- child_process.execSync(`docker rm -f ${this.containerName}`);
271
+ child_process2.execSync(`docker rm -f ${this.containerName}`);
181
272
  } catch {
182
273
  }
183
274
  }
@@ -196,16 +287,15 @@ var TestNotary = class {
196
287
 
197
288
  // src/TestMainchain.ts
198
289
  import * as fs2 from "fs";
199
- import { execSync as execSync2, spawn as spawn2 } from "child_process";
200
- import * as Path2 from "path";
290
+ import { execSync as execSync3, spawn as spawn2 } from "child_process";
291
+ import * as Path3 from "path";
201
292
  import * as readline2 from "readline";
202
293
  import { detectPort } from "detect-port";
203
294
  import { customAlphabet as customAlphabet2 } from "nanoid";
204
- import Client from "bitcoin-core";
205
295
  import * as lockfile from "proper-lockfile";
206
296
  import { getClient } from "@argonprotocol/mainchain";
207
297
  var nanoid2 = customAlphabet2("0123456789abcdefghijklmnopqrstuvwxyz", 4);
208
- var lockPath = Path2.join(process.cwd(), ".port-lock");
298
+ var lockPath = Path3.join(process.cwd(), ".port-lock");
209
299
  var TestMainchain = class {
210
300
  ip = "127.0.0.1";
211
301
  port;
@@ -228,8 +318,8 @@ var TestMainchain = class {
228
318
  return `ws://${this.ip}:${this.port}`;
229
319
  }
230
320
  constructor(binPath) {
231
- this.#binPath = binPath ?? Path2.join(projectRoot(), `target/debug/argon-node`);
232
- this.#binPath = Path2.resolve(this.#binPath);
321
+ this.#binPath = binPath ?? Path3.join(projectRoot(), `target/debug/argon-node`);
322
+ this.#binPath = Path3.resolve(this.#binPath);
233
323
  if (!process.env.ARGON_USE_DOCKER_BINS && !fs2.existsSync(this.#binPath)) {
234
324
  throw new Error(`Mainchain binary not found at ${this.#binPath}`);
235
325
  }
@@ -237,11 +327,7 @@ var TestMainchain = class {
237
327
  addTeardown(this);
238
328
  }
239
329
  getBitcoinClient() {
240
- return new Client({
241
- username: "bitcoin",
242
- password: "bitcoin",
243
- host: `http://localhost:${this.bitcoinPort}`
244
- });
330
+ return new BitcoinRpcClient(`http://localhost:${this.bitcoinPort}`, "bitcoin", "bitcoin");
245
331
  }
246
332
  /**
247
333
  * Launch and return the localhost url. NOTE: this url will not work cross-docker. You need to use the containerAddress property
@@ -331,9 +417,20 @@ var TestMainchain = class {
331
417
  return this.address;
332
418
  }
333
419
  async client() {
334
- const client = await getClient(this.address);
335
- disconnectOnTeardown(client);
336
- return client;
420
+ let lastError;
421
+ for (let attempt = 0; attempt < 20; attempt += 1) {
422
+ try {
423
+ const client = await getClient(this.address);
424
+ disconnectOnTeardown(client);
425
+ return client;
426
+ } catch (error) {
427
+ lastError = error;
428
+ await new Promise((resolve3) => setTimeout(resolve3, 250));
429
+ }
430
+ }
431
+ throw new Error(`Unable to connect to mainchain client at ${this.address}`, {
432
+ cause: lastError instanceof Error ? lastError : void 0
433
+ });
337
434
  }
338
435
  async bootAddress() {
339
436
  const client = await this.client();
@@ -349,7 +446,7 @@ var TestMainchain = class {
349
446
  async teardown() {
350
447
  if (process.env.ARGON_USE_DOCKER_BINS) {
351
448
  try {
352
- execSync2(`docker rm -f ${this.containerName}`);
449
+ execSync3(`docker rm -f ${this.containerName}`);
353
450
  } catch {
354
451
  }
355
452
  }
@@ -383,7 +480,7 @@ var TestMainchain = class {
383
480
  const release = await lockfile.lock(lockPath, { retries: 10 });
384
481
  try {
385
482
  rpcPort = await detectPort();
386
- const path2 = execSync2(Path2.join(projectRoot(), `target/debug/argon-testing-bitcoin`), {
483
+ const path2 = execSync3(Path3.join(projectRoot(), `target/debug/argon-testing-bitcoin`), {
387
484
  encoding: "utf8"
388
485
  }).trim();
389
486
  const tmpDir = fs2.mkdtempSync("/tmp/argon-bitcoin-" + this.uuid);
@@ -424,18 +521,63 @@ var TestMainchain = class {
424
521
  return cleanHostForDocker(`http://bitcoin:bitcoin@localhost:${rpcPort}`);
425
522
  }
426
523
  };
524
+ var BitcoinRpcClient = class {
525
+ #rpcUrl;
526
+ #authorization;
527
+ constructor(rpcUrl, username, password) {
528
+ this.#rpcUrl = rpcUrl;
529
+ this.#authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
530
+ }
531
+ async command(method, ...params) {
532
+ const response = await fetch(this.#rpcUrl, {
533
+ method: "POST",
534
+ headers: {
535
+ authorization: this.#authorization,
536
+ "content-type": "application/json"
537
+ },
538
+ body: JSON.stringify({
539
+ jsonrpc: "1.0",
540
+ id: `${method}-${Date.now()}`,
541
+ method,
542
+ params
543
+ })
544
+ });
545
+ const body = await response.text();
546
+ let payload;
547
+ if (body) {
548
+ try {
549
+ payload = JSON.parse(body);
550
+ } catch {
551
+ payload = void 0;
552
+ }
553
+ }
554
+ if (payload?.error) {
555
+ const httpStatus = response.ok ? "" : ` with HTTP ${response.status}`;
556
+ throw new Error(
557
+ `Bitcoin RPC ${method} failed${httpStatus} (${payload.error.code}): ${payload.error.message}`
558
+ );
559
+ }
560
+ if (!response.ok) {
561
+ throw new Error(`Bitcoin RPC ${method} failed with HTTP ${response.status}`);
562
+ }
563
+ if (!payload) {
564
+ throw new Error(`Bitcoin RPC ${method} returned an invalid JSON response`);
565
+ }
566
+ return payload.result;
567
+ }
568
+ };
427
569
 
428
570
  // src/TestBitcoinCli.ts
429
- import * as child_process2 from "child_process";
430
- import * as Path3 from "path";
571
+ import * as child_process3 from "child_process";
572
+ import * as Path4 from "path";
431
573
  var TestBitcoinCli = class {
432
574
  /**
433
575
  * Returns the localhost address of the notary (NOTE: not accessible from containers)
434
576
  */
435
577
  static run(command) {
436
- const binPath = Path3.join(`${projectRoot()}`, "target/debug/argon-bitcoin-cli");
578
+ const binPath = Path4.join(`${projectRoot()}`, "target/debug/argon-bitcoin-cli");
437
579
  try {
438
- return child_process2.execSync(`${binPath} ${command}`, {
580
+ return child_process3.execSync(`${binPath} ${command}`, {
439
581
  encoding: "utf8"
440
582
  }).trim();
441
583
  } catch (e) {
@@ -447,12 +589,12 @@ var TestBitcoinCli = class {
447
589
  };
448
590
 
449
591
  // src/TestOracle.ts
450
- import * as child_process3 from "child_process";
592
+ import * as child_process4 from "child_process";
451
593
  import { Keyring as Keyring2 } from "@argonprotocol/mainchain";
452
594
  import * as fs3 from "fs";
453
595
  import * as readline3 from "readline";
454
- import * as process3 from "process";
455
- import * as Path4 from "path";
596
+ import * as process4 from "process";
597
+ import * as Path5 from "path";
456
598
  var TestOracle = class _TestOracle {
457
599
  static BitcoinOperator = "//Dave";
458
600
  static PriceIndexOperator = "//Eve";
@@ -467,7 +609,7 @@ var TestOracle = class _TestOracle {
467
609
  const { pathToBin, mainchainUrl, bitcoinRpcUrl } = options;
468
610
  const operatorSuri = service == "bitcoin" ? _TestOracle.BitcoinOperator : _TestOracle.PriceIndexOperator;
469
611
  this.operator = new Keyring2({ type: "sr25519" }).createFromUri(operatorSuri);
470
- const binPath = pathToBin ?? Path4.join(projectRoot(), "target/debug/argon-oracle");
612
+ const binPath = pathToBin ?? Path5.join(projectRoot(), "target/debug/argon-oracle");
471
613
  if (!fs3.existsSync(binPath)) {
472
614
  throw new Error(`Oracle binary not found at ${binPath}`);
473
615
  }
@@ -479,9 +621,9 @@ var TestOracle = class _TestOracle {
479
621
  }
480
622
  execArgs.push("--bitcoin-rpc-url", bitcoinRpcUrl);
481
623
  }
482
- this.#childProcess = child_process3.spawn(binPath, execArgs, {
624
+ this.#childProcess = child_process4.spawn(binPath, execArgs, {
483
625
  stdio: ["ignore", "pipe", "pipe"],
484
- env: { ...process3.env, RUST_LOG: "info", ...options.env }
626
+ env: { ...process4.env, RUST_LOG: "info", ...options.env }
485
627
  });
486
628
  this.#childProcess.stdout.setEncoding("utf8");
487
629
  this.#childProcess.stderr.setEncoding("utf8");
@@ -501,11 +643,475 @@ var TestOracle = class _TestOracle {
501
643
  }
502
644
  };
503
645
 
646
+ // src/TestEthereum.ts
647
+ import * as fs4 from "fs/promises";
648
+ import * as os from "os";
649
+ import * as Path6 from "path";
650
+ import { spawn as spawn4, spawnSync } from "child_process";
651
+ import { detectPort as detectPort2 } from "detect-port";
652
+ import { EvmContracts } from "@argonprotocol/mainchain";
653
+ import { privateKeyToAccount } from "viem/accounts";
654
+ import {
655
+ createPublicClient,
656
+ createWalletClient,
657
+ defineChain,
658
+ encodeFunctionData,
659
+ getAddress,
660
+ http as http2,
661
+ zeroAddress
662
+ } from "viem";
663
+ var {
664
+ argonTokenArtifact,
665
+ argonotTokenArtifact,
666
+ hashMintingGatewayGlobalIssuanceCouncil,
667
+ mintingGatewayArtifact,
668
+ proxyAdminArtifact,
669
+ transparentUpgradeableProxyArtifact
670
+ } = EvmContracts;
671
+ var DEFAULT_KURTOSIS_BIN = "kurtosis";
672
+ var DEFAULT_ETHEREUM_PACKAGE = "github.com/ethpandaops/ethereum-package";
673
+ var DEFAULT_EL_PORT_START = 32e3;
674
+ var DEFAULT_CL_PORT_START = 33e3;
675
+ var PORT_RANGE_SIZE = 32;
676
+ var ENCLAVE_NAME_PREFIX = "argon-eth-";
677
+ var PROBE_INTERVAL_MS = 1e3;
678
+ var PROBE_TIMEOUT_MS = 6e4;
679
+ var LIGHT_CLIENT_READY_TIMEOUT_MS = 5 * 6e4;
680
+ var KURTOSIS_RUN_TIMEOUT_MS = 20 * 6e4;
681
+ var DEFAULT_SEED_ARGON_AMOUNT_BASE_UNITS = 1000000000n;
682
+ var DEFAULT_INITIAL_MICROGONS_PER_ARGONOT = 1000000n;
683
+ var ERC1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
684
+ var TestEthereum = class {
685
+ enclaveName;
686
+ kurtosisBin;
687
+ packageRef;
688
+ executionRpcUrl;
689
+ beaconApiUrl;
690
+ chainId;
691
+ #argsDir;
692
+ constructor(enclaveName = `${ENCLAVE_NAME_PREFIX}${Math.random().toString(36).slice(2, 8)}`, kurtosisBin = DEFAULT_KURTOSIS_BIN, packageRef = DEFAULT_ETHEREUM_PACKAGE) {
693
+ this.enclaveName = enclaveName;
694
+ this.kurtosisBin = kurtosisBin;
695
+ this.packageRef = packageRef;
696
+ addTeardown(this);
697
+ }
698
+ static isInstalled(kurtosisBin = DEFAULT_KURTOSIS_BIN) {
699
+ return spawnSync(kurtosisBin, ["version"], { stdio: "ignore" }).status === 0;
700
+ }
701
+ async launch(options) {
702
+ const {
703
+ consensusClient = "lighthouse",
704
+ preset = "mainnet",
705
+ secondsPerSlot,
706
+ waitForFinalization = true,
707
+ prefundedAccounts
708
+ } = options ?? {};
709
+ const elPublicPortStart = await findFreePortRange(DEFAULT_EL_PORT_START, PORT_RANGE_SIZE);
710
+ const clPublicPortStart = await findFreePortRange(DEFAULT_CL_PORT_START, PORT_RANGE_SIZE);
711
+ this.#argsDir = await fs4.mkdtemp(Path6.join(os.tmpdir(), "argon-ethereum-devnet-"));
712
+ const argsFile = Path6.join(this.#argsDir, "network-params.yaml");
713
+ await fs4.writeFile(
714
+ argsFile,
715
+ renderEthereumArgs(
716
+ elPublicPortStart,
717
+ clPublicPortStart,
718
+ consensusClient,
719
+ preset,
720
+ secondsPerSlot,
721
+ waitForFinalization,
722
+ prefundedAccounts
723
+ )
724
+ );
725
+ await runCommand(
726
+ this.kurtosisBin,
727
+ ["run", "--enclave", this.enclaveName, this.packageRef, "--args-file", argsFile],
728
+ KURTOSIS_RUN_TIMEOUT_MS
729
+ );
730
+ const executionRpc = await waitForProbe(
731
+ () => findExecutionRpcUrl(elPublicPortStart, PORT_RANGE_SIZE),
732
+ PROBE_TIMEOUT_MS
733
+ );
734
+ const beaconApi = await waitForProbe(
735
+ () => findBeaconApiUrl(clPublicPortStart, PORT_RANGE_SIZE),
736
+ PROBE_TIMEOUT_MS
737
+ );
738
+ this.executionRpcUrl = executionRpc.url;
739
+ this.beaconApiUrl = beaconApi.url;
740
+ this.chainId = executionRpc.chainId;
741
+ await waitForProbe(
742
+ () => this.getBeacon("/eth/v1/beacon/genesis"),
743
+ LIGHT_CLIENT_READY_TIMEOUT_MS
744
+ );
745
+ return {
746
+ executionRpcUrl: this.executionRpcUrl,
747
+ beaconApiUrl: this.beaconApiUrl,
748
+ chainId: this.chainId
749
+ };
750
+ }
751
+ async callExecution(method, params = []) {
752
+ const executionRpcUrl = this.executionRpcUrl;
753
+ if (!executionRpcUrl) {
754
+ throw new Error("Execution RPC URL is not available before launch");
755
+ }
756
+ const response = await fetch(executionRpcUrl, {
757
+ method: "POST",
758
+ headers: { "content-type": "application/json" },
759
+ body: JSON.stringify({
760
+ id: 1,
761
+ jsonrpc: "2.0",
762
+ method,
763
+ params
764
+ }),
765
+ signal: AbortSignal.timeout(1e4)
766
+ });
767
+ if (!response.ok) {
768
+ throw new Error(`Execution RPC request failed for ${method}: ${response.status}`);
769
+ }
770
+ const body = await response.json();
771
+ if (body.error) {
772
+ throw new Error(
773
+ `Execution RPC ${method} failed (${body.error.code ?? "unknown"}): ${body.error.message ?? "unknown error"}`
774
+ );
775
+ }
776
+ return body.result;
777
+ }
778
+ async getBeacon(path2) {
779
+ const beaconApiUrl = this.beaconApiUrl;
780
+ if (!beaconApiUrl) {
781
+ throw new Error("Beacon API URL is not available before launch");
782
+ }
783
+ const response = await fetch(new URL(path2, `${beaconApiUrl}/`), {
784
+ signal: AbortSignal.timeout(1e4)
785
+ });
786
+ if (!response.ok) {
787
+ throw new Error(`Beacon API request failed for ${path2}: ${response.status}`);
788
+ }
789
+ return await response.json();
790
+ }
791
+ async deployMintingGatewayFixture(options) {
792
+ const { executionRpcUrl, chainId } = this;
793
+ if (!executionRpcUrl || !chainId) {
794
+ throw new Error("Ethereum devnet must be launched before deploying MintingGateway fixtures");
795
+ }
796
+ const account = privateKeyToAccount(options.deployerPrivateKey);
797
+ const adminSafe = options.adminSafe ?? account.address;
798
+ const guardianSafe = options.guardianSafe ?? adminSafe;
799
+ const chain = createExecutionChain(chainId, executionRpcUrl);
800
+ const publicClient = createPublicClient({
801
+ chain,
802
+ transport: http2(executionRpcUrl)
803
+ });
804
+ const walletClient = createWalletClient({
805
+ account,
806
+ chain,
807
+ transport: http2(executionRpcUrl)
808
+ });
809
+ const bootstrapCouncil = {
810
+ signers: [adminSafe],
811
+ weights: [1n]
812
+ };
813
+ const initialMicrogonsPerArgonot = options.initialMicrogonsPerArgonot ?? DEFAULT_INITIAL_MICROGONS_PER_ARGONOT;
814
+ const bootstrapCouncilHash = hashMintingGatewayGlobalIssuanceCouncil({
815
+ ...bootstrapCouncil,
816
+ epochMicrogonsPerArgonot: initialMicrogonsPerArgonot
817
+ });
818
+ const bootstrapImplementationAddress = await deployContract(walletClient, publicClient, {
819
+ abi: mintingGatewayArtifact.abi,
820
+ bytecode: mintingGatewayArtifact.bytecode,
821
+ args: [zeroAddress, zeroAddress]
822
+ });
823
+ const initializeData = encodeFunctionData({
824
+ abi: mintingGatewayArtifact.abi,
825
+ functionName: "initialize",
826
+ args: [
827
+ adminSafe,
828
+ guardianSafe,
829
+ bootstrapCouncilHash,
830
+ BigInt(bootstrapCouncil.signers.length),
831
+ 1n,
832
+ initialMicrogonsPerArgonot
833
+ ]
834
+ });
835
+ const gatewayAddress = await deployContract(walletClient, publicClient, {
836
+ abi: transparentUpgradeableProxyArtifact.abi,
837
+ bytecode: transparentUpgradeableProxyArtifact.bytecode,
838
+ args: [bootstrapImplementationAddress, adminSafe, initializeData]
839
+ });
840
+ const proxyAdminAddress = getAddressFromStorage(
841
+ await publicClient.getStorageAt({
842
+ address: gatewayAddress,
843
+ slot: ERC1967_ADMIN_SLOT
844
+ })
845
+ );
846
+ const argonTokenAddress = await deployContract(walletClient, publicClient, {
847
+ abi: argonTokenArtifact.abi,
848
+ bytecode: argonTokenArtifact.bytecode,
849
+ args: [gatewayAddress]
850
+ });
851
+ const argonotTokenAddress = await deployContract(walletClient, publicClient, {
852
+ abi: argonotTokenArtifact.abi,
853
+ bytecode: argonotTokenArtifact.bytecode,
854
+ args: [gatewayAddress]
855
+ });
856
+ const finalImplementationAddress = await deployContract(walletClient, publicClient, {
857
+ abi: mintingGatewayArtifact.abi,
858
+ bytecode: mintingGatewayArtifact.bytecode,
859
+ args: [argonTokenAddress, argonotTokenAddress]
860
+ });
861
+ const upgradeHash = await walletClient.sendTransaction({
862
+ to: proxyAdminAddress,
863
+ data: encodeFunctionData({
864
+ abi: proxyAdminArtifact.abi,
865
+ functionName: "upgradeAndCall",
866
+ args: [gatewayAddress, finalImplementationAddress, "0x"]
867
+ })
868
+ });
869
+ const upgradeReceipt = await waitForExecutionReceipt(publicClient, upgradeHash);
870
+ if (upgradeReceipt.status !== "success") {
871
+ throw new Error("MintingGateway proxy upgrade failed");
872
+ }
873
+ if (options.seedArgonRecipient) {
874
+ const mintHash = await walletClient.sendTransaction({
875
+ to: gatewayAddress,
876
+ data: encodeFunctionData({
877
+ abi: mintingGatewayArtifact.abi,
878
+ functionName: "migrate",
879
+ args: [
880
+ {
881
+ recipients: [options.seedArgonRecipient],
882
+ amounts: [options.seedArgonAmountBaseUnits ?? DEFAULT_SEED_ARGON_AMOUNT_BASE_UNITS]
883
+ },
884
+ {
885
+ recipients: [],
886
+ amounts: []
887
+ }
888
+ ]
889
+ })
890
+ });
891
+ const mintReceipt = await waitForExecutionReceipt(publicClient, mintHash);
892
+ if (mintReceipt.status !== "success") {
893
+ throw new Error("MintingGateway migrate failed");
894
+ }
895
+ }
896
+ return {
897
+ argonTokenAddress,
898
+ argonotTokenAddress,
899
+ gatewayAddress
900
+ };
901
+ }
902
+ async teardown() {
903
+ if (this.#argsDir) {
904
+ await fs4.rm(this.#argsDir, { recursive: true, force: true });
905
+ this.#argsDir = void 0;
906
+ }
907
+ await runCommand(this.kurtosisBin, ["enclave", "rm", "-f", this.enclaveName], 6e4, true);
908
+ }
909
+ };
910
+ async function deployContract(walletClient, publicClient, request) {
911
+ const hash = await walletClient.deployContract({
912
+ ...request,
913
+ account: walletClient.account,
914
+ chain: walletClient.chain
915
+ });
916
+ const receipt = await waitForExecutionReceipt(publicClient, hash);
917
+ if (receipt.status !== "success" || !receipt.contractAddress) {
918
+ throw new Error(`Contract deployment failed for ${request.bytecode.slice(0, 10)}`);
919
+ }
920
+ return receipt.contractAddress;
921
+ }
922
+ function getAddressFromStorage(value) {
923
+ if (!value || value === "0x") {
924
+ throw new Error("Missing proxy admin address in ERC1967 admin slot");
925
+ }
926
+ return getAddress(`0x${value.slice(-40)}`);
927
+ }
928
+ function renderEthereumArgs(elPublicPortStart, clPublicPortStart, consensusClient, preset, secondsPerSlot, waitForFinalization, prefundedAccounts) {
929
+ const lines = [
930
+ "participants:",
931
+ " - el_type: geth",
932
+ ` cl_type: ${consensusClient}`,
933
+ "network_params:",
934
+ " network: kurtosis",
935
+ ` preset: ${preset}`,
936
+ ...secondsPerSlot ? [` seconds_per_slot: ${secondsPerSlot}`] : [],
937
+ ...prefundedAccounts && Object.keys(prefundedAccounts).length > 0 ? [` prefunded_accounts: '${JSON.stringify(prefundedAccounts)}'`] : [],
938
+ "additional_services: []",
939
+ `wait_for_finalization: ${waitForFinalization ? "true" : "false"}`,
940
+ "global_log_level: warn",
941
+ "port_publisher:",
942
+ " el:",
943
+ " enabled: true",
944
+ ` public_port_start: ${elPublicPortStart}`,
945
+ " cl:",
946
+ " enabled: true",
947
+ ` public_port_start: ${clPublicPortStart}`
948
+ ];
949
+ lines.push("");
950
+ return lines.join("\n");
951
+ }
952
+ async function findExecutionRpcUrl(portStart, rangeSize) {
953
+ for (let port2 = portStart; port2 < portStart + rangeSize; port2 += 1) {
954
+ const url2 = `http://127.0.0.1:${port2}`;
955
+ const response = await fetchJsonRpc(url2, "eth_chainId");
956
+ if (typeof response === "string") {
957
+ return { url: url2, chainId: response };
958
+ }
959
+ }
960
+ throw new Error(
961
+ `Unable to find an execution RPC endpoint in ${portStart}-${portStart + rangeSize - 1}`
962
+ );
963
+ }
964
+ async function findBeaconApiUrl(portStart, rangeSize) {
965
+ for (let port2 = portStart; port2 < portStart + rangeSize; port2 += 1) {
966
+ const url2 = `http://127.0.0.1:${port2}`;
967
+ try {
968
+ const response = await fetch(new URL("/eth/v1/node/version", `${url2}/`), {
969
+ signal: AbortSignal.timeout(2e3)
970
+ });
971
+ if (!response.ok) {
972
+ continue;
973
+ }
974
+ const body = await response.json();
975
+ if (body.data?.version) {
976
+ return { url: url2 };
977
+ }
978
+ } catch {
979
+ }
980
+ }
981
+ throw new Error(
982
+ `Unable to find a Beacon API endpoint in ${portStart}-${portStart + rangeSize - 1}`
983
+ );
984
+ }
985
+ async function fetchJsonRpc(url2, method) {
986
+ try {
987
+ const response = await fetch(url2, {
988
+ method: "POST",
989
+ headers: { "content-type": "application/json" },
990
+ body: JSON.stringify({
991
+ id: 1,
992
+ jsonrpc: "2.0",
993
+ method,
994
+ params: []
995
+ }),
996
+ signal: AbortSignal.timeout(2e3)
997
+ });
998
+ if (!response.ok) {
999
+ return null;
1000
+ }
1001
+ const body = await response.json();
1002
+ return body.result ?? null;
1003
+ } catch {
1004
+ return null;
1005
+ }
1006
+ }
1007
+ function createExecutionChain(chainId, executionRpcUrl) {
1008
+ return defineChain({
1009
+ id: Number.parseInt(chainId, 16),
1010
+ name: "argon-test-ethereum",
1011
+ nativeCurrency: {
1012
+ name: "Ether",
1013
+ symbol: "ETH",
1014
+ decimals: 18
1015
+ },
1016
+ rpcUrls: {
1017
+ default: {
1018
+ http: [executionRpcUrl]
1019
+ }
1020
+ }
1021
+ });
1022
+ }
1023
+ async function findFreePortRange(start, size) {
1024
+ for (let candidate = start; candidate < start + 1e3; candidate += size) {
1025
+ const ports = Array.from({ length: size }, (_, index) => candidate + index);
1026
+ const results = await Promise.all(ports.map((port2) => detectPort2(port2)));
1027
+ if (results.every((resolvedPort, index) => resolvedPort === ports[index])) {
1028
+ return candidate;
1029
+ }
1030
+ }
1031
+ throw new Error(`Unable to find a free port range starting near ${start}`);
1032
+ }
1033
+ async function waitForProbe(probe, timeoutMs) {
1034
+ const start = Date.now();
1035
+ let lastError;
1036
+ while (Date.now() - start < timeoutMs) {
1037
+ try {
1038
+ return await probe();
1039
+ } catch (error) {
1040
+ lastError = error;
1041
+ await delay(PROBE_INTERVAL_MS);
1042
+ }
1043
+ }
1044
+ throw lastError instanceof Error ? lastError : new Error("Timed out waiting for probe");
1045
+ }
1046
+ async function waitForExecutionReceipt(publicClient, hash) {
1047
+ const start = Date.now();
1048
+ let lastError;
1049
+ while (Date.now() - start < 12e4) {
1050
+ try {
1051
+ const receipt = await publicClient.getTransactionReceipt({ hash });
1052
+ if (receipt) {
1053
+ return receipt;
1054
+ }
1055
+ } catch (error) {
1056
+ const errorText = error instanceof Error ? [
1057
+ error.message,
1058
+ "details" in error && typeof error.details === "string" ? error.details : void 0
1059
+ ].filter(Boolean).join(" ") : String(error);
1060
+ if (!errorText.includes("indexing is in progress") && !errorText.includes("Transaction receipt with hash") && !errorText.includes("could not be found")) {
1061
+ throw error;
1062
+ }
1063
+ lastError = error instanceof Error ? error : new Error(errorText);
1064
+ }
1065
+ await delay(500);
1066
+ }
1067
+ throw lastError ?? new Error(`Timed out waiting for execution receipt ${hash}`);
1068
+ }
1069
+ async function runCommand(command, args, timeoutMs, allowFailure = false) {
1070
+ await new Promise((resolve3, reject) => {
1071
+ const child = spawn4(command, args, {
1072
+ stdio: ["ignore", "pipe", "pipe"]
1073
+ });
1074
+ let stdout = "";
1075
+ let stderr = "";
1076
+ const timeout = setTimeout(() => {
1077
+ child.kill("SIGTERM");
1078
+ reject(new Error(`Command timed out: ${command} ${args.join(" ")}`));
1079
+ }, timeoutMs);
1080
+ child.stdout?.setEncoding("utf8");
1081
+ child.stderr?.setEncoding("utf8");
1082
+ child.stdout?.on("data", (chunk) => {
1083
+ stdout += chunk;
1084
+ });
1085
+ child.stderr?.on("data", (chunk) => {
1086
+ stderr += chunk;
1087
+ });
1088
+ child.on("error", (error) => {
1089
+ clearTimeout(timeout);
1090
+ reject(error);
1091
+ });
1092
+ child.on("exit", (code) => {
1093
+ clearTimeout(timeout);
1094
+ if (code === 0 || allowFailure) {
1095
+ resolve3();
1096
+ return;
1097
+ }
1098
+ reject(
1099
+ new Error(
1100
+ [`Command failed: ${command} ${args.join(" ")}`, stdout.trim(), stderr.trim()].filter(Boolean).join("\n")
1101
+ )
1102
+ );
1103
+ });
1104
+ });
1105
+ }
1106
+ async function delay(ms) {
1107
+ await new Promise((resolve3) => setTimeout(resolve3, ms));
1108
+ }
1109
+
504
1110
  // src/TestNetwork.ts
505
1111
  import * as docker from "docker-compose";
506
- import * as Path5 from "path";
1112
+ import * as Path7 from "path";
507
1113
  async function startNetwork(testName, options) {
508
- const config = Path5.join(__dirname, `docker-compose.yml`);
1114
+ const config = Path7.join(__dirname, `dev.docker-compose.yml`);
509
1115
  const env4 = {
510
1116
  VERSION: "dev",
511
1117
  ARGON_CHAIN: "dev-docker",
@@ -537,52 +1143,1081 @@ async function startNetwork(testName, options) {
537
1143
  };
538
1144
  }
539
1145
 
540
- // src/index.ts
541
- var toTeardown = [];
542
- var proxy = null;
543
- var proxyServer = null;
544
- var SKIP_E2E = process4.env.SKIP_E2E === "true" || process4.env.SKIP_E2E === "1";
545
- async function getProxy() {
546
- if (!proxy) {
547
- proxy = HttpProxy.createProxyServer({
548
- changeOrigin: true,
549
- ws: true,
550
- autoRewrite: true
551
- });
552
- proxy.on("error", () => null);
553
- proxyServer = http.createServer(function(req, res) {
554
- const queryData = url.parse(req.url, true).query;
555
- if (!queryData.target) {
556
- res.writeHead(500, { "Content-Type": "text/plain" });
557
- res.end("Target parameter is required");
558
- return;
1146
+ // src/EthereumE2eUtils.ts
1147
+ import {
1148
+ getLatestArgonFinalizedExecutionHeader,
1149
+ getNextEthereumBeaconSyncTxs,
1150
+ hexToU8a,
1151
+ isOutdatedTransactionError,
1152
+ TxSubmitter as TxSubmitter2,
1153
+ u8aToHex
1154
+ } from "@argonprotocol/mainchain";
1155
+ import { parseSignature } from "viem";
1156
+ async function signGatewayPermit(args) {
1157
+ const signature = parseSignature(
1158
+ await args.account.signTypedData({
1159
+ domain: {
1160
+ name: "Argon",
1161
+ version: "1",
1162
+ chainId: args.chainId,
1163
+ verifyingContract: args.tokenAddress
1164
+ },
1165
+ types: {
1166
+ Permit: [
1167
+ { name: "owner", type: "address" },
1168
+ { name: "spender", type: "address" },
1169
+ { name: "value", type: "uint256" },
1170
+ { name: "nonce", type: "uint256" },
1171
+ { name: "deadline", type: "uint256" }
1172
+ ]
1173
+ },
1174
+ primaryType: "Permit",
1175
+ message: {
1176
+ owner: args.owner,
1177
+ spender: args.gatewayAddress,
1178
+ value: args.value,
1179
+ nonce: args.nonce,
1180
+ deadline: args.deadline
559
1181
  }
560
- console.log("Proxying http request", queryData.target);
561
- proxy?.web(req, res, { target: queryData.target });
1182
+ })
1183
+ );
1184
+ return {
1185
+ v: Number(signature.v),
1186
+ r: signature.r,
1187
+ s: signature.s
1188
+ };
1189
+ }
1190
+ async function waitForFinalizedBeaconExecutionAtOrAbove(ethereum, minimumExecutionBlockNumber, options = {}) {
1191
+ const startedAt = Date.now();
1192
+ const minimumFinalizedSlot = options.minimumFinalizedSlot ?? 0n;
1193
+ let lastSeenExecutionBlockNumber = 0n;
1194
+ let lastSeenHeadSlot = 0n;
1195
+ let lastSeenFinalizedSlot = 0n;
1196
+ let lastError;
1197
+ while (Date.now() - startedAt < 3e5) {
1198
+ try {
1199
+ const [headHeader, finalizedHeader] = await Promise.all([
1200
+ ethereum.getBeacon("/eth/v1/beacon/headers/head"),
1201
+ ethereum.getBeacon("/eth/v1/beacon/headers/finalized")
1202
+ ]);
1203
+ lastSeenHeadSlot = BigInt(headHeader.data.header.message.slot);
1204
+ lastSeenFinalizedSlot = BigInt(finalizedHeader.data.header.message.slot);
1205
+ const block = await ethereum.getBeacon(
1206
+ `/eth/v2/beacon/blocks/${finalizedHeader.data.root}`
1207
+ );
1208
+ const executionBlockNumber = BigInt(block.data.message.body.execution_payload.block_number);
1209
+ lastSeenExecutionBlockNumber = executionBlockNumber;
1210
+ lastError = void 0;
1211
+ if (executionBlockNumber >= minimumExecutionBlockNumber && lastSeenFinalizedSlot >= minimumFinalizedSlot) {
1212
+ return { header: finalizedHeader, block };
1213
+ }
1214
+ } catch (error) {
1215
+ if (!(error instanceof Error)) {
1216
+ throw error;
1217
+ }
1218
+ lastError = error;
1219
+ }
1220
+ await delay2(1e3);
1221
+ }
1222
+ const lastErrorSuffix = lastError ? `; last beacon error was: ${lastError.message}` : "";
1223
+ throw new Error(
1224
+ `Timed out waiting for finalized beacon execution block at or above ${minimumExecutionBlockNumber} and finalized slot at or above ${minimumFinalizedSlot}; last seen head slot was ${lastSeenHeadSlot}, finalized slot was ${lastSeenFinalizedSlot}, and finalized execution block was ${lastSeenExecutionBlockNumber}${lastErrorSuffix}`
1225
+ );
1226
+ }
1227
+ async function mineLaterExecutionAnchorReceipt(walletClient, chain, ethereum, account, minimumBlockNumber) {
1228
+ while (true) {
1229
+ const transactionHash = await walletClient.sendTransaction({
1230
+ account,
1231
+ chain,
1232
+ to: account.address,
1233
+ value: 0n
562
1234
  });
563
- proxyServer.on("upgrade", function(req, clientSocket, head) {
564
- const queryData = url.parse(req.url, true).query;
565
- const target = url.parse(queryData.target);
566
- proxy?.ws(req, clientSocket, head, {
567
- target: target.href,
568
- ws: true
1235
+ const receipt = await waitForExecutionReceipt2(ethereum, transactionHash);
1236
+ if (BigInt(receipt.blockNumber) > minimumBlockNumber) {
1237
+ return receipt;
1238
+ }
1239
+ }
1240
+ }
1241
+ async function waitForExecutionReceipt2(ethereum, transactionHash) {
1242
+ const startedAt = Date.now();
1243
+ while (Date.now() - startedAt < 12e4) {
1244
+ try {
1245
+ const receipt = await ethereum.callExecution(
1246
+ "eth_getTransactionReceipt",
1247
+ [transactionHash]
1248
+ );
1249
+ if (receipt) {
1250
+ return receipt;
1251
+ }
1252
+ } catch (error) {
1253
+ const errorText = error instanceof Error ? [
1254
+ error.message,
1255
+ "details" in error && typeof error.details === "string" ? error.details : void 0
1256
+ ].filter(Boolean).join(" ") : String(error);
1257
+ if (!errorText.includes("indexing is in progress")) {
1258
+ throw error;
1259
+ }
1260
+ }
1261
+ await delay2(500);
1262
+ }
1263
+ throw new Error(`Timed out waiting for execution receipt ${transactionHash}`);
1264
+ }
1265
+ async function syncEthereumVerifierUntilAnchorCovers(mainchainClient, relayer, beaconApiUrl, minimumExecutionBlockNumber) {
1266
+ const startedAt = Date.now();
1267
+ const timeoutMs = 5 * 6e4;
1268
+ let lastRetryableError;
1269
+ let lastAnchorBlockNumber;
1270
+ while (Date.now() - startedAt < timeoutMs) {
1271
+ try {
1272
+ const anchor = await getLatestArgonFinalizedExecutionHeader(mainchainClient);
1273
+ lastAnchorBlockNumber = anchor.blockNumber;
1274
+ if (anchor.blockNumber >= minimumExecutionBlockNumber) {
1275
+ return;
1276
+ }
1277
+ } catch {
1278
+ }
1279
+ const txs = await getNextEthereumBeaconSyncTxs(mainchainClient, beaconApiUrl);
1280
+ if (txs.length === 0) {
1281
+ await delay2(500);
1282
+ continue;
1283
+ }
1284
+ let shouldRetry = false;
1285
+ for (const tx of txs) {
1286
+ try {
1287
+ const result = await new TxSubmitter2(mainchainClient, tx, relayer).submit();
1288
+ await result.waitForInFirstBlock;
1289
+ lastRetryableError = void 0;
1290
+ } catch (error) {
1291
+ if (isRetryableEthereumVerifierSyncError(error)) {
1292
+ lastRetryableError = error instanceof Error ? error : new Error(String(error));
1293
+ shouldRetry = true;
1294
+ break;
1295
+ }
1296
+ throw error;
1297
+ }
1298
+ }
1299
+ if (shouldRetry) {
1300
+ await delay2(500);
1301
+ }
1302
+ }
1303
+ throw lastRetryableError ?? new Error(
1304
+ `Ethereum verifier did not retain an anchor at or above execution block ${minimumExecutionBlockNumber} within ${Math.floor(timeoutMs / 1e3)}s; last seen anchor was ${lastAnchorBlockNumber ?? "unavailable"}`
1305
+ );
1306
+ }
1307
+ function toArgonKeccakSignature(signature) {
1308
+ const bytes = hexToU8a(signature);
1309
+ if (bytes.length !== 65) {
1310
+ throw new Error(`Expected 65-byte ECDSA signature, received ${bytes.length} bytes`);
1311
+ }
1312
+ if (bytes[64] >= 27) {
1313
+ bytes[64] -= 27;
1314
+ }
1315
+ return u8aToHex(bytes);
1316
+ }
1317
+ function toEvmRecoverableSignature(signature) {
1318
+ const bytes = hexToU8a(signature);
1319
+ if (bytes.length !== 65) {
1320
+ throw new Error(`Expected 65-byte ECDSA signature, received ${bytes.length} bytes`);
1321
+ }
1322
+ if (bytes[64] <= 1) {
1323
+ bytes[64] += 27;
1324
+ }
1325
+ return u8aToHex(bytes);
1326
+ }
1327
+ function isRetryableEthereumVerifierSyncError(error) {
1328
+ const message = error instanceof Error ? error.message : String(error);
1329
+ return isOutdatedTransactionError(error) || message.includes("ethereumVerifier.InvalidHeaderMerkleProof");
1330
+ }
1331
+ async function delay2(ms) {
1332
+ await new Promise((resolve3) => setTimeout(resolve3, ms));
1333
+ }
1334
+
1335
+ // src/EthereumGatewayQueue.ts
1336
+ import {
1337
+ EvmContracts as EvmContracts2
1338
+ } from "@argonprotocol/mainchain";
1339
+ import { getAddress as getAddress2, keccak256 } from "viem";
1340
+ var {
1341
+ encodeMintingGatewayMintingAuthorityActivationTarget,
1342
+ encodeMintingGatewayMintingAuthorityDeactivateTarget,
1343
+ hashMintingGatewayActivateMintingAuthority,
1344
+ hashMintingGatewayGatewayUpdateApproval,
1345
+ hashMintingGatewayMintingAuthorityDeactivation,
1346
+ mintingGatewayAbi,
1347
+ MINTING_GATEWAY_UPDATE_KINDS
1348
+ } = EvmContracts2;
1349
+ async function getReadyEthereumGatewayUpdates(client, gatewayClient, options = {}) {
1350
+ const destinationChain = options.destinationChain ?? "Ethereum";
1351
+ const maxQueueEntries = options.maxQueueEntries ?? 100;
1352
+ if (maxQueueEntries < 1) {
1353
+ throw new Error(`maxQueueEntries must be at least 1, received ${maxQueueEntries}`);
1354
+ }
1355
+ const chainConfigOption = await client.query.crosschainTransfer.chainConfigBySourceChain(destinationChain);
1356
+ if (chainConfigOption.isNone) {
1357
+ throw new Error(`Crosschain config not found for ${destinationChain}`);
1358
+ }
1359
+ const chainConfig = chainConfigOption.unwrap();
1360
+ if (!chainConfig.isEvm) {
1361
+ throw new Error(`Chain config for ${destinationChain} is not EVM-shaped`);
1362
+ }
1363
+ const gatewayAddress = getAddress2(toHexValue(chainConfig.asEvm.gateway));
1364
+ const chainId = chainConfig.asEvm.chainId.toBigInt();
1365
+ const hashContext = { chainId, gatewayAddress };
1366
+ const currentCouncilHashOption = await client.query.crosschainTransfer.activeGlobalIssuanceCouncilByDestinationChain(
1367
+ destinationChain
1368
+ );
1369
+ if (currentCouncilHashOption.isNone) {
1370
+ throw new Error(`Active GlobalIssuanceCouncil not found for ${destinationChain}`);
1371
+ }
1372
+ const currentCouncilHash = toHexValue(currentCouncilHashOption.unwrap());
1373
+ const councilCache = /* @__PURE__ */ new Map();
1374
+ const currentCouncil = councilToSnapshot(
1375
+ await loadCouncilByHash(client, currentCouncilHash, councilCache)
1376
+ );
1377
+ const [rawArgonApprovalsNonce, rawArgonApprovalsHash, rawPaused] = await Promise.all([
1378
+ gatewayClient.readContract({
1379
+ abi: mintingGatewayAbi,
1380
+ address: gatewayAddress,
1381
+ functionName: "argonApprovalsNonce"
1382
+ }),
1383
+ gatewayClient.readContract({
1384
+ abi: mintingGatewayAbi,
1385
+ address: gatewayAddress,
1386
+ functionName: "argonApprovalsHash"
1387
+ }),
1388
+ gatewayClient.readContract({
1389
+ abi: mintingGatewayAbi,
1390
+ address: gatewayAddress,
1391
+ functionName: "paused"
1392
+ })
1393
+ ]);
1394
+ const argonApprovalsNonce = rawArgonApprovalsNonce;
1395
+ const argonApprovalsHash = rawArgonApprovalsHash;
1396
+ const paused = rawPaused;
1397
+ const pendingClearOutQueueNonces = [];
1398
+ const candidateUpdates = [];
1399
+ let expectedPreviousApprovalHash = argonApprovalsHash;
1400
+ let readyQueueEntriesScanned = 0;
1401
+ if (!paused) {
1402
+ for (let queueNonce = argonApprovalsNonce + 1n; readyQueueEntriesScanned < maxQueueEntries; queueNonce += 1n) {
1403
+ const entryOption = await client.query.crosschainTransfer.councilApprovalQueueByDestinationChainAndNonce(
1404
+ destinationChain,
1405
+ queueNonce
1406
+ );
1407
+ if (entryOption.isNone) {
1408
+ break;
1409
+ }
1410
+ const entry = entryOption.unwrap();
1411
+ const approvingCouncilHash = toHexValue(entry.approvingCouncilHash);
1412
+ if (!await queueEntryIsReady(
1413
+ client,
1414
+ entry,
1415
+ approvingCouncilHash,
1416
+ councilCache,
1417
+ hashContext,
1418
+ queueNonce
1419
+ )) {
1420
+ break;
1421
+ }
1422
+ readyQueueEntriesScanned += 1;
1423
+ if (toHexValue(entry.previousApprovalHash) !== expectedPreviousApprovalHash) {
1424
+ throw new Error(
1425
+ `Queue nonce ${queueNonce} expected previous approval hash ${expectedPreviousApprovalHash}, received ${toHexValue(entry.previousApprovalHash)}`
1426
+ );
1427
+ }
1428
+ const update = await buildGatewayUpdate(client, destinationChain, hashContext, queueNonce, {
1429
+ entry,
1430
+ approvingCouncilHash
569
1431
  });
570
- clientSocket.on("error", console.error);
1432
+ candidateUpdates.push(update);
1433
+ expectedPreviousApprovalHash = toHexValue(entry.approvalHash);
1434
+ }
1435
+ }
1436
+ while (candidateUpdates.length > 0 && candidateUpdates[candidateUpdates.length - 1]?.kind === MINTING_GATEWAY_UPDATE_KINDS.mintingAuthorityDeactivate) {
1437
+ pendingClearOutQueueNonces.unshift(candidateUpdates.pop().queueNonce);
1438
+ }
1439
+ const updates = candidateUpdates;
1440
+ const firstQueueNonce = updates[0]?.queueNonce;
1441
+ const lastQueueNonce = updates[updates.length - 1]?.queueNonce;
1442
+ return {
1443
+ destinationChain,
1444
+ chainId,
1445
+ gatewayAddress,
1446
+ currentCouncilHash,
1447
+ currentCouncil,
1448
+ argonApprovalsNonce,
1449
+ argonApprovalsHash,
1450
+ paused,
1451
+ pendingClearOutQueueNonces,
1452
+ ...firstQueueNonce !== void 0 ? { firstQueueNonce, lastQueueNonce } : {},
1453
+ updates
1454
+ };
1455
+ }
1456
+ async function buildGatewayUpdate(client, destinationChain, hashContext, queueNonce, queueItem) {
1457
+ const { entry, approvingCouncilHash } = queueItem;
1458
+ if (entry.target.isMintingAuthorityActivation) {
1459
+ const signatures = getSortedSignatures(entry.signatures);
1460
+ const signingKey = getAddress2(toHexValue(entry.target.asMintingAuthorityActivation));
1461
+ const authorityOption = await client.query.crosschainTransfer.mintingAuthoritiesBySigner(signingKey);
1462
+ if (authorityOption.isNone) {
1463
+ throw new Error(
1464
+ `Minting authority activation ${signingKey} not found for queue nonce ${queueNonce}`
1465
+ );
1466
+ }
1467
+ const authority = authorityOption.unwrap();
1468
+ if (authority.destinationChain.type !== destinationChain) {
1469
+ throw new Error(
1470
+ `Minting authority ${signingKey} belongs to ${String(authority.destinationChain.type)}, expected ${String(destinationChain)}`
1471
+ );
1472
+ }
1473
+ const target = {
1474
+ microgonCollateral: authority.gatewayRemainingMicrogonCollateral.toBigInt(),
1475
+ micronotCollateral: authority.gatewayRemainingMicronotCollateral.toBigInt(),
1476
+ signingKey
1477
+ };
1478
+ const payload = encodeMintingGatewayMintingAuthorityActivationTarget(target);
1479
+ const targetPayloadHash = payloadHashFromActivationPayload(hashContext, target);
1480
+ const approvalHash = hashMintingGatewayGatewayUpdateApproval(hashContext, {
1481
+ queueNonce,
1482
+ approvingCouncilHash,
1483
+ kind: MINTING_GATEWAY_UPDATE_KINDS.mintingAuthorityActivate,
1484
+ targetId: `0x${signingKey.slice(2).padStart(64, "0").toLowerCase()}`,
1485
+ targetPayloadHash,
1486
+ previousUpdateHash: toHexValue(entry.previousApprovalHash)
571
1487
  });
572
- await new Promise((resolve3) => proxyServer.listen(0, resolve3));
573
- toTeardown.push({
574
- teardown: () => new Promise((resolve3) => {
575
- proxy?.close();
576
- proxyServer?.close((_) => null);
577
- proxy = null;
578
- proxyServer = null;
579
- resolve3();
1488
+ if (toHexValue(entry.targetPayloadHash) !== targetPayloadHash) {
1489
+ throw new Error(`Queue nonce ${queueNonce} target payload hash does not match authority`);
1490
+ }
1491
+ if (toHexValue(entry.approvalHash) !== approvalHash) {
1492
+ throw new Error(
1493
+ `Queue nonce ${queueNonce} approval hash does not match authority: actual=${toHexValue(entry.approvalHash)} expected=${approvalHash} previous=${toHexValue(entry.previousApprovalHash)} council=${approvingCouncilHash} targetPayload=${toHexValue(entry.targetPayloadHash)}`
1494
+ );
1495
+ }
1496
+ return {
1497
+ queueNonce,
1498
+ kind: MINTING_GATEWAY_UPDATE_KINDS.mintingAuthorityActivate,
1499
+ payload,
1500
+ signatures
1501
+ };
1502
+ }
1503
+ if (entry.target.isMintingAuthorityDeactivation) {
1504
+ const { payload, signatures } = validateDeactivationEntry(
1505
+ hashContext,
1506
+ queueNonce,
1507
+ entry,
1508
+ approvingCouncilHash
1509
+ );
1510
+ return {
1511
+ queueNonce,
1512
+ kind: MINTING_GATEWAY_UPDATE_KINDS.mintingAuthorityDeactivate,
1513
+ payload,
1514
+ signatures
1515
+ };
1516
+ }
1517
+ throw new Error(`Unsupported approval queue target ${entry.target.type}`);
1518
+ }
1519
+ async function loadCouncilByHash(client, councilHash, cache) {
1520
+ const cached = cache.get(councilHash);
1521
+ if (cached) {
1522
+ return cached;
1523
+ }
1524
+ const councilOption = await client.query.crosschainTransfer.globalIssuanceCouncilByHash(councilHash);
1525
+ if (councilOption.isNone) {
1526
+ throw new Error(`GlobalIssuanceCouncil ${councilHash} not found`);
1527
+ }
1528
+ const council = councilOption.unwrap();
1529
+ const loaded = {
1530
+ totalWeight: council.totalWeight.toBigInt(),
1531
+ members: [...council.members.entries()].map(([signer, member]) => ({
1532
+ signer: getAddress2(toHexValue(signer)),
1533
+ weight: member.weight.toBigInt()
1534
+ })).sort((left, right) => left.signer.localeCompare(right.signer))
1535
+ };
1536
+ cache.set(councilHash, loaded);
1537
+ return loaded;
1538
+ }
1539
+ function queueEntryHasQuorum(entry, council) {
1540
+ const signedWeight = [...entry.signatures.entries()].reduce((total, [signer]) => {
1541
+ const signerAddress = getAddress2(toHexValue(signer));
1542
+ const member = council.members.find((x) => x.signer === signerAddress);
1543
+ if (!member) {
1544
+ throw new Error(`Signature submitted by ${signerAddress}, which is not in the council`);
1545
+ }
1546
+ return total + member.weight;
1547
+ }, 0n);
1548
+ return signedWeight * 2n > council.totalWeight;
1549
+ }
1550
+ async function queueEntryIsReady(client, entry, approvingCouncilHash, councilCache, hashContext, queueNonce) {
1551
+ if (entry.target.isMintingAuthorityDeactivation) {
1552
+ validateDeactivationEntry(hashContext, queueNonce, entry, approvingCouncilHash);
1553
+ return true;
1554
+ }
1555
+ const approvingCouncil = await loadCouncilByHash(client, approvingCouncilHash, councilCache);
1556
+ return queueEntryHasQuorum(entry, approvingCouncil);
1557
+ }
1558
+ function councilToSnapshot(council) {
1559
+ return {
1560
+ signers: council.members.map((member) => member.signer),
1561
+ weights: council.members.map((member) => member.weight)
1562
+ };
1563
+ }
1564
+ function payloadHashFromActivationPayload(hashContext, target) {
1565
+ return hashMintingGatewayActivateMintingAuthority(hashContext, target);
1566
+ }
1567
+ function payloadHashFromDeactivationPayload(target) {
1568
+ return keccak256(encodeMintingGatewayMintingAuthorityDeactivateTarget(target));
1569
+ }
1570
+ function validateDeactivationEntry(hashContext, queueNonce, entry, approvingCouncilHash) {
1571
+ const signingKey = getAddress2(toHexValue(entry.target.asMintingAuthorityDeactivation));
1572
+ const target = { signingKey };
1573
+ const payload = encodeMintingGatewayMintingAuthorityDeactivateTarget(target);
1574
+ const targetPayloadHash = payloadHashFromDeactivationPayload(target);
1575
+ const approvalHash = hashMintingGatewayMintingAuthorityDeactivation(hashContext, {
1576
+ queueNonce,
1577
+ target,
1578
+ previousUpdateHash: toHexValue(entry.previousApprovalHash)
1579
+ });
1580
+ if (toHexValue(entry.targetPayloadHash) !== targetPayloadHash) {
1581
+ throw new Error(`Queue nonce ${queueNonce} target payload hash does not match deactivation`);
1582
+ }
1583
+ if (toHexValue(entry.approvalHash) !== approvalHash) {
1584
+ throw new Error(
1585
+ `Queue nonce ${queueNonce} approval hash does not match deactivation: actual=${toHexValue(entry.approvalHash)} expected=${approvalHash} previous=${toHexValue(entry.previousApprovalHash)} council=${approvingCouncilHash}`
1586
+ );
1587
+ }
1588
+ const deactivationSignatures = [...entry.signatures.entries()];
1589
+ if (deactivationSignatures.length !== 1) {
1590
+ throw new Error(
1591
+ `Queue nonce ${queueNonce} expected exactly one deactivation signature, received ${deactivationSignatures.length}`
1592
+ );
1593
+ }
1594
+ const [signer] = deactivationSignatures[0];
1595
+ if (getAddress2(toHexValue(signer)) !== signingKey) {
1596
+ throw new Error(
1597
+ `Queue nonce ${queueNonce} deactivation signature was submitted by ${getAddress2(toHexValue(signer))}, expected ${signingKey}`
1598
+ );
1599
+ }
1600
+ return {
1601
+ payload,
1602
+ signatures: getSortedSignatures(entry.signatures)
1603
+ };
1604
+ }
1605
+ function getSortedSignatures(signatures) {
1606
+ return [...signatures.entries()].sort(
1607
+ ([leftSigner], [rightSigner]) => toHexValue(leftSigner).localeCompare(toHexValue(rightSigner))
1608
+ ).map(([, signature]) => toEvmRecoverableSignature(toHexValue(signature)));
1609
+ }
1610
+ function toHexValue(value) {
1611
+ return value.toHex();
1612
+ }
1613
+
1614
+ // src/TestEthereumProofActors.ts
1615
+ import {
1616
+ buildGatewayActivityProofPayload,
1617
+ decodeAddress,
1618
+ dispatchErrorToString,
1619
+ EvmContracts as EvmContracts3,
1620
+ getEthereumBeaconSyncBootstrapTx,
1621
+ Keyring as Keyring3,
1622
+ toFixedNumber,
1623
+ TxSubmitter as TxSubmitter3,
1624
+ U8aFixed,
1625
+ Vault,
1626
+ Vec
1627
+ } from "@argonprotocol/mainchain";
1628
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
1629
+ import {
1630
+ createPublicClient as createPublicClient2,
1631
+ createWalletClient as createWalletClient3,
1632
+ defineChain as defineChain3,
1633
+ encodeFunctionData as encodeFunctionData2,
1634
+ http as http3,
1635
+ toHex
1636
+ } from "viem";
1637
+ var { argonTokenAbi, mintingGatewayAbi: mintingGatewayAbi2, MINTING_GATEWAY_RUNTIME_TO_ERC20_SCALE } = EvmContracts3;
1638
+ var MINIMAL_BOOTSTRAP_FINALIZED_SLOT = 64n;
1639
+ var EthereumProofE2eHarness = class _EthereumProofE2eHarness {
1640
+ constructor(ethereum, endpoints, mainchain, mainchainClient, deployerPrivateKey, proofRelayerUri) {
1641
+ this.ethereum = ethereum;
1642
+ this.endpoints = endpoints;
1643
+ this.mainchain = mainchain;
1644
+ this.mainchainClient = mainchainClient;
1645
+ this.deployer = privateKeyToAccount2(deployerPrivateKey);
1646
+ this.chain = defineChain3({
1647
+ id: Number.parseInt(endpoints.chainId, 16),
1648
+ name: "argon-test-ethereum",
1649
+ nativeCurrency: {
1650
+ name: "Ether",
1651
+ symbol: "ETH",
1652
+ decimals: 18
1653
+ },
1654
+ rpcUrls: {
1655
+ default: {
1656
+ http: [endpoints.executionRpcUrl]
1657
+ }
1658
+ }
1659
+ });
1660
+ this.publicClient = createPublicClient2({
1661
+ chain: this.chain,
1662
+ transport: http3(endpoints.executionRpcUrl)
1663
+ });
1664
+ this.walletClient = createWalletClient3({
1665
+ account: this.deployer,
1666
+ chain: this.chain,
1667
+ transport: http3(endpoints.executionRpcUrl)
1668
+ });
1669
+ this.proofRelayer = new Keyring3({ type: "sr25519" }).createFromUri(proofRelayerUri);
1670
+ }
1671
+ sudoSigner = new Keyring3({ type: "sr25519" }).createFromUri("//Alice");
1672
+ deployer;
1673
+ chain;
1674
+ publicClient;
1675
+ walletClient;
1676
+ mainchainClient;
1677
+ proofRelayer;
1678
+ static async launch(args) {
1679
+ const ethereum = new TestEthereum();
1680
+ const endpoints = await ethereum.launch({
1681
+ consensusClient: "lodestar",
1682
+ preset: "minimal",
1683
+ secondsPerSlot: 1,
1684
+ prefundedAccounts: {
1685
+ [args.testAccount.address]: {
1686
+ balance: args.testAccount.balance
1687
+ }
1688
+ }
1689
+ });
1690
+ const mainchain = new TestMainchain();
1691
+ await mainchain.launch();
1692
+ const mainchainClient = await mainchain.client();
1693
+ return new _EthereumProofE2eHarness(
1694
+ ethereum,
1695
+ endpoints,
1696
+ mainchain,
1697
+ mainchainClient,
1698
+ args.testAccount.privateKey,
1699
+ args.proofRelayerUri
1700
+ );
1701
+ }
1702
+ async submit(tx, signer) {
1703
+ const result = await new TxSubmitter3(this.mainchainClient, tx, signer).submit();
1704
+ await result.waitForInFirstBlock;
1705
+ return result;
1706
+ }
1707
+ async sudoSubmit(tx) {
1708
+ const result = await this.submit(
1709
+ this.mainchainClient.tx.sudo.sudo(tx),
1710
+ this.sudoSigner
1711
+ );
1712
+ const sudoEvent = result.events.find((event) => this.mainchainClient.events.sudo.Sudid.is(event));
1713
+ if (!sudoEvent || !this.mainchainClient.events.sudo.Sudid.is(sudoEvent)) {
1714
+ throw new Error("sudo did not emit sudo.Sudid");
1715
+ }
1716
+ const sudoResult = sudoEvent.data[0];
1717
+ if (sudoResult.isErr) {
1718
+ throw new Error(
1719
+ `sudo failed: ${dispatchErrorToString(this.mainchainClient, sudoResult.asErr)}`
1720
+ );
1721
+ }
1722
+ return result;
1723
+ }
1724
+ async syncVerifierThrough(minimumExecutionBlockNumber) {
1725
+ await syncEthereumVerifierUntilAnchorCovers(
1726
+ this.mainchainClient,
1727
+ this.sudoSigner,
1728
+ this.endpoints.beaconApiUrl,
1729
+ minimumExecutionBlockNumber
1730
+ );
1731
+ }
1732
+ async proveGatewayActivity(gatewayAddress, throughExecutionBlockNumber) {
1733
+ const payload = await buildGatewayActivityProofPayload(this.mainchainClient, {
1734
+ executionRpcUrl: this.endpoints.executionRpcUrl,
1735
+ gatewayAddress,
1736
+ throughExecutionBlockNumber
1737
+ });
1738
+ if (!payload) {
1739
+ throw new Error("Expected uncovered gateway activity to prove");
1740
+ }
1741
+ const result = await this.submit(
1742
+ this.mainchainClient.tx.crosschainTransfer.proveGatewayActivity(
1743
+ "Ethereum",
1744
+ payload.previousGatewayActivityNonce,
1745
+ payload.proof
1746
+ ),
1747
+ this.proofRelayer
1748
+ );
1749
+ return { payload, result };
1750
+ }
1751
+ async configureEthereumRuntime(gateway, mode) {
1752
+ const calls = [
1753
+ this.mainchainClient.tx.crosschainTransfer.setChainConfig("Ethereum", {
1754
+ Evm: {
1755
+ chainId: BigInt(this.chain.id).toString(),
1756
+ gateway: gateway.gatewayAddress,
1757
+ argonToken: gateway.argonTokenAddress,
1758
+ argonotToken: gateway.argonotTokenAddress
1759
+ }
580
1760
  })
1761
+ ];
1762
+ if (mode.kind === "outbound") {
1763
+ const activationPricing = {
1764
+ activationGasCost: BigInt(mode.activationPricing.activationGasCost),
1765
+ signatureGasCost: BigInt(mode.activationPricing.signatureGasCost),
1766
+ estimatedWeiPerGas: BigInt(mode.activationPricing.estimatedWeiPerGas),
1767
+ estimatedMicrogonsPerEth: BigInt(mode.activationPricing.estimatedMicrogonsPerEth)
1768
+ };
1769
+ calls.push(
1770
+ this.mainchainClient.tx.crosschainTransfer.setMintingAuthorityActivationRepaymentPricing(
1771
+ "Ethereum",
1772
+ {
1773
+ activationGasCost: activationPricing.activationGasCost.toString(),
1774
+ signatureGasCost: activationPricing.signatureGasCost.toString(),
1775
+ estimatedWeiPerGas: activationPricing.estimatedWeiPerGas.toString(),
1776
+ estimatedMicrogonsPerEth: activationPricing.estimatedMicrogonsPerEth.toString()
1777
+ }
1778
+ )
1779
+ );
1780
+ if (mode.minimumMintingAuthorityValue !== void 0) {
1781
+ calls.push(
1782
+ this.mainchainClient.tx.crosschainTransfer.setMinimumMintingAuthorityValue(
1783
+ "Ethereum",
1784
+ mode.minimumMintingAuthorityValue.toString()
1785
+ )
1786
+ );
1787
+ }
1788
+ }
1789
+ calls.push(
1790
+ await getEthereumBeaconSyncBootstrapTx(this.mainchainClient, this.endpoints.beaconApiUrl)
1791
+ );
1792
+ const result = await this.sudoSubmit(this.mainchainClient.tx.utility.batchAll(calls));
1793
+ return { result };
1794
+ }
1795
+ async fundBurnAccount(amount) {
1796
+ const burnAccount = this.mainchainClient.consts.crosschainTransfer.ethereumBurnAccount.toString();
1797
+ return this.forceSetBalance(burnAccount, amount);
1798
+ }
1799
+ async fundProofRelayer(amount = this.mainchainClient.consts.balances.existentialDeposit.toBigInt() + 1000000n) {
1800
+ return this.submit(
1801
+ this.mainchainClient.tx.balances.transferAllowDeath(this.proofRelayer.address, amount),
1802
+ this.sudoSigner
1803
+ );
1804
+ }
1805
+ async forceSetBalance(address, amount) {
1806
+ return this.sudoSubmit(this.mainchainClient.tx.balances.forceSetBalance(address, amount));
1807
+ }
1808
+ async forceSetOwnership(address, amount) {
1809
+ return this.sudoSubmit(this.mainchainClient.tx.ownership.forceSetBalance(address, amount));
1810
+ }
1811
+ async waitForExecutionFinalizedAfter(minimumExecutionBlockNumber) {
1812
+ const laterReceipt = await mineLaterExecutionAnchorReceipt(
1813
+ this.walletClient,
1814
+ this.chain,
1815
+ this.ethereum,
1816
+ this.deployer,
1817
+ minimumExecutionBlockNumber
1818
+ );
1819
+ await waitForFinalizedBeaconExecutionAtOrAbove(
1820
+ this.ethereum,
1821
+ BigInt(laterReceipt.blockNumber),
1822
+ {
1823
+ // Matches the apps/pr/gateway-proof bootstrap guard for minimal devnets.
1824
+ minimumFinalizedSlot: MINIMAL_BOOTSTRAP_FINALIZED_SLOT
1825
+ }
1826
+ );
1827
+ return laterReceipt;
1828
+ }
1829
+ };
1830
+ var TestMintingGateway = class _TestMintingGateway {
1831
+ constructor(harness, deployment) {
1832
+ this.harness = harness;
1833
+ this.deployment = deployment;
1834
+ }
1835
+ static async deploy(harness, options) {
1836
+ const deployment = await harness.ethereum.deployMintingGatewayFixture(options);
1837
+ return new _TestMintingGateway(harness, deployment);
1838
+ }
1839
+ get gatewayAddress() {
1840
+ return this.deployment.gatewayAddress;
1841
+ }
1842
+ get argonTokenAddress() {
1843
+ return this.deployment.argonTokenAddress;
1844
+ }
1845
+ get argonotTokenAddress() {
1846
+ return this.deployment.argonotTokenAddress;
1847
+ }
1848
+ async startTransferToArgon(args) {
1849
+ const permitDeadline = (await this.harness.publicClient.getBlock()).timestamp + 3600n;
1850
+ const permitNonce = await this.harness.publicClient.readContract({
1851
+ address: this.argonTokenAddress,
1852
+ abi: argonTokenAbi,
1853
+ functionName: "nonces",
1854
+ args: [args.account.address]
1855
+ });
1856
+ const permitSignature = await signGatewayPermit({
1857
+ account: args.account,
1858
+ chainId: this.harness.chain.id,
1859
+ tokenAddress: this.argonTokenAddress,
1860
+ gatewayAddress: this.gatewayAddress,
1861
+ owner: args.account.address,
1862
+ value: args.amountRuntimeUnits * MINTING_GATEWAY_RUNTIME_TO_ERC20_SCALE,
1863
+ nonce: permitNonce,
1864
+ deadline: permitDeadline
581
1865
  });
1866
+ const transactionHash = await createWalletClient3({
1867
+ account: args.account,
1868
+ chain: this.harness.chain,
1869
+ transport: http3(this.harness.endpoints.executionRpcUrl)
1870
+ }).sendTransaction({
1871
+ account: args.account,
1872
+ chain: this.harness.chain,
1873
+ to: this.gatewayAddress,
1874
+ data: encodeFunctionData2({
1875
+ abi: mintingGatewayAbi2,
1876
+ functionName: "startTransferToArgon",
1877
+ args: [
1878
+ this.argonTokenAddress,
1879
+ args.amountRuntimeUnits,
1880
+ args.recipientArgonAddress,
1881
+ permitDeadline,
1882
+ permitSignature.v,
1883
+ permitSignature.r,
1884
+ permitSignature.s
1885
+ ]
1886
+ })
1887
+ });
1888
+ return waitForExecutionReceipt2(this.harness.ethereum, transactionHash);
582
1889
  }
583
- const port2 = proxyServer.address().port;
584
- return `ws://host.docker.internal:${port2}`;
1890
+ async forceUpdateActiveCouncil(replacementCouncil, nextMicrogonsPerArgonot) {
1891
+ return this.harness.publicClient.waitForTransactionReceipt({
1892
+ hash: await this.harness.walletClient.writeContract({
1893
+ account: this.harness.deployer,
1894
+ chain: this.harness.chain,
1895
+ address: this.gatewayAddress,
1896
+ abi: mintingGatewayAbi2,
1897
+ functionName: "forceUpdateActiveCouncil",
1898
+ args: [replacementCouncil, nextMicrogonsPerArgonot]
1899
+ })
1900
+ });
1901
+ }
1902
+ async relayReadyApprovals(batch, operatorAddress) {
1903
+ return this.harness.publicClient.waitForTransactionReceipt({
1904
+ hash: await this.harness.walletClient.writeContract({
1905
+ account: this.harness.deployer,
1906
+ chain: this.harness.chain,
1907
+ address: this.gatewayAddress,
1908
+ abi: mintingGatewayAbi2,
1909
+ functionName: "applyGatewayUpdates",
1910
+ args: [
1911
+ batch.currentCouncil,
1912
+ batch.updates,
1913
+ toHex(decodeAddress(operatorAddress), { size: 32 })
1914
+ ]
1915
+ })
1916
+ });
1917
+ }
1918
+ async argonApprovalsNonce() {
1919
+ return await this.harness.publicClient.readContract({
1920
+ address: this.gatewayAddress,
1921
+ abi: mintingGatewayAbi2,
1922
+ functionName: "argonApprovalsNonce"
1923
+ });
1924
+ }
1925
+ async globalIssuanceCouncil() {
1926
+ return await this.harness.publicClient.readContract({
1927
+ address: this.gatewayAddress,
1928
+ abi: mintingGatewayAbi2,
1929
+ functionName: "globalIssuanceCouncil"
1930
+ });
1931
+ }
1932
+ async authorityCollateral(signingKey) {
1933
+ return await this.harness.publicClient.readContract({
1934
+ address: this.gatewayAddress,
1935
+ abi: mintingGatewayAbi2,
1936
+ functionName: "mintingAuthorityCollateralRemaining",
1937
+ args: [signingKey]
1938
+ });
1939
+ }
1940
+ async finalizeTransferOut(args) {
1941
+ return this.harness.publicClient.waitForTransactionReceipt({
1942
+ hash: await this.harness.walletClient.writeContract({
1943
+ account: this.harness.deployer,
1944
+ chain: this.harness.chain,
1945
+ address: this.gatewayAddress,
1946
+ abi: mintingGatewayAbi2,
1947
+ functionName: "finalizeTransferOutOfArgon",
1948
+ args: [
1949
+ args.transferRequest,
1950
+ {
1951
+ authorizations: [
1952
+ {
1953
+ microgonCollateral: 0n,
1954
+ micronotCollateral: args.micronotCollateral,
1955
+ signature: args.collateralizationSignature
1956
+ }
1957
+ ]
1958
+ }
1959
+ ]
1960
+ })
1961
+ });
1962
+ }
1963
+ async isFinalizedTransferOut(transferRequest) {
1964
+ return await this.harness.publicClient.readContract({
1965
+ address: this.gatewayAddress,
1966
+ abi: mintingGatewayAbi2,
1967
+ functionName: "finalizedTransferOutOfArgonIds",
1968
+ args: [EvmContracts3.hashMintingGatewayTransferOutOfArgonRequest(transferRequest)]
1969
+ });
1970
+ }
1971
+ async argonBalance(address) {
1972
+ return await this.harness.publicClient.readContract({
1973
+ address: this.argonTokenAddress,
1974
+ abi: argonTokenAbi,
1975
+ functionName: "balanceOf",
1976
+ args: [address]
1977
+ });
1978
+ }
1979
+ async fundExecutionAccount(address, value) {
1980
+ return this.harness.publicClient.waitForTransactionReceipt({
1981
+ hash: await this.harness.walletClient.sendTransaction({
1982
+ account: this.harness.deployer,
1983
+ chain: this.harness.chain,
1984
+ to: address,
1985
+ value
1986
+ })
1987
+ });
1988
+ }
1989
+ };
1990
+ var TestMintingAuthorityActor = class {
1991
+ constructor(harness, args) {
1992
+ this.harness = harness;
1993
+ this.operator = new Keyring3({ type: "sr25519" }).createFromUri(args.operatorUri);
1994
+ this.councilSigner = privateKeyToAccount2(args.councilPrivateKey);
1995
+ this.authoritySigner = privateKeyToAccount2(args.authorityPrivateKey);
1996
+ }
1997
+ operator;
1998
+ councilSigner;
1999
+ authoritySigner;
2000
+ gateway;
2001
+ attachGateway(gateway) {
2002
+ this.gateway = gateway;
2003
+ }
2004
+ async prepareOperator(args) {
2005
+ await this.harness.forceSetBalance(this.operator.address, args.freeBalance);
2006
+ await this.harness.forceSetOwnership(this.operator.address, args.ownershipBalance);
2007
+ const vault = await Vault.create(this.harness.mainchainClient, this.operator, {
2008
+ securitization: 1000000000n,
2009
+ securitizationRatio: 1,
2010
+ annualPercentRate: 0.05,
2011
+ baseFee: 0n,
2012
+ bitcoinXpub: args.bitcoinXpub,
2013
+ treasuryProfitSharing: 0
2014
+ });
2015
+ await vault.getVault();
2016
+ await this.harness.sudoSubmit(
2017
+ this.harness.mainchainClient.tx.priceIndex.setOperator(this.operator.address)
2018
+ );
2019
+ const currentTick = await this.harness.mainchainClient.query.ticks.currentTick();
2020
+ await this.harness.submit(
2021
+ this.harness.mainchainClient.tx.priceIndex.submit({
2022
+ btcUsdPrice: toFixedNumber(6e4, 18),
2023
+ argonotUsdPrice: toFixedNumber(1, 18),
2024
+ argonUsdPrice: toFixedNumber(1, 18),
2025
+ argonUsdTargetPrice: toFixedNumber(1, 18),
2026
+ argonTimeWeightedAverageLiquidity: toFixedNumber(1e6, 18),
2027
+ tick: currentTick.toBigInt()
2028
+ }),
2029
+ this.operator
2030
+ );
2031
+ await this.harness.submit(
2032
+ this.harness.mainchainClient.tx.vaults.setCommittedArgonots(args.committedArgonots),
2033
+ this.operator
2034
+ );
2035
+ }
2036
+ async registerCouncilSigner() {
2037
+ return this.harness.submit(
2038
+ this.harness.mainchainClient.tx.crosschainTransfer.registerCouncilSigner(
2039
+ "Ethereum",
2040
+ this.councilSigner.address,
2041
+ toArgonKeccakSignature(
2042
+ await this.councilSigner.signMessage({
2043
+ message: { raw: toHex(this.registrationMessage("argon/council-signer/v2")) }
2044
+ })
2045
+ )
2046
+ ),
2047
+ this.operator
2048
+ );
2049
+ }
2050
+ async forceSingleMemberCouncil() {
2051
+ await this.harness.sudoSubmit(
2052
+ this.harness.mainchainClient.tx.crosschainTransfer.forceSetGlobalIssuanceCouncil(
2053
+ "Ethereum",
2054
+ 0,
2055
+ [this.operator.address]
2056
+ )
2057
+ );
2058
+ const activeCouncilHashOption = await this.harness.mainchainClient.query.crosschainTransfer.activeGlobalIssuanceCouncilByDestinationChain(
2059
+ "Ethereum"
2060
+ );
2061
+ if (activeCouncilHashOption.isNone) {
2062
+ throw new Error("Expected active Ethereum council hash");
2063
+ }
2064
+ const activeCouncilOption = await this.harness.mainchainClient.query.crosschainTransfer.globalIssuanceCouncilByHash(
2065
+ activeCouncilHashOption.unwrap()
2066
+ );
2067
+ if (activeCouncilOption.isNone) {
2068
+ throw new Error("Expected active Ethereum council");
2069
+ }
2070
+ const activeCouncil = activeCouncilOption.unwrap();
2071
+ const currentCouncil = [...activeCouncil.members.entries()].map(([signer, member]) => ({
2072
+ signer: signer.toHex(),
2073
+ weight: member.weight.toBigInt()
2074
+ })).sort((left, right) => left.signer.localeCompare(right.signer));
2075
+ return {
2076
+ activeCouncilHash: activeCouncilHashOption.unwrap().toHex(),
2077
+ activeCouncil,
2078
+ currentCouncil: {
2079
+ signers: currentCouncil.map((member) => member.signer),
2080
+ weights: currentCouncil.map((member) => member.weight)
2081
+ }
2082
+ };
2083
+ }
2084
+ async registerMintingAuthority(micronotCollateral) {
2085
+ return this.harness.submit(
2086
+ this.harness.mainchainClient.tx.crosschainTransfer.registerMintingAuthority(
2087
+ "Ethereum",
2088
+ this.authoritySigner.address,
2089
+ toArgonKeccakSignature(
2090
+ await this.authoritySigner.signMessage({
2091
+ message: {
2092
+ raw: toHex(this.registrationMessage("argon/minting-authority-signer/v2"))
2093
+ }
2094
+ })
2095
+ ),
2096
+ 0n,
2097
+ micronotCollateral
2098
+ ),
2099
+ this.operator
2100
+ );
2101
+ }
2102
+ async approveActivationQueueEntry(queueNonce = 1n) {
2103
+ const approvalQueueEntry = await this.harness.mainchainClient.query.crosschainTransfer.councilApprovalQueueByDestinationChainAndNonce(
2104
+ "Ethereum",
2105
+ queueNonce
2106
+ );
2107
+ if (approvalQueueEntry.isNone) {
2108
+ throw new Error(`Expected queue nonce ${queueNonce} to exist`);
2109
+ }
2110
+ const councilApprovalSignature = await this.councilSigner.signMessage({
2111
+ message: {
2112
+ raw: approvalQueueEntry.unwrap().approvalHash.toHex()
2113
+ }
2114
+ });
2115
+ await this.harness.submit(
2116
+ this.harness.mainchainClient.tx.crosschainTransfer.approveQueueEntries(
2117
+ "Ethereum",
2118
+ new Vec(this.harness.mainchainClient.registry, U8aFixed.with(520), [
2119
+ new U8aFixed(
2120
+ this.harness.mainchainClient.registry,
2121
+ toArgonKeccakSignature(councilApprovalSignature),
2122
+ 520
2123
+ )
2124
+ ])
2125
+ ),
2126
+ this.operator
2127
+ );
2128
+ const batch = await getReadyEthereumGatewayUpdates(
2129
+ this.harness.mainchainClient,
2130
+ this.harness.publicClient
2131
+ );
2132
+ return {
2133
+ approvalQueueEntry,
2134
+ councilApprovalSignature,
2135
+ batch
2136
+ };
2137
+ }
2138
+ async collateralizeFirstPendingTransferOut() {
2139
+ const gateway = this.requireGateway();
2140
+ const pendingRequests = await this.harness.mainchainClient.query.crosschainTransfer.pendingCollateralizationRequestsByChain(
2141
+ "Ethereum"
2142
+ );
2143
+ if (pendingRequests.length === 0) {
2144
+ throw new Error("Expected a pending collateralization request");
2145
+ }
2146
+ const pendingRequest = pendingRequests[0];
2147
+ const transferId = pendingRequest.transferId.toHex();
2148
+ const transferOption = await this.harness.mainchainClient.query.crosschainTransfer.transferOutById(transferId);
2149
+ if (transferOption.isNone) {
2150
+ throw new Error(`Expected transfer out ${transferId} to exist`);
2151
+ }
2152
+ const transfer = transferOption.unwrap();
2153
+ const transferRequest = {
2154
+ argonAccountId: transfer.argonAccountId.toHex(),
2155
+ argonTransferNonce: transfer.argonTransferNonce.toBigInt(),
2156
+ chainId: BigInt(this.harness.chain.id),
2157
+ microgonsPerArgonot: transfer.microgonsPerArgonot.toBigInt(),
2158
+ recipient: transfer.destinationAccount.toHex(),
2159
+ validUntilBlock: transfer.validUntilEthereumBlock.toBigInt(),
2160
+ token: gateway.argonTokenAddress,
2161
+ amount: transfer.amount.toBigInt(),
2162
+ mintingAuthorityTip: transfer.mintingAuthorityTip.toBigInt()
2163
+ };
2164
+ const micronotCollateral = transfer.amount.toBigInt();
2165
+ const collateralizationHash = EvmContracts3.hashMintingGatewayMintingAuthorization(
2166
+ { chainId: BigInt(this.harness.chain.id), gatewayAddress: gateway.gatewayAddress },
2167
+ {
2168
+ request: transferRequest,
2169
+ microgonCollateral: 0n,
2170
+ micronotCollateral
2171
+ }
2172
+ );
2173
+ const collateralizationSignature = await this.authoritySigner.signMessage({
2174
+ message: {
2175
+ raw: collateralizationHash
2176
+ }
2177
+ });
2178
+ const result = await this.harness.submit(
2179
+ this.harness.mainchainClient.tx.crosschainTransfer.collateralizeTransfer(
2180
+ transferId,
2181
+ toArgonKeccakSignature(collateralizationSignature),
2182
+ 0n,
2183
+ micronotCollateral
2184
+ ),
2185
+ this.operator
2186
+ );
2187
+ return {
2188
+ pendingRequest,
2189
+ transferId,
2190
+ transferRequest,
2191
+ micronotCollateral,
2192
+ collateralizationSignature,
2193
+ result
2194
+ };
2195
+ }
2196
+ registrationMessage(prefix) {
2197
+ const prefixBytes = this.harness.mainchainClient.registry.createType("Bytes", prefix).toU8a();
2198
+ const destinationChainBytes = this.harness.mainchainClient.registry.createType("PalletCrosschainTransferSourceChain", "Ethereum").toU8a();
2199
+ const operatorAccountIdBytes = this.harness.mainchainClient.registry.createType("AccountId32", this.operator.address).toU8a();
2200
+ return concatBytes(prefixBytes, destinationChainBytes, operatorAccountIdBytes);
2201
+ }
2202
+ requireGateway() {
2203
+ if (!this.gateway) {
2204
+ throw new Error("Minting authority actor requires an attached TestMintingGateway");
2205
+ }
2206
+ return this.gateway;
2207
+ }
2208
+ };
2209
+ function concatBytes(...parts) {
2210
+ const totalLength = parts.reduce((sum, part) => sum + part.length, 0);
2211
+ const bytes = new Uint8Array(totalLength);
2212
+ let offset = 0;
2213
+ for (const part of parts) {
2214
+ bytes.set(part, offset);
2215
+ offset += part.length;
2216
+ }
2217
+ return bytes;
585
2218
  }
2219
+
2220
+ // src/index.ts
586
2221
  function stringifyExt(obj) {
587
2222
  return JSON.stringify(
588
2223
  obj,
@@ -598,54 +2233,12 @@ function stringifyExt(obj) {
598
2233
  2
599
2234
  );
600
2235
  }
601
- function projectRoot() {
602
- if (process4.env.ARGON_PROJECT_ROOT) {
603
- return Path6.join(process4.env.ARGON_PROJECT_ROOT);
604
- }
605
- return Path6.join(__dirname, `../../..`);
606
- }
607
- async function runTestScript(relativePath) {
608
- const scriptPath = Path6.resolve(projectRoot(), relativePath);
609
- return child_process4.execSync(scriptPath, { encoding: "utf8" }).trim();
610
- }
611
- async function getDockerPortMapping(containerName, port2) {
612
- return child_process4.execSync(`docker port ${containerName} ${port2}`, { encoding: "utf8" }).trim().split(":").pop();
613
- }
614
- async function teardown() {
615
- for (const t of toTeardown) {
616
- try {
617
- await t.teardown().catch(console.error);
618
- } catch {
619
- }
620
- }
621
- toTeardown.length = 0;
622
- }
623
- function cleanHostForDocker(host, replacer = "host.docker.internal") {
624
- if (process4.env.ARGON_USE_DOCKER_BINS) {
625
- return host.replace("localhost", replacer).replace("127.0.0.1", replacer).replace("0.0.0.0", replacer);
626
- }
627
- return host;
628
- }
629
- function addTeardown(teardownable) {
630
- toTeardown.push(teardownable);
631
- }
632
- function runOnTeardown(teardown2) {
633
- addTeardown({ teardown: teardown2 });
634
- }
635
- function closeOnTeardown(closeable) {
636
- addTeardown({ teardown: () => closeable.close() });
637
- return closeable;
638
- }
639
- function disconnectOnTeardown(closeable) {
640
- addTeardown({ teardown: () => closeable.disconnect() });
641
- return closeable;
642
- }
643
2236
  function sudo() {
644
- return new Keyring3({ type: "sr25519" }).createFromUri("//Alice");
2237
+ return new Keyring4({ type: "sr25519" }).createFromUri("//Alice");
645
2238
  }
646
2239
  async function activateNotary(sudo2, client, notary) {
647
2240
  await notary.register(client);
648
- const txResult = await new TxSubmitter2(
2241
+ const txResult = await new TxSubmitter4(
649
2242
  client,
650
2243
  client.tx.sudo.sudo(client.tx.notaries.activate(notary.operator.publicKey)),
651
2244
  sudo2
@@ -653,9 +2246,13 @@ async function activateNotary(sudo2, client, notary) {
653
2246
  await txResult.waitForInFirstBlock;
654
2247
  }
655
2248
  export {
2249
+ EthereumProofE2eHarness,
656
2250
  SKIP_E2E,
657
2251
  TestBitcoinCli,
2252
+ TestEthereum,
658
2253
  TestMainchain,
2254
+ TestMintingAuthorityActor,
2255
+ TestMintingGateway,
659
2256
  TestNotary,
660
2257
  TestOracle,
661
2258
  activateNotary,
@@ -665,12 +2262,20 @@ export {
665
2262
  disconnectOnTeardown,
666
2263
  getDockerPortMapping,
667
2264
  getProxy,
2265
+ getReadyEthereumGatewayUpdates,
2266
+ mineLaterExecutionAnchorReceipt,
668
2267
  projectRoot,
669
2268
  runOnTeardown,
670
2269
  runTestScript,
2270
+ signGatewayPermit,
671
2271
  startNetwork,
672
2272
  stringifyExt,
673
2273
  sudo,
674
- teardown
2274
+ syncEthereumVerifierUntilAnchorCovers,
2275
+ teardown,
2276
+ toArgonKeccakSignature,
2277
+ toEvmRecoverableSignature,
2278
+ waitForExecutionReceipt2 as waitForExecutionReceipt,
2279
+ waitForFinalizedBeaconExecutionAtOrAbove
675
2280
  };
676
2281
  //# sourceMappingURL=index.js.map