@argonprotocol/testing 1.4.3-dev.7c826893 → 1.4.3-dev.83602b96
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.cjs +1462 -16
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +265 -2
- package/lib/index.d.ts +265 -2
- package/lib/index.js +1485 -15
- package/lib/index.js.map +1 -1
- package/package.json +6 -6
package/lib/index.js
CHANGED
|
@@ -6,13 +6,13 @@ var getDirname = () => path.dirname(getFilename());
|
|
|
6
6
|
var __dirname = /* @__PURE__ */ getDirname();
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import { Keyring as
|
|
9
|
+
import { Keyring as Keyring4, TxSubmitter as TxSubmitter4 } from "@argonprotocol/mainchain";
|
|
10
10
|
import * as process4 from "process";
|
|
11
11
|
import HttpProxy from "http-proxy";
|
|
12
12
|
import * as child_process4 from "child_process";
|
|
13
|
-
import * as
|
|
13
|
+
import * as http3 from "http";
|
|
14
14
|
import * as url from "url";
|
|
15
|
-
import * as
|
|
15
|
+
import * as Path7 from "path";
|
|
16
16
|
|
|
17
17
|
// src/TestNotary.ts
|
|
18
18
|
import { customAlphabet } from "nanoid";
|
|
@@ -326,9 +326,20 @@ var TestMainchain = class {
|
|
|
326
326
|
return this.address;
|
|
327
327
|
}
|
|
328
328
|
async client() {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
329
|
+
let lastError;
|
|
330
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
331
|
+
try {
|
|
332
|
+
const client = await getClient(this.address);
|
|
333
|
+
disconnectOnTeardown(client);
|
|
334
|
+
return client;
|
|
335
|
+
} catch (error) {
|
|
336
|
+
lastError = error;
|
|
337
|
+
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
throw new Error(`Unable to connect to mainchain client at ${this.address}`, {
|
|
341
|
+
cause: lastError instanceof Error ? lastError : void 0
|
|
342
|
+
});
|
|
332
343
|
}
|
|
333
344
|
async bootAddress() {
|
|
334
345
|
const client = await this.client();
|
|
@@ -541,11 +552,475 @@ var TestOracle = class _TestOracle {
|
|
|
541
552
|
}
|
|
542
553
|
};
|
|
543
554
|
|
|
555
|
+
// src/TestEthereum.ts
|
|
556
|
+
import * as fs4 from "fs/promises";
|
|
557
|
+
import * as os from "os";
|
|
558
|
+
import * as Path5 from "path";
|
|
559
|
+
import { spawn as spawn4, spawnSync } from "child_process";
|
|
560
|
+
import { detectPort as detectPort2 } from "detect-port";
|
|
561
|
+
import { EvmContracts } from "@argonprotocol/mainchain";
|
|
562
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
563
|
+
import {
|
|
564
|
+
createPublicClient,
|
|
565
|
+
createWalletClient,
|
|
566
|
+
defineChain,
|
|
567
|
+
encodeFunctionData,
|
|
568
|
+
getAddress,
|
|
569
|
+
http,
|
|
570
|
+
zeroAddress
|
|
571
|
+
} from "viem";
|
|
572
|
+
var {
|
|
573
|
+
argonTokenArtifact,
|
|
574
|
+
argonotTokenArtifact,
|
|
575
|
+
hashMintingGatewayGlobalIssuanceCouncil,
|
|
576
|
+
mintingGatewayArtifact,
|
|
577
|
+
proxyAdminArtifact,
|
|
578
|
+
transparentUpgradeableProxyArtifact
|
|
579
|
+
} = EvmContracts;
|
|
580
|
+
var DEFAULT_KURTOSIS_BIN = "kurtosis";
|
|
581
|
+
var DEFAULT_ETHEREUM_PACKAGE = "github.com/ethpandaops/ethereum-package";
|
|
582
|
+
var DEFAULT_EL_PORT_START = 32e3;
|
|
583
|
+
var DEFAULT_CL_PORT_START = 33e3;
|
|
584
|
+
var PORT_RANGE_SIZE = 32;
|
|
585
|
+
var ENCLAVE_NAME_PREFIX = "argon-eth-";
|
|
586
|
+
var PROBE_INTERVAL_MS = 1e3;
|
|
587
|
+
var PROBE_TIMEOUT_MS = 6e4;
|
|
588
|
+
var LIGHT_CLIENT_READY_TIMEOUT_MS = 5 * 6e4;
|
|
589
|
+
var KURTOSIS_RUN_TIMEOUT_MS = 20 * 6e4;
|
|
590
|
+
var DEFAULT_SEED_ARGON_AMOUNT_BASE_UNITS = 1000000000n;
|
|
591
|
+
var DEFAULT_INITIAL_MICROGONS_PER_ARGONOT = 1000000n;
|
|
592
|
+
var ERC1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
593
|
+
var TestEthereum = class {
|
|
594
|
+
enclaveName;
|
|
595
|
+
kurtosisBin;
|
|
596
|
+
packageRef;
|
|
597
|
+
executionRpcUrl;
|
|
598
|
+
beaconApiUrl;
|
|
599
|
+
chainId;
|
|
600
|
+
#argsDir;
|
|
601
|
+
constructor(enclaveName = `${ENCLAVE_NAME_PREFIX}${Math.random().toString(36).slice(2, 8)}`, kurtosisBin = DEFAULT_KURTOSIS_BIN, packageRef = DEFAULT_ETHEREUM_PACKAGE) {
|
|
602
|
+
this.enclaveName = enclaveName;
|
|
603
|
+
this.kurtosisBin = kurtosisBin;
|
|
604
|
+
this.packageRef = packageRef;
|
|
605
|
+
addTeardown(this);
|
|
606
|
+
}
|
|
607
|
+
static isInstalled(kurtosisBin = DEFAULT_KURTOSIS_BIN) {
|
|
608
|
+
return spawnSync(kurtosisBin, ["version"], { stdio: "ignore" }).status === 0;
|
|
609
|
+
}
|
|
610
|
+
async launch(options) {
|
|
611
|
+
const {
|
|
612
|
+
consensusClient = "lighthouse",
|
|
613
|
+
preset = "mainnet",
|
|
614
|
+
secondsPerSlot,
|
|
615
|
+
waitForFinalization = true,
|
|
616
|
+
prefundedAccounts
|
|
617
|
+
} = options ?? {};
|
|
618
|
+
const elPublicPortStart = await findFreePortRange(DEFAULT_EL_PORT_START, PORT_RANGE_SIZE);
|
|
619
|
+
const clPublicPortStart = await findFreePortRange(DEFAULT_CL_PORT_START, PORT_RANGE_SIZE);
|
|
620
|
+
this.#argsDir = await fs4.mkdtemp(Path5.join(os.tmpdir(), "argon-ethereum-devnet-"));
|
|
621
|
+
const argsFile = Path5.join(this.#argsDir, "network-params.yaml");
|
|
622
|
+
await fs4.writeFile(
|
|
623
|
+
argsFile,
|
|
624
|
+
renderEthereumArgs(
|
|
625
|
+
elPublicPortStart,
|
|
626
|
+
clPublicPortStart,
|
|
627
|
+
consensusClient,
|
|
628
|
+
preset,
|
|
629
|
+
secondsPerSlot,
|
|
630
|
+
waitForFinalization,
|
|
631
|
+
prefundedAccounts
|
|
632
|
+
)
|
|
633
|
+
);
|
|
634
|
+
await runCommand(
|
|
635
|
+
this.kurtosisBin,
|
|
636
|
+
["run", "--enclave", this.enclaveName, this.packageRef, "--args-file", argsFile],
|
|
637
|
+
KURTOSIS_RUN_TIMEOUT_MS
|
|
638
|
+
);
|
|
639
|
+
const executionRpc = await waitForProbe(
|
|
640
|
+
() => findExecutionRpcUrl(elPublicPortStart, PORT_RANGE_SIZE),
|
|
641
|
+
PROBE_TIMEOUT_MS
|
|
642
|
+
);
|
|
643
|
+
const beaconApi = await waitForProbe(
|
|
644
|
+
() => findBeaconApiUrl(clPublicPortStart, PORT_RANGE_SIZE),
|
|
645
|
+
PROBE_TIMEOUT_MS
|
|
646
|
+
);
|
|
647
|
+
this.executionRpcUrl = executionRpc.url;
|
|
648
|
+
this.beaconApiUrl = beaconApi.url;
|
|
649
|
+
this.chainId = executionRpc.chainId;
|
|
650
|
+
await waitForProbe(
|
|
651
|
+
() => this.getBeacon("/eth/v1/beacon/genesis"),
|
|
652
|
+
LIGHT_CLIENT_READY_TIMEOUT_MS
|
|
653
|
+
);
|
|
654
|
+
return {
|
|
655
|
+
executionRpcUrl: this.executionRpcUrl,
|
|
656
|
+
beaconApiUrl: this.beaconApiUrl,
|
|
657
|
+
chainId: this.chainId
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
async callExecution(method, params = []) {
|
|
661
|
+
const executionRpcUrl = this.executionRpcUrl;
|
|
662
|
+
if (!executionRpcUrl) {
|
|
663
|
+
throw new Error("Execution RPC URL is not available before launch");
|
|
664
|
+
}
|
|
665
|
+
const response = await fetch(executionRpcUrl, {
|
|
666
|
+
method: "POST",
|
|
667
|
+
headers: { "content-type": "application/json" },
|
|
668
|
+
body: JSON.stringify({
|
|
669
|
+
id: 1,
|
|
670
|
+
jsonrpc: "2.0",
|
|
671
|
+
method,
|
|
672
|
+
params
|
|
673
|
+
}),
|
|
674
|
+
signal: AbortSignal.timeout(1e4)
|
|
675
|
+
});
|
|
676
|
+
if (!response.ok) {
|
|
677
|
+
throw new Error(`Execution RPC request failed for ${method}: ${response.status}`);
|
|
678
|
+
}
|
|
679
|
+
const body = await response.json();
|
|
680
|
+
if (body.error) {
|
|
681
|
+
throw new Error(
|
|
682
|
+
`Execution RPC ${method} failed (${body.error.code ?? "unknown"}): ${body.error.message ?? "unknown error"}`
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
return body.result;
|
|
686
|
+
}
|
|
687
|
+
async getBeacon(path2) {
|
|
688
|
+
const beaconApiUrl = this.beaconApiUrl;
|
|
689
|
+
if (!beaconApiUrl) {
|
|
690
|
+
throw new Error("Beacon API URL is not available before launch");
|
|
691
|
+
}
|
|
692
|
+
const response = await fetch(new URL(path2, `${beaconApiUrl}/`), {
|
|
693
|
+
signal: AbortSignal.timeout(1e4)
|
|
694
|
+
});
|
|
695
|
+
if (!response.ok) {
|
|
696
|
+
throw new Error(`Beacon API request failed for ${path2}: ${response.status}`);
|
|
697
|
+
}
|
|
698
|
+
return await response.json();
|
|
699
|
+
}
|
|
700
|
+
async deployMintingGatewayFixture(options) {
|
|
701
|
+
const { executionRpcUrl, chainId } = this;
|
|
702
|
+
if (!executionRpcUrl || !chainId) {
|
|
703
|
+
throw new Error("Ethereum devnet must be launched before deploying MintingGateway fixtures");
|
|
704
|
+
}
|
|
705
|
+
const account = privateKeyToAccount(options.deployerPrivateKey);
|
|
706
|
+
const adminSafe = options.adminSafe ?? account.address;
|
|
707
|
+
const guardianSafe = options.guardianSafe ?? adminSafe;
|
|
708
|
+
const chain = createExecutionChain(chainId, executionRpcUrl);
|
|
709
|
+
const publicClient = createPublicClient({
|
|
710
|
+
chain,
|
|
711
|
+
transport: http(executionRpcUrl)
|
|
712
|
+
});
|
|
713
|
+
const walletClient = createWalletClient({
|
|
714
|
+
account,
|
|
715
|
+
chain,
|
|
716
|
+
transport: http(executionRpcUrl)
|
|
717
|
+
});
|
|
718
|
+
const bootstrapCouncil = {
|
|
719
|
+
signers: [adminSafe],
|
|
720
|
+
weights: [1n]
|
|
721
|
+
};
|
|
722
|
+
const initialMicrogonsPerArgonot = options.initialMicrogonsPerArgonot ?? DEFAULT_INITIAL_MICROGONS_PER_ARGONOT;
|
|
723
|
+
const bootstrapCouncilHash = hashMintingGatewayGlobalIssuanceCouncil({
|
|
724
|
+
...bootstrapCouncil,
|
|
725
|
+
epochMicrogonsPerArgonot: initialMicrogonsPerArgonot
|
|
726
|
+
});
|
|
727
|
+
const bootstrapImplementationAddress = await deployContract(walletClient, publicClient, {
|
|
728
|
+
abi: mintingGatewayArtifact.abi,
|
|
729
|
+
bytecode: mintingGatewayArtifact.bytecode,
|
|
730
|
+
args: [zeroAddress, zeroAddress]
|
|
731
|
+
});
|
|
732
|
+
const initializeData = encodeFunctionData({
|
|
733
|
+
abi: mintingGatewayArtifact.abi,
|
|
734
|
+
functionName: "initialize",
|
|
735
|
+
args: [
|
|
736
|
+
adminSafe,
|
|
737
|
+
guardianSafe,
|
|
738
|
+
bootstrapCouncilHash,
|
|
739
|
+
BigInt(bootstrapCouncil.signers.length),
|
|
740
|
+
1n,
|
|
741
|
+
initialMicrogonsPerArgonot
|
|
742
|
+
]
|
|
743
|
+
});
|
|
744
|
+
const gatewayAddress = await deployContract(walletClient, publicClient, {
|
|
745
|
+
abi: transparentUpgradeableProxyArtifact.abi,
|
|
746
|
+
bytecode: transparentUpgradeableProxyArtifact.bytecode,
|
|
747
|
+
args: [bootstrapImplementationAddress, adminSafe, initializeData]
|
|
748
|
+
});
|
|
749
|
+
const proxyAdminAddress = getAddressFromStorage(
|
|
750
|
+
await publicClient.getStorageAt({
|
|
751
|
+
address: gatewayAddress,
|
|
752
|
+
slot: ERC1967_ADMIN_SLOT
|
|
753
|
+
})
|
|
754
|
+
);
|
|
755
|
+
const argonTokenAddress = await deployContract(walletClient, publicClient, {
|
|
756
|
+
abi: argonTokenArtifact.abi,
|
|
757
|
+
bytecode: argonTokenArtifact.bytecode,
|
|
758
|
+
args: [gatewayAddress]
|
|
759
|
+
});
|
|
760
|
+
const argonotTokenAddress = await deployContract(walletClient, publicClient, {
|
|
761
|
+
abi: argonotTokenArtifact.abi,
|
|
762
|
+
bytecode: argonotTokenArtifact.bytecode,
|
|
763
|
+
args: [gatewayAddress]
|
|
764
|
+
});
|
|
765
|
+
const finalImplementationAddress = await deployContract(walletClient, publicClient, {
|
|
766
|
+
abi: mintingGatewayArtifact.abi,
|
|
767
|
+
bytecode: mintingGatewayArtifact.bytecode,
|
|
768
|
+
args: [argonTokenAddress, argonotTokenAddress]
|
|
769
|
+
});
|
|
770
|
+
const upgradeHash = await walletClient.sendTransaction({
|
|
771
|
+
to: proxyAdminAddress,
|
|
772
|
+
data: encodeFunctionData({
|
|
773
|
+
abi: proxyAdminArtifact.abi,
|
|
774
|
+
functionName: "upgradeAndCall",
|
|
775
|
+
args: [gatewayAddress, finalImplementationAddress, "0x"]
|
|
776
|
+
})
|
|
777
|
+
});
|
|
778
|
+
const upgradeReceipt = await waitForExecutionReceipt(publicClient, upgradeHash);
|
|
779
|
+
if (upgradeReceipt.status !== "success") {
|
|
780
|
+
throw new Error("MintingGateway proxy upgrade failed");
|
|
781
|
+
}
|
|
782
|
+
if (options.seedArgonRecipient) {
|
|
783
|
+
const mintHash = await walletClient.sendTransaction({
|
|
784
|
+
to: gatewayAddress,
|
|
785
|
+
data: encodeFunctionData({
|
|
786
|
+
abi: mintingGatewayArtifact.abi,
|
|
787
|
+
functionName: "migrate",
|
|
788
|
+
args: [
|
|
789
|
+
{
|
|
790
|
+
recipients: [options.seedArgonRecipient],
|
|
791
|
+
amounts: [options.seedArgonAmountBaseUnits ?? DEFAULT_SEED_ARGON_AMOUNT_BASE_UNITS]
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
recipients: [],
|
|
795
|
+
amounts: []
|
|
796
|
+
}
|
|
797
|
+
]
|
|
798
|
+
})
|
|
799
|
+
});
|
|
800
|
+
const mintReceipt = await waitForExecutionReceipt(publicClient, mintHash);
|
|
801
|
+
if (mintReceipt.status !== "success") {
|
|
802
|
+
throw new Error("MintingGateway migrate failed");
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
argonTokenAddress,
|
|
807
|
+
argonotTokenAddress,
|
|
808
|
+
gatewayAddress
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
async teardown() {
|
|
812
|
+
if (this.#argsDir) {
|
|
813
|
+
await fs4.rm(this.#argsDir, { recursive: true, force: true });
|
|
814
|
+
this.#argsDir = void 0;
|
|
815
|
+
}
|
|
816
|
+
await runCommand(this.kurtosisBin, ["enclave", "rm", "-f", this.enclaveName], 6e4, true);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
async function deployContract(walletClient, publicClient, request) {
|
|
820
|
+
const hash = await walletClient.deployContract({
|
|
821
|
+
...request,
|
|
822
|
+
account: walletClient.account,
|
|
823
|
+
chain: walletClient.chain
|
|
824
|
+
});
|
|
825
|
+
const receipt = await waitForExecutionReceipt(publicClient, hash);
|
|
826
|
+
if (receipt.status !== "success" || !receipt.contractAddress) {
|
|
827
|
+
throw new Error(`Contract deployment failed for ${request.bytecode.slice(0, 10)}`);
|
|
828
|
+
}
|
|
829
|
+
return receipt.contractAddress;
|
|
830
|
+
}
|
|
831
|
+
function getAddressFromStorage(value) {
|
|
832
|
+
if (!value || value === "0x") {
|
|
833
|
+
throw new Error("Missing proxy admin address in ERC1967 admin slot");
|
|
834
|
+
}
|
|
835
|
+
return getAddress(`0x${value.slice(-40)}`);
|
|
836
|
+
}
|
|
837
|
+
function renderEthereumArgs(elPublicPortStart, clPublicPortStart, consensusClient, preset, secondsPerSlot, waitForFinalization, prefundedAccounts) {
|
|
838
|
+
const lines = [
|
|
839
|
+
"participants:",
|
|
840
|
+
" - el_type: geth",
|
|
841
|
+
` cl_type: ${consensusClient}`,
|
|
842
|
+
"network_params:",
|
|
843
|
+
" network: kurtosis",
|
|
844
|
+
` preset: ${preset}`,
|
|
845
|
+
...secondsPerSlot ? [` seconds_per_slot: ${secondsPerSlot}`] : [],
|
|
846
|
+
...prefundedAccounts && Object.keys(prefundedAccounts).length > 0 ? [` prefunded_accounts: '${JSON.stringify(prefundedAccounts)}'`] : [],
|
|
847
|
+
"additional_services: []",
|
|
848
|
+
`wait_for_finalization: ${waitForFinalization ? "true" : "false"}`,
|
|
849
|
+
"global_log_level: warn",
|
|
850
|
+
"port_publisher:",
|
|
851
|
+
" el:",
|
|
852
|
+
" enabled: true",
|
|
853
|
+
` public_port_start: ${elPublicPortStart}`,
|
|
854
|
+
" cl:",
|
|
855
|
+
" enabled: true",
|
|
856
|
+
` public_port_start: ${clPublicPortStart}`
|
|
857
|
+
];
|
|
858
|
+
lines.push("");
|
|
859
|
+
return lines.join("\n");
|
|
860
|
+
}
|
|
861
|
+
async function findExecutionRpcUrl(portStart, rangeSize) {
|
|
862
|
+
for (let port2 = portStart; port2 < portStart + rangeSize; port2 += 1) {
|
|
863
|
+
const url2 = `http://127.0.0.1:${port2}`;
|
|
864
|
+
const response = await fetchJsonRpc(url2, "eth_chainId");
|
|
865
|
+
if (typeof response === "string") {
|
|
866
|
+
return { url: url2, chainId: response };
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
throw new Error(
|
|
870
|
+
`Unable to find an execution RPC endpoint in ${portStart}-${portStart + rangeSize - 1}`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
async function findBeaconApiUrl(portStart, rangeSize) {
|
|
874
|
+
for (let port2 = portStart; port2 < portStart + rangeSize; port2 += 1) {
|
|
875
|
+
const url2 = `http://127.0.0.1:${port2}`;
|
|
876
|
+
try {
|
|
877
|
+
const response = await fetch(new URL("/eth/v1/node/version", `${url2}/`), {
|
|
878
|
+
signal: AbortSignal.timeout(2e3)
|
|
879
|
+
});
|
|
880
|
+
if (!response.ok) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
const body = await response.json();
|
|
884
|
+
if (body.data?.version) {
|
|
885
|
+
return { url: url2 };
|
|
886
|
+
}
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
throw new Error(
|
|
891
|
+
`Unable to find a Beacon API endpoint in ${portStart}-${portStart + rangeSize - 1}`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
async function fetchJsonRpc(url2, method) {
|
|
895
|
+
try {
|
|
896
|
+
const response = await fetch(url2, {
|
|
897
|
+
method: "POST",
|
|
898
|
+
headers: { "content-type": "application/json" },
|
|
899
|
+
body: JSON.stringify({
|
|
900
|
+
id: 1,
|
|
901
|
+
jsonrpc: "2.0",
|
|
902
|
+
method,
|
|
903
|
+
params: []
|
|
904
|
+
}),
|
|
905
|
+
signal: AbortSignal.timeout(2e3)
|
|
906
|
+
});
|
|
907
|
+
if (!response.ok) {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
const body = await response.json();
|
|
911
|
+
return body.result ?? null;
|
|
912
|
+
} catch {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function createExecutionChain(chainId, executionRpcUrl) {
|
|
917
|
+
return defineChain({
|
|
918
|
+
id: Number.parseInt(chainId, 16),
|
|
919
|
+
name: "argon-test-ethereum",
|
|
920
|
+
nativeCurrency: {
|
|
921
|
+
name: "Ether",
|
|
922
|
+
symbol: "ETH",
|
|
923
|
+
decimals: 18
|
|
924
|
+
},
|
|
925
|
+
rpcUrls: {
|
|
926
|
+
default: {
|
|
927
|
+
http: [executionRpcUrl]
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
async function findFreePortRange(start, size) {
|
|
933
|
+
for (let candidate = start; candidate < start + 1e3; candidate += size) {
|
|
934
|
+
const ports = Array.from({ length: size }, (_, index) => candidate + index);
|
|
935
|
+
const results = await Promise.all(ports.map((port2) => detectPort2(port2)));
|
|
936
|
+
if (results.every((resolvedPort, index) => resolvedPort === ports[index])) {
|
|
937
|
+
return candidate;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
throw new Error(`Unable to find a free port range starting near ${start}`);
|
|
941
|
+
}
|
|
942
|
+
async function waitForProbe(probe, timeoutMs) {
|
|
943
|
+
const start = Date.now();
|
|
944
|
+
let lastError;
|
|
945
|
+
while (Date.now() - start < timeoutMs) {
|
|
946
|
+
try {
|
|
947
|
+
return await probe();
|
|
948
|
+
} catch (error) {
|
|
949
|
+
lastError = error;
|
|
950
|
+
await delay(PROBE_INTERVAL_MS);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
throw lastError instanceof Error ? lastError : new Error("Timed out waiting for probe");
|
|
954
|
+
}
|
|
955
|
+
async function waitForExecutionReceipt(publicClient, hash) {
|
|
956
|
+
const start = Date.now();
|
|
957
|
+
let lastError;
|
|
958
|
+
while (Date.now() - start < 12e4) {
|
|
959
|
+
try {
|
|
960
|
+
const receipt = await publicClient.getTransactionReceipt({ hash });
|
|
961
|
+
if (receipt) {
|
|
962
|
+
return receipt;
|
|
963
|
+
}
|
|
964
|
+
} catch (error) {
|
|
965
|
+
const errorText = error instanceof Error ? [
|
|
966
|
+
error.message,
|
|
967
|
+
"details" in error && typeof error.details === "string" ? error.details : void 0
|
|
968
|
+
].filter(Boolean).join(" ") : String(error);
|
|
969
|
+
if (!errorText.includes("indexing is in progress") && !errorText.includes("Transaction receipt with hash") && !errorText.includes("could not be found")) {
|
|
970
|
+
throw error;
|
|
971
|
+
}
|
|
972
|
+
lastError = error instanceof Error ? error : new Error(errorText);
|
|
973
|
+
}
|
|
974
|
+
await delay(500);
|
|
975
|
+
}
|
|
976
|
+
throw lastError ?? new Error(`Timed out waiting for execution receipt ${hash}`);
|
|
977
|
+
}
|
|
978
|
+
async function runCommand(command, args, timeoutMs, allowFailure = false) {
|
|
979
|
+
await new Promise((resolve3, reject) => {
|
|
980
|
+
const child = spawn4(command, args, {
|
|
981
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
982
|
+
});
|
|
983
|
+
let stdout = "";
|
|
984
|
+
let stderr = "";
|
|
985
|
+
const timeout = setTimeout(() => {
|
|
986
|
+
child.kill("SIGTERM");
|
|
987
|
+
reject(new Error(`Command timed out: ${command} ${args.join(" ")}`));
|
|
988
|
+
}, timeoutMs);
|
|
989
|
+
child.stdout?.setEncoding("utf8");
|
|
990
|
+
child.stderr?.setEncoding("utf8");
|
|
991
|
+
child.stdout?.on("data", (chunk) => {
|
|
992
|
+
stdout += chunk;
|
|
993
|
+
});
|
|
994
|
+
child.stderr?.on("data", (chunk) => {
|
|
995
|
+
stderr += chunk;
|
|
996
|
+
});
|
|
997
|
+
child.on("error", (error) => {
|
|
998
|
+
clearTimeout(timeout);
|
|
999
|
+
reject(error);
|
|
1000
|
+
});
|
|
1001
|
+
child.on("exit", (code) => {
|
|
1002
|
+
clearTimeout(timeout);
|
|
1003
|
+
if (code === 0 || allowFailure) {
|
|
1004
|
+
resolve3();
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
reject(
|
|
1008
|
+
new Error(
|
|
1009
|
+
[`Command failed: ${command} ${args.join(" ")}`, stdout.trim(), stderr.trim()].filter(Boolean).join("\n")
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
async function delay(ms) {
|
|
1016
|
+
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1017
|
+
}
|
|
1018
|
+
|
|
544
1019
|
// src/TestNetwork.ts
|
|
545
1020
|
import * as docker from "docker-compose";
|
|
546
|
-
import * as
|
|
1021
|
+
import * as Path6 from "path";
|
|
547
1022
|
async function startNetwork(testName, options) {
|
|
548
|
-
const config =
|
|
1023
|
+
const config = Path6.join(__dirname, `dev.docker-compose.yml`);
|
|
549
1024
|
const env4 = {
|
|
550
1025
|
VERSION: "dev",
|
|
551
1026
|
ARGON_CHAIN: "dev-docker",
|
|
@@ -577,6 +1052,989 @@ async function startNetwork(testName, options) {
|
|
|
577
1052
|
};
|
|
578
1053
|
}
|
|
579
1054
|
|
|
1055
|
+
// src/EthereumE2eUtils.ts
|
|
1056
|
+
import {
|
|
1057
|
+
getLatestArgonFinalizedExecutionHeader,
|
|
1058
|
+
getNextEthereumBeaconSyncTxs,
|
|
1059
|
+
hexToU8a,
|
|
1060
|
+
isOutdatedTransactionError,
|
|
1061
|
+
TxSubmitter as TxSubmitter2,
|
|
1062
|
+
u8aToHex
|
|
1063
|
+
} from "@argonprotocol/mainchain";
|
|
1064
|
+
import { parseSignature } from "viem";
|
|
1065
|
+
async function signGatewayPermit(args) {
|
|
1066
|
+
const signature = parseSignature(
|
|
1067
|
+
await args.account.signTypedData({
|
|
1068
|
+
domain: {
|
|
1069
|
+
name: "Argon",
|
|
1070
|
+
version: "1",
|
|
1071
|
+
chainId: args.chainId,
|
|
1072
|
+
verifyingContract: args.tokenAddress
|
|
1073
|
+
},
|
|
1074
|
+
types: {
|
|
1075
|
+
Permit: [
|
|
1076
|
+
{ name: "owner", type: "address" },
|
|
1077
|
+
{ name: "spender", type: "address" },
|
|
1078
|
+
{ name: "value", type: "uint256" },
|
|
1079
|
+
{ name: "nonce", type: "uint256" },
|
|
1080
|
+
{ name: "deadline", type: "uint256" }
|
|
1081
|
+
]
|
|
1082
|
+
},
|
|
1083
|
+
primaryType: "Permit",
|
|
1084
|
+
message: {
|
|
1085
|
+
owner: args.owner,
|
|
1086
|
+
spender: args.gatewayAddress,
|
|
1087
|
+
value: args.value,
|
|
1088
|
+
nonce: args.nonce,
|
|
1089
|
+
deadline: args.deadline
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
);
|
|
1093
|
+
return {
|
|
1094
|
+
v: Number(signature.v),
|
|
1095
|
+
r: signature.r,
|
|
1096
|
+
s: signature.s
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
async function waitForFinalizedBeaconExecutionAtOrAbove(ethereum, minimumExecutionBlockNumber, options = {}) {
|
|
1100
|
+
const startedAt = Date.now();
|
|
1101
|
+
const minimumFinalizedSlot = options.minimumFinalizedSlot ?? 0n;
|
|
1102
|
+
let lastSeenExecutionBlockNumber = 0n;
|
|
1103
|
+
let lastSeenHeadSlot = 0n;
|
|
1104
|
+
let lastSeenFinalizedSlot = 0n;
|
|
1105
|
+
let lastError;
|
|
1106
|
+
while (Date.now() - startedAt < 3e5) {
|
|
1107
|
+
try {
|
|
1108
|
+
const [headHeader, finalizedHeader] = await Promise.all([
|
|
1109
|
+
ethereum.getBeacon("/eth/v1/beacon/headers/head"),
|
|
1110
|
+
ethereum.getBeacon("/eth/v1/beacon/headers/finalized")
|
|
1111
|
+
]);
|
|
1112
|
+
lastSeenHeadSlot = BigInt(headHeader.data.header.message.slot);
|
|
1113
|
+
lastSeenFinalizedSlot = BigInt(finalizedHeader.data.header.message.slot);
|
|
1114
|
+
const block = await ethereum.getBeacon(
|
|
1115
|
+
`/eth/v2/beacon/blocks/${finalizedHeader.data.root}`
|
|
1116
|
+
);
|
|
1117
|
+
const executionBlockNumber = BigInt(block.data.message.body.execution_payload.block_number);
|
|
1118
|
+
lastSeenExecutionBlockNumber = executionBlockNumber;
|
|
1119
|
+
lastError = void 0;
|
|
1120
|
+
if (executionBlockNumber >= minimumExecutionBlockNumber && lastSeenFinalizedSlot >= minimumFinalizedSlot) {
|
|
1121
|
+
return { header: finalizedHeader, block };
|
|
1122
|
+
}
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
if (!(error instanceof Error)) {
|
|
1125
|
+
throw error;
|
|
1126
|
+
}
|
|
1127
|
+
lastError = error;
|
|
1128
|
+
}
|
|
1129
|
+
await delay2(1e3);
|
|
1130
|
+
}
|
|
1131
|
+
const lastErrorSuffix = lastError ? `; last beacon error was: ${lastError.message}` : "";
|
|
1132
|
+
throw new Error(
|
|
1133
|
+
`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}`
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
async function mineLaterExecutionAnchorReceipt(walletClient, chain, ethereum, account, minimumBlockNumber) {
|
|
1137
|
+
while (true) {
|
|
1138
|
+
const transactionHash = await walletClient.sendTransaction({
|
|
1139
|
+
account,
|
|
1140
|
+
chain,
|
|
1141
|
+
to: account.address,
|
|
1142
|
+
value: 0n
|
|
1143
|
+
});
|
|
1144
|
+
const receipt = await waitForExecutionReceipt2(ethereum, transactionHash);
|
|
1145
|
+
if (BigInt(receipt.blockNumber) > minimumBlockNumber) {
|
|
1146
|
+
return receipt;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async function waitForExecutionReceipt2(ethereum, transactionHash) {
|
|
1151
|
+
const startedAt = Date.now();
|
|
1152
|
+
while (Date.now() - startedAt < 12e4) {
|
|
1153
|
+
try {
|
|
1154
|
+
const receipt = await ethereum.callExecution(
|
|
1155
|
+
"eth_getTransactionReceipt",
|
|
1156
|
+
[transactionHash]
|
|
1157
|
+
);
|
|
1158
|
+
if (receipt) {
|
|
1159
|
+
return receipt;
|
|
1160
|
+
}
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
const errorText = error instanceof Error ? [
|
|
1163
|
+
error.message,
|
|
1164
|
+
"details" in error && typeof error.details === "string" ? error.details : void 0
|
|
1165
|
+
].filter(Boolean).join(" ") : String(error);
|
|
1166
|
+
if (!errorText.includes("indexing is in progress")) {
|
|
1167
|
+
throw error;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
await delay2(500);
|
|
1171
|
+
}
|
|
1172
|
+
throw new Error(`Timed out waiting for execution receipt ${transactionHash}`);
|
|
1173
|
+
}
|
|
1174
|
+
async function syncEthereumVerifierUntilAnchorCovers(mainchainClient, relayer, beaconApiUrl, minimumExecutionBlockNumber) {
|
|
1175
|
+
const startedAt = Date.now();
|
|
1176
|
+
const timeoutMs = 5 * 6e4;
|
|
1177
|
+
let lastRetryableError;
|
|
1178
|
+
let lastAnchorBlockNumber;
|
|
1179
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1180
|
+
try {
|
|
1181
|
+
const anchor = await getLatestArgonFinalizedExecutionHeader(mainchainClient);
|
|
1182
|
+
lastAnchorBlockNumber = anchor.blockNumber;
|
|
1183
|
+
if (anchor.blockNumber >= minimumExecutionBlockNumber) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
} catch {
|
|
1187
|
+
}
|
|
1188
|
+
const txs = await getNextEthereumBeaconSyncTxs(mainchainClient, beaconApiUrl);
|
|
1189
|
+
if (txs.length === 0) {
|
|
1190
|
+
await delay2(500);
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
let shouldRetry = false;
|
|
1194
|
+
for (const tx of txs) {
|
|
1195
|
+
try {
|
|
1196
|
+
const result = await new TxSubmitter2(mainchainClient, tx, relayer).submit();
|
|
1197
|
+
await result.waitForInFirstBlock;
|
|
1198
|
+
lastRetryableError = void 0;
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
if (isRetryableEthereumVerifierSyncError(error)) {
|
|
1201
|
+
lastRetryableError = error instanceof Error ? error : new Error(String(error));
|
|
1202
|
+
shouldRetry = true;
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
1205
|
+
throw error;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (shouldRetry) {
|
|
1209
|
+
await delay2(500);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
throw lastRetryableError ?? new Error(
|
|
1213
|
+
`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"}`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
function toArgonKeccakSignature(signature) {
|
|
1217
|
+
const bytes = hexToU8a(signature);
|
|
1218
|
+
if (bytes.length !== 65) {
|
|
1219
|
+
throw new Error(`Expected 65-byte ECDSA signature, received ${bytes.length} bytes`);
|
|
1220
|
+
}
|
|
1221
|
+
if (bytes[64] >= 27) {
|
|
1222
|
+
bytes[64] -= 27;
|
|
1223
|
+
}
|
|
1224
|
+
return u8aToHex(bytes);
|
|
1225
|
+
}
|
|
1226
|
+
function toEvmRecoverableSignature(signature) {
|
|
1227
|
+
const bytes = hexToU8a(signature);
|
|
1228
|
+
if (bytes.length !== 65) {
|
|
1229
|
+
throw new Error(`Expected 65-byte ECDSA signature, received ${bytes.length} bytes`);
|
|
1230
|
+
}
|
|
1231
|
+
if (bytes[64] <= 1) {
|
|
1232
|
+
bytes[64] += 27;
|
|
1233
|
+
}
|
|
1234
|
+
return u8aToHex(bytes);
|
|
1235
|
+
}
|
|
1236
|
+
function isRetryableEthereumVerifierSyncError(error) {
|
|
1237
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1238
|
+
return isOutdatedTransactionError(error) || message.includes("ethereumVerifier.InvalidHeaderMerkleProof");
|
|
1239
|
+
}
|
|
1240
|
+
async function delay2(ms) {
|
|
1241
|
+
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/EthereumGatewayQueue.ts
|
|
1245
|
+
import {
|
|
1246
|
+
EvmContracts as EvmContracts2
|
|
1247
|
+
} from "@argonprotocol/mainchain";
|
|
1248
|
+
import { getAddress as getAddress2 } from "viem";
|
|
1249
|
+
var {
|
|
1250
|
+
encodeMintingGatewayMintingAuthorityActivationTarget,
|
|
1251
|
+
hashMintingGatewayActivateMintingAuthority,
|
|
1252
|
+
hashMintingGatewayGatewayUpdateApproval,
|
|
1253
|
+
mintingGatewayAbi,
|
|
1254
|
+
MINTING_GATEWAY_UPDATE_KINDS
|
|
1255
|
+
} = EvmContracts2;
|
|
1256
|
+
async function getReadyEthereumGatewayUpdates(client, gatewayClient, options = {}) {
|
|
1257
|
+
const destinationChain = options.destinationChain ?? "Ethereum";
|
|
1258
|
+
const maxQueueEntries = options.maxQueueEntries ?? 100;
|
|
1259
|
+
if (maxQueueEntries < 1) {
|
|
1260
|
+
throw new Error(`maxQueueEntries must be at least 1, received ${maxQueueEntries}`);
|
|
1261
|
+
}
|
|
1262
|
+
const chainConfigOption = await client.query.crosschainTransfer.chainConfigBySourceChain(destinationChain);
|
|
1263
|
+
if (chainConfigOption.isNone) {
|
|
1264
|
+
throw new Error(`Crosschain config not found for ${destinationChain}`);
|
|
1265
|
+
}
|
|
1266
|
+
const chainConfig = chainConfigOption.unwrap();
|
|
1267
|
+
if (!chainConfig.isEvm) {
|
|
1268
|
+
throw new Error(`Chain config for ${destinationChain} is not EVM-shaped`);
|
|
1269
|
+
}
|
|
1270
|
+
const gatewayAddress = getAddress2(toHexValue(chainConfig.asEvm.gateway));
|
|
1271
|
+
const chainId = chainConfig.asEvm.chainId.toBigInt();
|
|
1272
|
+
const hashContext = { chainId, gatewayAddress };
|
|
1273
|
+
const currentCouncilHashOption = await client.query.crosschainTransfer.activeGlobalIssuanceCouncilByDestinationChain(
|
|
1274
|
+
destinationChain
|
|
1275
|
+
);
|
|
1276
|
+
if (currentCouncilHashOption.isNone) {
|
|
1277
|
+
throw new Error(`Active GlobalIssuanceCouncil not found for ${destinationChain}`);
|
|
1278
|
+
}
|
|
1279
|
+
const currentCouncilHash = toHexValue(currentCouncilHashOption.unwrap());
|
|
1280
|
+
const councilCache = /* @__PURE__ */ new Map();
|
|
1281
|
+
const currentCouncil = councilToSnapshot(
|
|
1282
|
+
await loadCouncilByHash(client, currentCouncilHash, councilCache)
|
|
1283
|
+
);
|
|
1284
|
+
const [rawArgonApprovalsNonce, rawArgonApprovalsHash, rawPaused] = await Promise.all([
|
|
1285
|
+
gatewayClient.readContract({
|
|
1286
|
+
abi: mintingGatewayAbi,
|
|
1287
|
+
address: gatewayAddress,
|
|
1288
|
+
functionName: "argonApprovalsNonce"
|
|
1289
|
+
}),
|
|
1290
|
+
gatewayClient.readContract({
|
|
1291
|
+
abi: mintingGatewayAbi,
|
|
1292
|
+
address: gatewayAddress,
|
|
1293
|
+
functionName: "argonApprovalsHash"
|
|
1294
|
+
}),
|
|
1295
|
+
gatewayClient.readContract({
|
|
1296
|
+
abi: mintingGatewayAbi,
|
|
1297
|
+
address: gatewayAddress,
|
|
1298
|
+
functionName: "paused"
|
|
1299
|
+
})
|
|
1300
|
+
]);
|
|
1301
|
+
const argonApprovalsNonce = rawArgonApprovalsNonce;
|
|
1302
|
+
const argonApprovalsHash = rawArgonApprovalsHash;
|
|
1303
|
+
const paused = rawPaused;
|
|
1304
|
+
const updates = [];
|
|
1305
|
+
let expectedPreviousApprovalHash = argonApprovalsHash;
|
|
1306
|
+
let firstQueueNonce;
|
|
1307
|
+
let lastQueueNonce;
|
|
1308
|
+
if (!paused) {
|
|
1309
|
+
for (let queueNonce = argonApprovalsNonce + 1n; updates.length < maxQueueEntries; queueNonce += 1n) {
|
|
1310
|
+
const entryOption = await client.query.crosschainTransfer.councilApprovalQueueByDestinationChainAndNonce(
|
|
1311
|
+
destinationChain,
|
|
1312
|
+
queueNonce
|
|
1313
|
+
);
|
|
1314
|
+
if (entryOption.isNone) {
|
|
1315
|
+
break;
|
|
1316
|
+
}
|
|
1317
|
+
const entry = entryOption.unwrap();
|
|
1318
|
+
const approvingCouncilHash = toHexValue(entry.approvingCouncilHash);
|
|
1319
|
+
const approvingCouncil = await loadCouncilByHash(client, approvingCouncilHash, councilCache);
|
|
1320
|
+
if (!queueEntryHasQuorum(entry, approvingCouncil)) {
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1323
|
+
if (toHexValue(entry.previousApprovalHash) !== expectedPreviousApprovalHash) {
|
|
1324
|
+
throw new Error(
|
|
1325
|
+
`Queue nonce ${queueNonce} expected previous approval hash ${expectedPreviousApprovalHash}, received ${toHexValue(entry.previousApprovalHash)}`
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
const update = await buildGatewayUpdate(client, destinationChain, hashContext, queueNonce, {
|
|
1329
|
+
entry,
|
|
1330
|
+
approvingCouncilHash
|
|
1331
|
+
});
|
|
1332
|
+
updates.push(update);
|
|
1333
|
+
firstQueueNonce ??= queueNonce;
|
|
1334
|
+
lastQueueNonce = queueNonce;
|
|
1335
|
+
expectedPreviousApprovalHash = toHexValue(entry.approvalHash);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
return {
|
|
1339
|
+
destinationChain,
|
|
1340
|
+
chainId,
|
|
1341
|
+
gatewayAddress,
|
|
1342
|
+
currentCouncilHash,
|
|
1343
|
+
currentCouncil,
|
|
1344
|
+
argonApprovalsNonce,
|
|
1345
|
+
argonApprovalsHash,
|
|
1346
|
+
paused,
|
|
1347
|
+
...firstQueueNonce !== void 0 ? { firstQueueNonce, lastQueueNonce } : {},
|
|
1348
|
+
updates
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
async function buildGatewayUpdate(client, destinationChain, hashContext, queueNonce, queueItem) {
|
|
1352
|
+
const { entry, approvingCouncilHash } = queueItem;
|
|
1353
|
+
const signatures = getSortedSignatures(entry.signatures);
|
|
1354
|
+
if (entry.target.isMintingAuthorityActivation) {
|
|
1355
|
+
const signingKey = getAddress2(toHexValue(entry.target.asMintingAuthorityActivation));
|
|
1356
|
+
const authorityOption = await client.query.crosschainTransfer.mintingAuthoritiesBySigner(signingKey);
|
|
1357
|
+
if (authorityOption.isNone) {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
`Minting authority activation ${signingKey} not found for queue nonce ${queueNonce}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
const authority = authorityOption.unwrap();
|
|
1363
|
+
if (authority.destinationChain.type !== destinationChain) {
|
|
1364
|
+
throw new Error(
|
|
1365
|
+
`Minting authority ${signingKey} belongs to ${String(authority.destinationChain.type)}, expected ${String(destinationChain)}`
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
const target = {
|
|
1369
|
+
microgonCollateral: authority.gatewayRemainingMicrogonCollateral.toBigInt(),
|
|
1370
|
+
micronotCollateral: authority.gatewayRemainingMicronotCollateral.toBigInt(),
|
|
1371
|
+
signingKey
|
|
1372
|
+
};
|
|
1373
|
+
const payload = encodeMintingGatewayMintingAuthorityActivationTarget(target);
|
|
1374
|
+
const targetPayloadHash = payloadHashFromActivationPayload(hashContext, target);
|
|
1375
|
+
const approvalHash = hashMintingGatewayGatewayUpdateApproval(hashContext, {
|
|
1376
|
+
queueNonce,
|
|
1377
|
+
approvingCouncilHash,
|
|
1378
|
+
kind: MINTING_GATEWAY_UPDATE_KINDS.mintingAuthorityActivate,
|
|
1379
|
+
targetId: `0x${signingKey.slice(2).padStart(64, "0").toLowerCase()}`,
|
|
1380
|
+
targetPayloadHash,
|
|
1381
|
+
previousUpdateHash: toHexValue(entry.previousApprovalHash)
|
|
1382
|
+
});
|
|
1383
|
+
if (toHexValue(entry.targetPayloadHash) !== targetPayloadHash) {
|
|
1384
|
+
throw new Error(`Queue nonce ${queueNonce} target payload hash does not match authority`);
|
|
1385
|
+
}
|
|
1386
|
+
if (toHexValue(entry.approvalHash) !== approvalHash) {
|
|
1387
|
+
throw new Error(
|
|
1388
|
+
`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)}`
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
return {
|
|
1392
|
+
queueNonce,
|
|
1393
|
+
kind: MINTING_GATEWAY_UPDATE_KINDS.mintingAuthorityActivate,
|
|
1394
|
+
payload,
|
|
1395
|
+
signatures
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
throw new Error(`Unsupported approval queue target ${entry.target.type}`);
|
|
1399
|
+
}
|
|
1400
|
+
async function loadCouncilByHash(client, councilHash, cache) {
|
|
1401
|
+
const cached = cache.get(councilHash);
|
|
1402
|
+
if (cached) {
|
|
1403
|
+
return cached;
|
|
1404
|
+
}
|
|
1405
|
+
const councilOption = await client.query.crosschainTransfer.globalIssuanceCouncilByHash(councilHash);
|
|
1406
|
+
if (councilOption.isNone) {
|
|
1407
|
+
throw new Error(`GlobalIssuanceCouncil ${councilHash} not found`);
|
|
1408
|
+
}
|
|
1409
|
+
const council = councilOption.unwrap();
|
|
1410
|
+
const loaded = {
|
|
1411
|
+
totalWeight: council.totalWeight.toBigInt(),
|
|
1412
|
+
members: [...council.members.entries()].map(([signer, member]) => ({
|
|
1413
|
+
signer: getAddress2(toHexValue(signer)),
|
|
1414
|
+
weight: member.weight.toBigInt()
|
|
1415
|
+
})).sort((left, right) => left.signer.localeCompare(right.signer))
|
|
1416
|
+
};
|
|
1417
|
+
cache.set(councilHash, loaded);
|
|
1418
|
+
return loaded;
|
|
1419
|
+
}
|
|
1420
|
+
function queueEntryHasQuorum(entry, council) {
|
|
1421
|
+
const signedWeight = [...entry.signatures.entries()].reduce((total, [signer, signature]) => {
|
|
1422
|
+
const signerAddress = getAddress2(toHexValue(signer));
|
|
1423
|
+
const member = council.members.find((x) => x.signer === signerAddress);
|
|
1424
|
+
if (!member) {
|
|
1425
|
+
throw new Error(`Signature submitted by ${signerAddress}, which is not in the council`);
|
|
1426
|
+
}
|
|
1427
|
+
return total + member.weight;
|
|
1428
|
+
}, 0n);
|
|
1429
|
+
return signedWeight * 2n > council.totalWeight;
|
|
1430
|
+
}
|
|
1431
|
+
function councilToSnapshot(council) {
|
|
1432
|
+
return {
|
|
1433
|
+
signers: council.members.map((member) => member.signer),
|
|
1434
|
+
weights: council.members.map((member) => member.weight)
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function payloadHashFromActivationPayload(hashContext, target) {
|
|
1438
|
+
return hashMintingGatewayActivateMintingAuthority(hashContext, target);
|
|
1439
|
+
}
|
|
1440
|
+
function getSortedSignatures(signatures) {
|
|
1441
|
+
return [...signatures.entries()].sort(
|
|
1442
|
+
([leftSigner], [rightSigner]) => toHexValue(leftSigner).localeCompare(toHexValue(rightSigner))
|
|
1443
|
+
).map(([, signature]) => toEvmRecoverableSignature(toHexValue(signature)));
|
|
1444
|
+
}
|
|
1445
|
+
function toHexValue(value) {
|
|
1446
|
+
return value.toHex();
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/TestEthereumProofActors.ts
|
|
1450
|
+
import {
|
|
1451
|
+
buildGatewayActivityProofPayload,
|
|
1452
|
+
decodeAddress,
|
|
1453
|
+
dispatchErrorToString,
|
|
1454
|
+
EvmContracts as EvmContracts3,
|
|
1455
|
+
getEthereumBeaconSyncBootstrapTx,
|
|
1456
|
+
Keyring as Keyring3,
|
|
1457
|
+
toFixedNumber,
|
|
1458
|
+
TxSubmitter as TxSubmitter3,
|
|
1459
|
+
U8aFixed,
|
|
1460
|
+
Vault,
|
|
1461
|
+
Vec
|
|
1462
|
+
} from "@argonprotocol/mainchain";
|
|
1463
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
1464
|
+
import {
|
|
1465
|
+
createPublicClient as createPublicClient2,
|
|
1466
|
+
createWalletClient as createWalletClient3,
|
|
1467
|
+
defineChain as defineChain3,
|
|
1468
|
+
encodeFunctionData as encodeFunctionData2,
|
|
1469
|
+
http as http2,
|
|
1470
|
+
toHex
|
|
1471
|
+
} from "viem";
|
|
1472
|
+
var { argonTokenAbi, mintingGatewayAbi: mintingGatewayAbi2, MINTING_GATEWAY_RUNTIME_TO_ERC20_SCALE } = EvmContracts3;
|
|
1473
|
+
var MINIMAL_BOOTSTRAP_FINALIZED_SLOT = 64n;
|
|
1474
|
+
var EthereumProofE2eHarness = class _EthereumProofE2eHarness {
|
|
1475
|
+
constructor(ethereum, endpoints, mainchain, mainchainClient, deployerPrivateKey, proofRelayerUri) {
|
|
1476
|
+
this.ethereum = ethereum;
|
|
1477
|
+
this.endpoints = endpoints;
|
|
1478
|
+
this.mainchain = mainchain;
|
|
1479
|
+
this.mainchainClient = mainchainClient;
|
|
1480
|
+
this.deployer = privateKeyToAccount2(deployerPrivateKey);
|
|
1481
|
+
this.chain = defineChain3({
|
|
1482
|
+
id: Number.parseInt(endpoints.chainId, 16),
|
|
1483
|
+
name: "argon-test-ethereum",
|
|
1484
|
+
nativeCurrency: {
|
|
1485
|
+
name: "Ether",
|
|
1486
|
+
symbol: "ETH",
|
|
1487
|
+
decimals: 18
|
|
1488
|
+
},
|
|
1489
|
+
rpcUrls: {
|
|
1490
|
+
default: {
|
|
1491
|
+
http: [endpoints.executionRpcUrl]
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
this.publicClient = createPublicClient2({
|
|
1496
|
+
chain: this.chain,
|
|
1497
|
+
transport: http2(endpoints.executionRpcUrl)
|
|
1498
|
+
});
|
|
1499
|
+
this.walletClient = createWalletClient3({
|
|
1500
|
+
account: this.deployer,
|
|
1501
|
+
chain: this.chain,
|
|
1502
|
+
transport: http2(endpoints.executionRpcUrl)
|
|
1503
|
+
});
|
|
1504
|
+
this.proofRelayer = new Keyring3({ type: "sr25519" }).createFromUri(proofRelayerUri);
|
|
1505
|
+
}
|
|
1506
|
+
sudoSigner = new Keyring3({ type: "sr25519" }).createFromUri("//Alice");
|
|
1507
|
+
deployer;
|
|
1508
|
+
chain;
|
|
1509
|
+
publicClient;
|
|
1510
|
+
walletClient;
|
|
1511
|
+
mainchainClient;
|
|
1512
|
+
proofRelayer;
|
|
1513
|
+
static async launch(args) {
|
|
1514
|
+
const ethereum = new TestEthereum();
|
|
1515
|
+
const endpoints = await ethereum.launch({
|
|
1516
|
+
consensusClient: "lodestar",
|
|
1517
|
+
preset: "minimal",
|
|
1518
|
+
secondsPerSlot: 1,
|
|
1519
|
+
prefundedAccounts: {
|
|
1520
|
+
[args.testAccount.address]: {
|
|
1521
|
+
balance: args.testAccount.balance
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
const mainchain = new TestMainchain();
|
|
1526
|
+
await mainchain.launch();
|
|
1527
|
+
const mainchainClient = await mainchain.client();
|
|
1528
|
+
return new _EthereumProofE2eHarness(
|
|
1529
|
+
ethereum,
|
|
1530
|
+
endpoints,
|
|
1531
|
+
mainchain,
|
|
1532
|
+
mainchainClient,
|
|
1533
|
+
args.testAccount.privateKey,
|
|
1534
|
+
args.proofRelayerUri
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
async submit(tx, signer) {
|
|
1538
|
+
const result = await new TxSubmitter3(this.mainchainClient, tx, signer).submit();
|
|
1539
|
+
await result.waitForInFirstBlock;
|
|
1540
|
+
return result;
|
|
1541
|
+
}
|
|
1542
|
+
async sudoSubmit(tx) {
|
|
1543
|
+
const result = await this.submit(
|
|
1544
|
+
this.mainchainClient.tx.sudo.sudo(tx),
|
|
1545
|
+
this.sudoSigner
|
|
1546
|
+
);
|
|
1547
|
+
const sudoEvent = result.events.find((event) => this.mainchainClient.events.sudo.Sudid.is(event));
|
|
1548
|
+
if (!sudoEvent || !this.mainchainClient.events.sudo.Sudid.is(sudoEvent)) {
|
|
1549
|
+
throw new Error("sudo did not emit sudo.Sudid");
|
|
1550
|
+
}
|
|
1551
|
+
const sudoResult = sudoEvent.data[0];
|
|
1552
|
+
if (sudoResult.isErr) {
|
|
1553
|
+
throw new Error(
|
|
1554
|
+
`sudo failed: ${dispatchErrorToString(this.mainchainClient, sudoResult.asErr)}`
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
return result;
|
|
1558
|
+
}
|
|
1559
|
+
async bootstrapVerifier() {
|
|
1560
|
+
const checkpointTx = await getEthereumBeaconSyncBootstrapTx(
|
|
1561
|
+
this.mainchainClient,
|
|
1562
|
+
this.endpoints.beaconApiUrl
|
|
1563
|
+
);
|
|
1564
|
+
return this.sudoSubmit(checkpointTx);
|
|
1565
|
+
}
|
|
1566
|
+
async syncVerifierThrough(minimumExecutionBlockNumber) {
|
|
1567
|
+
await syncEthereumVerifierUntilAnchorCovers(
|
|
1568
|
+
this.mainchainClient,
|
|
1569
|
+
this.sudoSigner,
|
|
1570
|
+
this.endpoints.beaconApiUrl,
|
|
1571
|
+
minimumExecutionBlockNumber
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
async proveGatewayActivity(gatewayAddress, throughExecutionBlockNumber) {
|
|
1575
|
+
const payload = await buildGatewayActivityProofPayload(this.mainchainClient, {
|
|
1576
|
+
executionRpcUrl: this.endpoints.executionRpcUrl,
|
|
1577
|
+
gatewayAddress,
|
|
1578
|
+
throughExecutionBlockNumber
|
|
1579
|
+
});
|
|
1580
|
+
if (!payload) {
|
|
1581
|
+
throw new Error("Expected uncovered gateway activity to prove");
|
|
1582
|
+
}
|
|
1583
|
+
const result = await this.submit(
|
|
1584
|
+
this.mainchainClient.tx.crosschainTransfer.proveGatewayActivity(
|
|
1585
|
+
"Ethereum",
|
|
1586
|
+
payload.previousGatewayActivityNonce,
|
|
1587
|
+
payload.proof
|
|
1588
|
+
),
|
|
1589
|
+
this.proofRelayer
|
|
1590
|
+
);
|
|
1591
|
+
return { payload, result };
|
|
1592
|
+
}
|
|
1593
|
+
async setEthereumChainConfig(gateway) {
|
|
1594
|
+
return this.sudoSubmit(
|
|
1595
|
+
this.mainchainClient.tx.crosschainTransfer.setChainConfig("Ethereum", {
|
|
1596
|
+
Evm: {
|
|
1597
|
+
chainId: this.chain.id,
|
|
1598
|
+
gateway: gateway.gatewayAddress,
|
|
1599
|
+
argonToken: gateway.argonTokenAddress,
|
|
1600
|
+
argonotToken: gateway.argonotTokenAddress
|
|
1601
|
+
}
|
|
1602
|
+
})
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
async fundBurnAccount(amount) {
|
|
1606
|
+
const burnAccount = this.mainchainClient.consts.crosschainTransfer.ethereumBurnAccount.toString();
|
|
1607
|
+
return this.forceSetBalance(burnAccount, amount);
|
|
1608
|
+
}
|
|
1609
|
+
async fundProofRelayer(amount = this.mainchainClient.consts.balances.existentialDeposit.toBigInt() + 1000000n) {
|
|
1610
|
+
return this.submit(
|
|
1611
|
+
this.mainchainClient.tx.balances.transferAllowDeath(this.proofRelayer.address, amount),
|
|
1612
|
+
this.sudoSigner
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
async forceSetBalance(address, amount) {
|
|
1616
|
+
return this.sudoSubmit(this.mainchainClient.tx.balances.forceSetBalance(address, amount));
|
|
1617
|
+
}
|
|
1618
|
+
async forceSetOwnership(address, amount) {
|
|
1619
|
+
return this.sudoSubmit(this.mainchainClient.tx.ownership.forceSetBalance(address, amount));
|
|
1620
|
+
}
|
|
1621
|
+
async waitForExecutionFinalizedAfter(minimumExecutionBlockNumber) {
|
|
1622
|
+
const laterReceipt = await mineLaterExecutionAnchorReceipt(
|
|
1623
|
+
this.walletClient,
|
|
1624
|
+
this.chain,
|
|
1625
|
+
this.ethereum,
|
|
1626
|
+
this.deployer,
|
|
1627
|
+
minimumExecutionBlockNumber
|
|
1628
|
+
);
|
|
1629
|
+
await waitForFinalizedBeaconExecutionAtOrAbove(
|
|
1630
|
+
this.ethereum,
|
|
1631
|
+
BigInt(laterReceipt.blockNumber),
|
|
1632
|
+
{
|
|
1633
|
+
// Matches the apps/pr/gateway-proof bootstrap guard for minimal devnets.
|
|
1634
|
+
minimumFinalizedSlot: MINIMAL_BOOTSTRAP_FINALIZED_SLOT
|
|
1635
|
+
}
|
|
1636
|
+
);
|
|
1637
|
+
return laterReceipt;
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
var TestMintingGateway = class _TestMintingGateway {
|
|
1641
|
+
constructor(harness, deployment) {
|
|
1642
|
+
this.harness = harness;
|
|
1643
|
+
this.deployment = deployment;
|
|
1644
|
+
}
|
|
1645
|
+
static async deploy(harness, options) {
|
|
1646
|
+
const deployment = await harness.ethereum.deployMintingGatewayFixture(options);
|
|
1647
|
+
return new _TestMintingGateway(harness, deployment);
|
|
1648
|
+
}
|
|
1649
|
+
get gatewayAddress() {
|
|
1650
|
+
return this.deployment.gatewayAddress;
|
|
1651
|
+
}
|
|
1652
|
+
get argonTokenAddress() {
|
|
1653
|
+
return this.deployment.argonTokenAddress;
|
|
1654
|
+
}
|
|
1655
|
+
get argonotTokenAddress() {
|
|
1656
|
+
return this.deployment.argonotTokenAddress;
|
|
1657
|
+
}
|
|
1658
|
+
async startTransferToArgon(args) {
|
|
1659
|
+
const permitDeadline = (await this.harness.publicClient.getBlock()).timestamp + 3600n;
|
|
1660
|
+
const permitNonce = await this.harness.publicClient.readContract({
|
|
1661
|
+
address: this.argonTokenAddress,
|
|
1662
|
+
abi: argonTokenAbi,
|
|
1663
|
+
functionName: "nonces",
|
|
1664
|
+
args: [args.account.address]
|
|
1665
|
+
});
|
|
1666
|
+
const permitSignature = await signGatewayPermit({
|
|
1667
|
+
account: args.account,
|
|
1668
|
+
chainId: this.harness.chain.id,
|
|
1669
|
+
tokenAddress: this.argonTokenAddress,
|
|
1670
|
+
gatewayAddress: this.gatewayAddress,
|
|
1671
|
+
owner: args.account.address,
|
|
1672
|
+
value: args.amountRuntimeUnits * MINTING_GATEWAY_RUNTIME_TO_ERC20_SCALE,
|
|
1673
|
+
nonce: permitNonce,
|
|
1674
|
+
deadline: permitDeadline
|
|
1675
|
+
});
|
|
1676
|
+
const transactionHash = await createWalletClient3({
|
|
1677
|
+
account: args.account,
|
|
1678
|
+
chain: this.harness.chain,
|
|
1679
|
+
transport: http2(this.harness.endpoints.executionRpcUrl)
|
|
1680
|
+
}).sendTransaction({
|
|
1681
|
+
account: args.account,
|
|
1682
|
+
chain: this.harness.chain,
|
|
1683
|
+
to: this.gatewayAddress,
|
|
1684
|
+
data: encodeFunctionData2({
|
|
1685
|
+
abi: mintingGatewayAbi2,
|
|
1686
|
+
functionName: "startTransferToArgon",
|
|
1687
|
+
args: [
|
|
1688
|
+
this.argonTokenAddress,
|
|
1689
|
+
args.amountRuntimeUnits,
|
|
1690
|
+
args.recipientArgonAddress,
|
|
1691
|
+
permitDeadline,
|
|
1692
|
+
permitSignature.v,
|
|
1693
|
+
permitSignature.r,
|
|
1694
|
+
permitSignature.s
|
|
1695
|
+
]
|
|
1696
|
+
})
|
|
1697
|
+
});
|
|
1698
|
+
return waitForExecutionReceipt2(this.harness.ethereum, transactionHash);
|
|
1699
|
+
}
|
|
1700
|
+
async forceUpdateActiveCouncil(currentCouncil) {
|
|
1701
|
+
return this.harness.publicClient.waitForTransactionReceipt({
|
|
1702
|
+
hash: await this.harness.walletClient.writeContract({
|
|
1703
|
+
account: this.harness.deployer,
|
|
1704
|
+
chain: this.harness.chain,
|
|
1705
|
+
address: this.gatewayAddress,
|
|
1706
|
+
abi: mintingGatewayAbi2,
|
|
1707
|
+
functionName: "forceUpdateActiveCouncil",
|
|
1708
|
+
args: [currentCouncil]
|
|
1709
|
+
})
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
async relayReadyApprovals(batch, operatorAddress) {
|
|
1713
|
+
return this.harness.publicClient.waitForTransactionReceipt({
|
|
1714
|
+
hash: await this.harness.walletClient.writeContract({
|
|
1715
|
+
account: this.harness.deployer,
|
|
1716
|
+
chain: this.harness.chain,
|
|
1717
|
+
address: this.gatewayAddress,
|
|
1718
|
+
abi: mintingGatewayAbi2,
|
|
1719
|
+
functionName: "applyGatewayUpdates",
|
|
1720
|
+
args: [
|
|
1721
|
+
batch.currentCouncil,
|
|
1722
|
+
batch.updates,
|
|
1723
|
+
toHex(decodeAddress(operatorAddress), { size: 32 })
|
|
1724
|
+
]
|
|
1725
|
+
})
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
async argonApprovalsNonce() {
|
|
1729
|
+
return await this.harness.publicClient.readContract({
|
|
1730
|
+
address: this.gatewayAddress,
|
|
1731
|
+
abi: mintingGatewayAbi2,
|
|
1732
|
+
functionName: "argonApprovalsNonce"
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
async globalIssuanceCouncil() {
|
|
1736
|
+
return await this.harness.publicClient.readContract({
|
|
1737
|
+
address: this.gatewayAddress,
|
|
1738
|
+
abi: mintingGatewayAbi2,
|
|
1739
|
+
functionName: "globalIssuanceCouncil"
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
async authorityCollateral(signingKey) {
|
|
1743
|
+
return await this.harness.publicClient.readContract({
|
|
1744
|
+
address: this.gatewayAddress,
|
|
1745
|
+
abi: mintingGatewayAbi2,
|
|
1746
|
+
functionName: "mintingAuthorityCollateralRemaining",
|
|
1747
|
+
args: [signingKey]
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
async finalizeTransferOut(args) {
|
|
1751
|
+
return this.harness.publicClient.waitForTransactionReceipt({
|
|
1752
|
+
hash: await this.harness.walletClient.writeContract({
|
|
1753
|
+
account: this.harness.deployer,
|
|
1754
|
+
chain: this.harness.chain,
|
|
1755
|
+
address: this.gatewayAddress,
|
|
1756
|
+
abi: mintingGatewayAbi2,
|
|
1757
|
+
functionName: "finalizeTransferOutOfArgon",
|
|
1758
|
+
args: [
|
|
1759
|
+
args.transferRequest,
|
|
1760
|
+
{
|
|
1761
|
+
authorizations: [
|
|
1762
|
+
{
|
|
1763
|
+
microgonCollateral: 0n,
|
|
1764
|
+
micronotCollateral: args.micronotCollateral,
|
|
1765
|
+
signature: args.collateralizationSignature
|
|
1766
|
+
}
|
|
1767
|
+
]
|
|
1768
|
+
}
|
|
1769
|
+
]
|
|
1770
|
+
})
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
async isFinalizedTransferOut(transferRequest) {
|
|
1774
|
+
return await this.harness.publicClient.readContract({
|
|
1775
|
+
address: this.gatewayAddress,
|
|
1776
|
+
abi: mintingGatewayAbi2,
|
|
1777
|
+
functionName: "finalizedTransferOutOfArgonIds",
|
|
1778
|
+
args: [EvmContracts3.hashMintingGatewayTransferOutOfArgonRequest(transferRequest)]
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
async argonBalance(address) {
|
|
1782
|
+
return await this.harness.publicClient.readContract({
|
|
1783
|
+
address: this.argonTokenAddress,
|
|
1784
|
+
abi: argonTokenAbi,
|
|
1785
|
+
functionName: "balanceOf",
|
|
1786
|
+
args: [address]
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
async fundExecutionAccount(address, value) {
|
|
1790
|
+
return this.harness.publicClient.waitForTransactionReceipt({
|
|
1791
|
+
hash: await this.harness.walletClient.sendTransaction({
|
|
1792
|
+
account: this.harness.deployer,
|
|
1793
|
+
chain: this.harness.chain,
|
|
1794
|
+
to: address,
|
|
1795
|
+
value
|
|
1796
|
+
})
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
var TestMintingAuthorityActor = class {
|
|
1801
|
+
constructor(harness, args) {
|
|
1802
|
+
this.harness = harness;
|
|
1803
|
+
this.operator = new Keyring3({ type: "sr25519" }).createFromUri(args.operatorUri);
|
|
1804
|
+
this.councilSigner = privateKeyToAccount2(args.councilPrivateKey);
|
|
1805
|
+
this.authoritySigner = privateKeyToAccount2(args.authorityPrivateKey);
|
|
1806
|
+
}
|
|
1807
|
+
operator;
|
|
1808
|
+
councilSigner;
|
|
1809
|
+
authoritySigner;
|
|
1810
|
+
gateway;
|
|
1811
|
+
attachGateway(gateway) {
|
|
1812
|
+
this.gateway = gateway;
|
|
1813
|
+
}
|
|
1814
|
+
async prepareOperator(args) {
|
|
1815
|
+
await this.harness.forceSetBalance(this.operator.address, args.freeBalance);
|
|
1816
|
+
await this.harness.forceSetOwnership(this.operator.address, args.ownershipBalance);
|
|
1817
|
+
const vault = await Vault.create(this.harness.mainchainClient, this.operator, {
|
|
1818
|
+
securitization: 1000000000n,
|
|
1819
|
+
securitizationRatio: 1,
|
|
1820
|
+
annualPercentRate: 0.05,
|
|
1821
|
+
baseFee: 0n,
|
|
1822
|
+
bitcoinXpub: args.bitcoinXpub,
|
|
1823
|
+
treasuryProfitSharing: 0
|
|
1824
|
+
});
|
|
1825
|
+
await vault.getVault();
|
|
1826
|
+
await this.harness.sudoSubmit(
|
|
1827
|
+
this.harness.mainchainClient.tx.priceIndex.setOperator(this.operator.address)
|
|
1828
|
+
);
|
|
1829
|
+
const currentTick = await this.harness.mainchainClient.query.ticks.currentTick();
|
|
1830
|
+
await this.harness.submit(
|
|
1831
|
+
this.harness.mainchainClient.tx.priceIndex.submit({
|
|
1832
|
+
btcUsdPrice: toFixedNumber(6e4, 18),
|
|
1833
|
+
argonotUsdPrice: toFixedNumber(1, 18),
|
|
1834
|
+
argonUsdPrice: toFixedNumber(1, 18),
|
|
1835
|
+
argonUsdTargetPrice: toFixedNumber(1, 18),
|
|
1836
|
+
argonTimeWeightedAverageLiquidity: toFixedNumber(1e6, 18),
|
|
1837
|
+
tick: currentTick.toBigInt()
|
|
1838
|
+
}),
|
|
1839
|
+
this.operator
|
|
1840
|
+
);
|
|
1841
|
+
await this.harness.submit(
|
|
1842
|
+
this.harness.mainchainClient.tx.vaults.setCommittedArgonots(args.committedArgonots),
|
|
1843
|
+
this.operator
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
async registerCouncilSigner() {
|
|
1847
|
+
return this.harness.submit(
|
|
1848
|
+
this.harness.mainchainClient.tx.crosschainTransfer.registerCouncilSigner(
|
|
1849
|
+
"Ethereum",
|
|
1850
|
+
this.councilSigner.address,
|
|
1851
|
+
toArgonKeccakSignature(
|
|
1852
|
+
await this.councilSigner.signMessage({
|
|
1853
|
+
message: { raw: toHex(this.registrationMessage("argon/council-signer/v2")) }
|
|
1854
|
+
})
|
|
1855
|
+
)
|
|
1856
|
+
),
|
|
1857
|
+
this.operator
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
async forceSingleMemberCouncil() {
|
|
1861
|
+
await this.harness.sudoSubmit(
|
|
1862
|
+
this.harness.mainchainClient.tx.crosschainTransfer.forceSetGlobalIssuanceCouncil(
|
|
1863
|
+
"Ethereum",
|
|
1864
|
+
0,
|
|
1865
|
+
[this.operator.address]
|
|
1866
|
+
)
|
|
1867
|
+
);
|
|
1868
|
+
const activeCouncilHashOption = await this.harness.mainchainClient.query.crosschainTransfer.activeGlobalIssuanceCouncilByDestinationChain(
|
|
1869
|
+
"Ethereum"
|
|
1870
|
+
);
|
|
1871
|
+
if (activeCouncilHashOption.isNone) {
|
|
1872
|
+
throw new Error("Expected active Ethereum council hash");
|
|
1873
|
+
}
|
|
1874
|
+
const activeCouncilOption = await this.harness.mainchainClient.query.crosschainTransfer.globalIssuanceCouncilByHash(
|
|
1875
|
+
activeCouncilHashOption.unwrap()
|
|
1876
|
+
);
|
|
1877
|
+
if (activeCouncilOption.isNone) {
|
|
1878
|
+
throw new Error("Expected active Ethereum council");
|
|
1879
|
+
}
|
|
1880
|
+
const activeCouncil = activeCouncilOption.unwrap();
|
|
1881
|
+
const currentCouncil = [...activeCouncil.members.entries()].map(([signer, member]) => ({
|
|
1882
|
+
signer: signer.toHex(),
|
|
1883
|
+
weight: member.weight.toBigInt()
|
|
1884
|
+
})).sort((left, right) => left.signer.localeCompare(right.signer));
|
|
1885
|
+
return {
|
|
1886
|
+
activeCouncilHash: activeCouncilHashOption.unwrap().toHex(),
|
|
1887
|
+
activeCouncil,
|
|
1888
|
+
currentCouncil: {
|
|
1889
|
+
signers: currentCouncil.map((member) => member.signer),
|
|
1890
|
+
weights: currentCouncil.map((member) => member.weight)
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
async setMinimumValue(value) {
|
|
1895
|
+
return this.harness.sudoSubmit(
|
|
1896
|
+
this.harness.mainchainClient.tx.crosschainTransfer.setMinimumMintingAuthorityValue(
|
|
1897
|
+
"Ethereum",
|
|
1898
|
+
value
|
|
1899
|
+
)
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
async registerMintingAuthority(micronotCollateral) {
|
|
1903
|
+
return this.harness.submit(
|
|
1904
|
+
this.harness.mainchainClient.tx.crosschainTransfer.registerMintingAuthority(
|
|
1905
|
+
"Ethereum",
|
|
1906
|
+
this.authoritySigner.address,
|
|
1907
|
+
toArgonKeccakSignature(
|
|
1908
|
+
await this.authoritySigner.signMessage({
|
|
1909
|
+
message: {
|
|
1910
|
+
raw: toHex(this.registrationMessage("argon/minting-authority-signer/v2"))
|
|
1911
|
+
}
|
|
1912
|
+
})
|
|
1913
|
+
),
|
|
1914
|
+
0n,
|
|
1915
|
+
micronotCollateral
|
|
1916
|
+
),
|
|
1917
|
+
this.operator
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
async approveActivationQueueEntry(queueNonce = 1n) {
|
|
1921
|
+
const approvalQueueEntry = await this.harness.mainchainClient.query.crosschainTransfer.councilApprovalQueueByDestinationChainAndNonce(
|
|
1922
|
+
"Ethereum",
|
|
1923
|
+
queueNonce
|
|
1924
|
+
);
|
|
1925
|
+
if (approvalQueueEntry.isNone) {
|
|
1926
|
+
throw new Error(`Expected queue nonce ${queueNonce} to exist`);
|
|
1927
|
+
}
|
|
1928
|
+
const councilApprovalSignature = await this.councilSigner.signMessage({
|
|
1929
|
+
message: {
|
|
1930
|
+
raw: approvalQueueEntry.unwrap().approvalHash.toHex()
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1933
|
+
await this.harness.submit(
|
|
1934
|
+
this.harness.mainchainClient.tx.crosschainTransfer.approveQueueEntries(
|
|
1935
|
+
"Ethereum",
|
|
1936
|
+
new Vec(this.harness.mainchainClient.registry, U8aFixed.with(520), [
|
|
1937
|
+
new U8aFixed(
|
|
1938
|
+
this.harness.mainchainClient.registry,
|
|
1939
|
+
toArgonKeccakSignature(councilApprovalSignature),
|
|
1940
|
+
520
|
|
1941
|
+
)
|
|
1942
|
+
])
|
|
1943
|
+
),
|
|
1944
|
+
this.operator
|
|
1945
|
+
);
|
|
1946
|
+
const batch = await getReadyEthereumGatewayUpdates(
|
|
1947
|
+
this.harness.mainchainClient,
|
|
1948
|
+
this.harness.publicClient
|
|
1949
|
+
);
|
|
1950
|
+
return {
|
|
1951
|
+
approvalQueueEntry,
|
|
1952
|
+
councilApprovalSignature,
|
|
1953
|
+
batch
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
async collateralizeFirstPendingTransferOut() {
|
|
1957
|
+
const gateway = this.requireGateway();
|
|
1958
|
+
const pendingRequests = await this.harness.mainchainClient.query.crosschainTransfer.pendingCollateralizationRequestsByChain(
|
|
1959
|
+
"Ethereum"
|
|
1960
|
+
);
|
|
1961
|
+
if (pendingRequests.length === 0) {
|
|
1962
|
+
throw new Error("Expected a pending collateralization request");
|
|
1963
|
+
}
|
|
1964
|
+
const pendingRequest = pendingRequests[0];
|
|
1965
|
+
const transferId = pendingRequest.transferId.toHex();
|
|
1966
|
+
const transferOption = await this.harness.mainchainClient.query.crosschainTransfer.transferOutById(transferId);
|
|
1967
|
+
if (transferOption.isNone) {
|
|
1968
|
+
throw new Error(`Expected transfer out ${transferId} to exist`);
|
|
1969
|
+
}
|
|
1970
|
+
const transfer = transferOption.unwrap();
|
|
1971
|
+
const transferRequest = {
|
|
1972
|
+
argonAccountId: transfer.argonAccountId.toHex(),
|
|
1973
|
+
argonTransferNonce: transfer.argonTransferNonce.toBigInt(),
|
|
1974
|
+
chainId: BigInt(this.harness.chain.id),
|
|
1975
|
+
councilHash: transfer.councilHash.toHex(),
|
|
1976
|
+
recipient: transfer.destinationAccount.toHex(),
|
|
1977
|
+
validUntilBlock: transfer.validUntilEthereumBlock.toBigInt(),
|
|
1978
|
+
token: gateway.argonTokenAddress,
|
|
1979
|
+
amount: transfer.amount.toBigInt(),
|
|
1980
|
+
mintingAuthorityTip: transfer.mintingAuthorityTip.toBigInt()
|
|
1981
|
+
};
|
|
1982
|
+
const micronotCollateral = transfer.amount.toBigInt();
|
|
1983
|
+
const collateralizationHash = EvmContracts3.hashMintingGatewayMintingAuthorization(
|
|
1984
|
+
{ chainId: BigInt(this.harness.chain.id), gatewayAddress: gateway.gatewayAddress },
|
|
1985
|
+
{
|
|
1986
|
+
request: transferRequest,
|
|
1987
|
+
microgonCollateral: 0n,
|
|
1988
|
+
micronotCollateral
|
|
1989
|
+
}
|
|
1990
|
+
);
|
|
1991
|
+
const collateralizationSignature = await this.authoritySigner.signMessage({
|
|
1992
|
+
message: {
|
|
1993
|
+
raw: collateralizationHash
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
const result = await this.harness.submit(
|
|
1997
|
+
this.harness.mainchainClient.tx.crosschainTransfer.collateralizeTransfer(
|
|
1998
|
+
transferId,
|
|
1999
|
+
toArgonKeccakSignature(collateralizationSignature),
|
|
2000
|
+
0n,
|
|
2001
|
+
micronotCollateral
|
|
2002
|
+
),
|
|
2003
|
+
this.operator
|
|
2004
|
+
);
|
|
2005
|
+
return {
|
|
2006
|
+
pendingRequest,
|
|
2007
|
+
transferId,
|
|
2008
|
+
transferRequest,
|
|
2009
|
+
micronotCollateral,
|
|
2010
|
+
collateralizationSignature,
|
|
2011
|
+
result
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
registrationMessage(prefix) {
|
|
2015
|
+
const prefixBytes = this.harness.mainchainClient.registry.createType("Bytes", prefix).toU8a();
|
|
2016
|
+
const destinationChainBytes = this.harness.mainchainClient.registry.createType("PalletCrosschainTransferSourceChain", "Ethereum").toU8a();
|
|
2017
|
+
const operatorAccountIdBytes = this.harness.mainchainClient.registry.createType("AccountId32", this.operator.address).toU8a();
|
|
2018
|
+
return concatBytes(prefixBytes, destinationChainBytes, operatorAccountIdBytes);
|
|
2019
|
+
}
|
|
2020
|
+
requireGateway() {
|
|
2021
|
+
if (!this.gateway) {
|
|
2022
|
+
throw new Error("Minting authority actor requires an attached TestMintingGateway");
|
|
2023
|
+
}
|
|
2024
|
+
return this.gateway;
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
function concatBytes(...parts) {
|
|
2028
|
+
const totalLength = parts.reduce((sum, part) => sum + part.length, 0);
|
|
2029
|
+
const bytes = new Uint8Array(totalLength);
|
|
2030
|
+
let offset = 0;
|
|
2031
|
+
for (const part of parts) {
|
|
2032
|
+
bytes.set(part, offset);
|
|
2033
|
+
offset += part.length;
|
|
2034
|
+
}
|
|
2035
|
+
return bytes;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
580
2038
|
// src/index.ts
|
|
581
2039
|
var toTeardown = [];
|
|
582
2040
|
var proxy = null;
|
|
@@ -590,7 +2048,7 @@ async function getProxy() {
|
|
|
590
2048
|
autoRewrite: true
|
|
591
2049
|
});
|
|
592
2050
|
proxy.on("error", () => null);
|
|
593
|
-
proxyServer =
|
|
2051
|
+
proxyServer = http3.createServer(function(req, res) {
|
|
594
2052
|
const queryData = url.parse(req.url, true).query;
|
|
595
2053
|
if (!queryData.target) {
|
|
596
2054
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
@@ -640,12 +2098,12 @@ function stringifyExt(obj) {
|
|
|
640
2098
|
}
|
|
641
2099
|
function projectRoot() {
|
|
642
2100
|
if (process4.env.ARGON_PROJECT_ROOT) {
|
|
643
|
-
return
|
|
2101
|
+
return Path7.join(process4.env.ARGON_PROJECT_ROOT);
|
|
644
2102
|
}
|
|
645
|
-
return
|
|
2103
|
+
return Path7.join(__dirname, `../../..`);
|
|
646
2104
|
}
|
|
647
2105
|
async function runTestScript(relativePath) {
|
|
648
|
-
const scriptPath =
|
|
2106
|
+
const scriptPath = Path7.resolve(projectRoot(), relativePath);
|
|
649
2107
|
return child_process4.execSync(scriptPath, { encoding: "utf8" }).trim();
|
|
650
2108
|
}
|
|
651
2109
|
async function getDockerPortMapping(containerName, port2) {
|
|
@@ -681,11 +2139,11 @@ function disconnectOnTeardown(closeable) {
|
|
|
681
2139
|
return closeable;
|
|
682
2140
|
}
|
|
683
2141
|
function sudo() {
|
|
684
|
-
return new
|
|
2142
|
+
return new Keyring4({ type: "sr25519" }).createFromUri("//Alice");
|
|
685
2143
|
}
|
|
686
2144
|
async function activateNotary(sudo2, client, notary) {
|
|
687
2145
|
await notary.register(client);
|
|
688
|
-
const txResult = await new
|
|
2146
|
+
const txResult = await new TxSubmitter4(
|
|
689
2147
|
client,
|
|
690
2148
|
client.tx.sudo.sudo(client.tx.notaries.activate(notary.operator.publicKey)),
|
|
691
2149
|
sudo2
|
|
@@ -693,9 +2151,13 @@ async function activateNotary(sudo2, client, notary) {
|
|
|
693
2151
|
await txResult.waitForInFirstBlock;
|
|
694
2152
|
}
|
|
695
2153
|
export {
|
|
2154
|
+
EthereumProofE2eHarness,
|
|
696
2155
|
SKIP_E2E,
|
|
697
2156
|
TestBitcoinCli,
|
|
2157
|
+
TestEthereum,
|
|
698
2158
|
TestMainchain,
|
|
2159
|
+
TestMintingAuthorityActor,
|
|
2160
|
+
TestMintingGateway,
|
|
699
2161
|
TestNotary,
|
|
700
2162
|
TestOracle,
|
|
701
2163
|
activateNotary,
|
|
@@ -705,12 +2167,20 @@ export {
|
|
|
705
2167
|
disconnectOnTeardown,
|
|
706
2168
|
getDockerPortMapping,
|
|
707
2169
|
getProxy,
|
|
2170
|
+
getReadyEthereumGatewayUpdates,
|
|
2171
|
+
mineLaterExecutionAnchorReceipt,
|
|
708
2172
|
projectRoot,
|
|
709
2173
|
runOnTeardown,
|
|
710
2174
|
runTestScript,
|
|
2175
|
+
signGatewayPermit,
|
|
711
2176
|
startNetwork,
|
|
712
2177
|
stringifyExt,
|
|
713
2178
|
sudo,
|
|
714
|
-
|
|
2179
|
+
syncEthereumVerifierUntilAnchorCovers,
|
|
2180
|
+
teardown,
|
|
2181
|
+
toArgonKeccakSignature,
|
|
2182
|
+
toEvmRecoverableSignature,
|
|
2183
|
+
waitForExecutionReceipt2 as waitForExecutionReceipt,
|
|
2184
|
+
waitForFinalizedBeaconExecutionAtOrAbove
|
|
715
2185
|
};
|
|
716
2186
|
//# sourceMappingURL=index.js.map
|