diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol new file mode 100644 index 0000000..3ce198a --- /dev/null +++ b/contracts/ERC20.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ERC20 { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint256); +} diff --git a/deno.json b/deno.json index d4bd82a..6a37677 100644 --- a/deno.json +++ b/deno.json @@ -16,7 +16,9 @@ "test:pvm:manual": "deno task build && USE_BYTECODE=pvm deno test -P", "test:geth": "deno task build --solcOnly && USE_BYTECODE=evm START_GETH=true deno test -P", "test:update": "deno task build --solcOnly && USE_BYTECODE=evm START_GETH=true deno test -P -- --update", - "lint": "deno fmt --check && deno lint" + "test:polkadot": "USE_BYTECODE=evm START_ASSET_HUB_WESTEND=true START_ETH_RPC=true deno test -P src/polkadot-tests.ts", + "test:polkadot:manual": "USE_BYTECODE=evm deno test -P src/polkadot-tests.ts", + "lint": "deno fmt --check && deno task build && deno check && deno lint" }, "test": { "include": ["src/all-tests.ts"] @@ -32,7 +34,8 @@ "@std/expect": "jsr:@std/expect@^1.0.17", "@std/testing": "jsr:@std/testing@^1.0.16", "@std/path": "jsr:@std/path@^1.1.2", - "@std/log": "jsr:@std/log@^0.224.14" + "@std/log": "jsr:@std/log@^0.224.14", + "@polkadot/api": "npm:@polkadot/api@^14.0.1" }, "compilerOptions": { "strict": true diff --git a/deno.lock b/deno.lock index 5eb4757..93b3798 100644 --- a/deno.lock +++ b/deno.lock @@ -18,6 +18,7 @@ "jsr:@std/path@^1.1.2": "1.1.2", "jsr:@std/testing@^1.0.16": "1.0.16", "npm:@parity/resolc@0.5": "0.5.0", + "npm:@polkadot/api@^14.0.1": "14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", "npm:@types/node@*": "24.2.0", "npm:solc@~0.8.30": "0.8.30", "npm:viem@^2.39.0": "2.39.0_ws@8.18.3" @@ -132,6 +133,388 @@ "config-chain" ] }, + "@polkadot-api/json-rpc-provider-proxy@0.1.0": { + "integrity": "sha512-8GSFE5+EF73MCuLQm8tjrbCqlgclcHBSRaswvXziJ0ZW7iw3UEMsKkkKvELayWyBuOPa2T5i1nj6gFOeIsqvrg==" + }, + "@polkadot-api/json-rpc-provider@0.0.1": { + "integrity": "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA==" + }, + "@polkadot-api/metadata-builders@0.3.2": { + "integrity": "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg==", + "dependencies": [ + "@polkadot-api/substrate-bindings", + "@polkadot-api/utils" + ] + }, + "@polkadot-api/observable-client@0.3.2_@polkadot-api+substrate-client@0.1.4_rxjs@7.8.2": { + "integrity": "sha512-HGgqWgEutVyOBXoGOPp4+IAq6CNdK/3MfQJmhCJb8YaJiaK4W6aRGrdQuQSTPHfERHCARt9BrOmEvTXAT257Ug==", + "dependencies": [ + "@polkadot-api/metadata-builders", + "@polkadot-api/substrate-bindings", + "@polkadot-api/substrate-client", + "@polkadot-api/utils", + "rxjs" + ] + }, + "@polkadot-api/substrate-bindings@0.6.0": { + "integrity": "sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw==", + "dependencies": [ + "@noble/hashes", + "@polkadot-api/utils", + "@scure/base", + "scale-ts" + ] + }, + "@polkadot-api/substrate-client@0.1.4": { + "integrity": "sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A==", + "dependencies": [ + "@polkadot-api/json-rpc-provider", + "@polkadot-api/utils" + ] + }, + "@polkadot-api/utils@0.1.0": { + "integrity": "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA==" + }, + "@polkadot/api-augment@14.3.1": { + "integrity": "sha512-PE6DW+8kRhbnGKn7qCF7yM6eEt/kqrY8bh1i0RZcPY9QgwXW4bZZrtMK4WssX6Z70NTEoOW6xHYIjc7gFZuz8g==", + "dependencies": [ + "@polkadot/api-base", + "@polkadot/rpc-augment", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-augment@14.3.1", + "@polkadot/types-codec", + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/api-base@14.3.1": { + "integrity": "sha512-GZT6rTpT3HYZ/C3rLPjoX3rX3DOxNG/zgts+jKjNrCumAeZkVq5JErKIX8/3f2TVaE2Kbqniy3d1TH/AL4HBPA==", + "dependencies": [ + "@polkadot/rpc-core", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/util", + "rxjs", + "tslib" + ] + }, + "@polkadot/api-derive@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9": { + "integrity": "sha512-PhqUEJCY54vXtIaoYqGUtJY06wHd/K0cBmBz9yCLxp8UZkLoGWhfJRTruI25Jnucf9awS5cZKYqbsoDrL09Oqg==", + "dependencies": [ + "@polkadot/api", + "@polkadot/api-augment", + "@polkadot/api-base", + "@polkadot/rpc-core", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-codec", + "@polkadot/util", + "@polkadot/util-crypto@13.5.9_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9", + "rxjs", + "tslib" + ] + }, + "@polkadot/api@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9": { + "integrity": "sha512-ZBKSXEVJa1S1bnmpnA7KT/fX3sJDIJOdVD9Hp3X+G73yvXzuK5k1Mn5z9bD/AcMs/HAGcbuYU+b9+b9IByH9YQ==", + "dependencies": [ + "@polkadot/api-augment", + "@polkadot/api-base", + "@polkadot/api-derive", + "@polkadot/keyring@13.5.9_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/rpc-augment", + "@polkadot/rpc-core", + "@polkadot/rpc-provider", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-augment@14.3.1", + "@polkadot/types-codec", + "@polkadot/types-create", + "@polkadot/types-known", + "@polkadot/util", + "@polkadot/util-crypto@13.5.9_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9", + "eventemitter3", + "rxjs", + "tslib" + ] + }, + "@polkadot/keyring@13.5.9": { + "integrity": "sha512-bMCpHDN7U8ytxawjBZ89/he5s3AmEZuOdkM/ABcorh/flXNPfyghjFK27Gy4OKoFxX52yJ2sTHR4NxM87GuFXQ==" + }, + "@polkadot/keyring@13.5.9_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9": { + "integrity": "sha512-bMCpHDN7U8ytxawjBZ89/he5s3AmEZuOdkM/ABcorh/flXNPfyghjFK27Gy4OKoFxX52yJ2sTHR4NxM87GuFXQ==", + "dependencies": [ + "@polkadot/util", + "@polkadot/util-crypto@13.5.9_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9", + "tslib" + ] + }, + "@polkadot/networks@13.5.9": { + "integrity": "sha512-nmKUKJjiLgcih0MkdlJNMnhEYdwEml2rv/h59ll2+rAvpsVWMTLCb6Cq6q7UC44+8kiWK2UUJMkFU+3PFFxndA==", + "dependencies": [ + "@polkadot/util", + "@substrate/ss58-registry", + "tslib" + ] + }, + "@polkadot/rpc-augment@14.3.1": { + "integrity": "sha512-Z8Hp8fFHwFCiTX0bBCDqCZ4U26wLIJl1NRSjJTsAr+SS68pYZBDGCwhKztpKGqndk1W1akRUaxrkGqYdIFmspQ==", + "dependencies": [ + "@polkadot/rpc-core", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-codec", + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/rpc-core@14.3.1": { + "integrity": "sha512-FV2NPhFwFxmX8LqibDcGc6IKTBqmvwr7xwF2OA60Br4cX+AQzMSVpFlfQcETll+0M+LnRhqGKGkP0EQWXaSowA==", + "dependencies": [ + "@polkadot/rpc-augment", + "@polkadot/rpc-provider", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/util", + "rxjs", + "tslib" + ] + }, + "@polkadot/rpc-provider@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9": { + "integrity": "sha512-NF/Z/7lzT+jp5LZzC49g+YIjRzXVI0hFag3+B+4zh6E/kKADdF59EHj2Im4LDhRGOnEO9AE4H6/UjNEbZ94JtA==", + "dependencies": [ + "@polkadot/keyring@13.5.9_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-support", + "@polkadot/util", + "@polkadot/util-crypto@13.5.9_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9", + "@polkadot/x-fetch", + "@polkadot/x-global", + "@polkadot/x-ws", + "eventemitter3", + "mock-socket", + "nock", + "tslib" + ], + "optionalDependencies": [ + "@substrate/connect" + ] + }, + "@polkadot/types-augment@14.3.1": { + "integrity": "sha512-SC4M6TBlgCglNz+gRbvfoVRDz0Vyeev6v0HeAdw0H6ayEW4BXUdo5bFr0092bdS5uTrEPgiSyUry5TJs2KoXig==", + "dependencies": [ + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-codec", + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/types-augment@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9": { + "integrity": "sha512-SC4M6TBlgCglNz+gRbvfoVRDz0Vyeev6v0HeAdw0H6ayEW4BXUdo5bFr0092bdS5uTrEPgiSyUry5TJs2KoXig==", + "dependencies": [ + "@polkadot/types@14.3.1", + "@polkadot/types-codec", + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/types-codec@14.3.1": { + "integrity": "sha512-3y3RBGd+8ebscGbNUOjqUjnRE7hgicgid5LtofHK3O1EDcJQJnYBDkJ7fOAi96CDgHsg+f2FWWkBWEPgpOQoMQ==", + "dependencies": [ + "@polkadot/util", + "@polkadot/x-bigint", + "tslib" + ] + }, + "@polkadot/types-create@14.3.1": { + "integrity": "sha512-F4EBvF3Zvym0xrkAA5Yz01IAVMepMV3w2Dwd0C9IygEAQ5sYLLPHmf72/aXn+Ag+bSyT2wlJHpDc+nEBXNQ3Gw==", + "dependencies": [ + "@polkadot/types-codec", + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/types-known@14.3.1": { + "integrity": "sha512-58b3Yc7+sxwNjs8axmrA9OCgnxmEKIq7XCH2VxSgLqTeqbohVtxwUSCW/l8NPrq1nxzj4J2sopu0PPg8/++q4g==", + "dependencies": [ + "@polkadot/networks", + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-codec", + "@polkadot/types-create", + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/types-support@14.3.1": { + "integrity": "sha512-MfVe4iIOJIfBr+gj8Lu8gwIvhnO6gDbG5LeaKAjY6vS6Oh0y5Ztr8NdMIl8ccSpoyt3LqIXjfApeGzHiLzr6bw==", + "dependencies": [ + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/types@14.3.1": { + "integrity": "sha512-O748XgCLDQYxS5nQ6TJSqW88oC4QNIoNVlWZC2Qq4SmEXuSzaNHQwSVtdyPRJCCc4Oi1DCQvGui4O+EukUl7HA==", + "dependencies": [ + "@polkadot/keyring@13.5.9", + "@polkadot/types-augment@14.3.1", + "@polkadot/types-codec", + "@polkadot/types-create", + "@polkadot/util", + "@polkadot/util-crypto@13.5.9", + "rxjs", + "tslib" + ] + }, + "@polkadot/types@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9": { + "integrity": "sha512-O748XgCLDQYxS5nQ6TJSqW88oC4QNIoNVlWZC2Qq4SmEXuSzaNHQwSVtdyPRJCCc4Oi1DCQvGui4O+EukUl7HA==", + "dependencies": [ + "@polkadot/keyring@13.5.9_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-augment@14.3.1_@polkadot+util@13.5.9_@polkadot+util-crypto@13.5.9__@polkadot+util@13.5.9__@polkadot+x-randomvalues@13.5.9___@polkadot+util@13.5.9___@polkadot+wasm-util@7.5.4____@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9", + "@polkadot/types-codec", + "@polkadot/types-create", + "@polkadot/util", + "@polkadot/util-crypto@13.5.9_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9", + "rxjs", + "tslib" + ] + }, + "@polkadot/util-crypto@13.5.9": { + "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", + "dependencies": [ + "@noble/curves", + "@noble/hashes", + "@polkadot/networks" + ] + }, + "@polkadot/util-crypto@13.5.9_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9": { + "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", + "dependencies": [ + "@noble/curves", + "@noble/hashes", + "@polkadot/networks", + "@polkadot/util", + "@polkadot/wasm-crypto", + "@polkadot/wasm-util", + "@polkadot/x-bigint", + "@polkadot/x-randomvalues", + "@scure/base", + "tslib" + ] + }, + "@polkadot/util@13.5.9": { + "integrity": "sha512-pIK3XYXo7DKeFRkEBNYhf3GbCHg6dKQisSvdzZwuyzA6m7YxQq4DFw4IE464ve4Z7WsJFt3a6C9uII36hl9EWw==", + "dependencies": [ + "@polkadot/x-bigint", + "@polkadot/x-global", + "@polkadot/x-textdecoder", + "@polkadot/x-textencoder", + "@types/bn.js", + "bn.js", + "tslib" + ] + }, + "@polkadot/wasm-bridge@7.5.4_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9": { + "integrity": "sha512-6xaJVvoZbnbgpQYXNw9OHVNWjXmtcoPcWh7hlwx3NpfiLkkjljj99YS+XGZQlq7ks2fVCg7FbfknkNb8PldDaA==", + "dependencies": [ + "@polkadot/util", + "@polkadot/wasm-util", + "@polkadot/x-randomvalues", + "tslib" + ] + }, + "@polkadot/wasm-crypto-asmjs@7.5.4_@polkadot+util@13.5.9": { + "integrity": "sha512-ZYwxQHAJ8pPt6kYk9XFmyuFuSS+yirJLonvP+DYbxOrARRUHfN4nzp4zcZNXUuaFhpbDobDSFn6gYzye6BUotA==", + "dependencies": [ + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/wasm-crypto-init@7.5.4_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9": { + "integrity": "sha512-U6s4Eo2rHs2n1iR01vTz/sOQ7eOnRPjaCsGWhPV+ZC/20hkVzwPAhiizu/IqMEol4tO2yiSheD4D6bn0KxUJhg==", + "dependencies": [ + "@polkadot/util", + "@polkadot/wasm-bridge", + "@polkadot/wasm-crypto-asmjs", + "@polkadot/wasm-crypto-wasm", + "@polkadot/wasm-util", + "@polkadot/x-randomvalues", + "tslib" + ] + }, + "@polkadot/wasm-crypto-wasm@7.5.4_@polkadot+util@13.5.9": { + "integrity": "sha512-PsHgLsVTu43eprwSvUGnxybtOEuHPES6AbApcs7y5ZbM2PiDMzYbAjNul098xJK/CPtrxZ0ePDFnaQBmIJyTFw==", + "dependencies": [ + "@polkadot/util", + "@polkadot/wasm-util", + "tslib" + ] + }, + "@polkadot/wasm-crypto@7.5.4_@polkadot+util@13.5.9_@polkadot+x-randomvalues@13.5.9__@polkadot+util@13.5.9__@polkadot+wasm-util@7.5.4___@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9": { + "integrity": "sha512-1seyClxa7Jd7kQjfnCzTTTfYhTa/KUTDUaD3DMHBk5Q4ZUN1D1unJgX+v1aUeXSPxmzocdZETPJJRZjhVOqg9g==", + "dependencies": [ + "@polkadot/util", + "@polkadot/wasm-bridge", + "@polkadot/wasm-crypto-asmjs", + "@polkadot/wasm-crypto-init", + "@polkadot/wasm-crypto-wasm", + "@polkadot/wasm-util", + "@polkadot/x-randomvalues", + "tslib" + ] + }, + "@polkadot/wasm-util@7.5.4_@polkadot+util@13.5.9": { + "integrity": "sha512-hqPpfhCpRAqCIn/CYbBluhh0TXmwkJnDRjxrU9Bnqtw9nMNa97D8JuOjdd2pi0rxm+eeLQ/f1rQMp71RMM9t4w==", + "dependencies": [ + "@polkadot/util", + "tslib" + ] + }, + "@polkadot/x-bigint@13.5.9": { + "integrity": "sha512-JVW6vw3e8fkcRyN9eoc6JIl63MRxNQCP/tuLdHWZts1tcAYao0hpWUzteqJY93AgvmQ91KPsC1Kf3iuuZCi74g==", + "dependencies": [ + "@polkadot/x-global", + "tslib" + ] + }, + "@polkadot/x-fetch@13.5.9": { + "integrity": "sha512-urwXQZtT4yYROiRdJS6zHu18J/jCoAGpbgPIAjwdqjT11t9XIq4SjuPMxD19xBRhbYe9ocWV8i1KHuoMbZgKbA==", + "dependencies": [ + "@polkadot/x-global", + "node-fetch", + "tslib" + ] + }, + "@polkadot/x-global@13.5.9": { + "integrity": "sha512-zSRWvELHd3Q+bFkkI1h2cWIqLo1ETm+MxkNXLec3lB56iyq/MjWBxfXnAFFYFayvlEVneo7CLHcp+YTFd9aVSA==", + "dependencies": [ + "tslib" + ] + }, + "@polkadot/x-randomvalues@13.5.9_@polkadot+util@13.5.9_@polkadot+wasm-util@7.5.4__@polkadot+util@13.5.9": { + "integrity": "sha512-Uuuz3oubf1JCCK97fsnVUnHvk4BGp/W91mQWJlgl5TIOUSSTIRr+lb5GurCfl4kgnQq53Zi5fJV+qR9YumbnZw==", + "dependencies": [ + "@polkadot/util", + "@polkadot/wasm-util", + "@polkadot/x-global", + "tslib" + ] + }, + "@polkadot/x-textdecoder@13.5.9": { + "integrity": "sha512-W2HhVNUbC/tuFdzNMbnXAWsIHSg9SC9QWDNmFD3nXdSzlXNgL8NmuiwN2fkYvCQBtp/XSoy0gDLx0C+Fo19cfw==", + "dependencies": [ + "@polkadot/x-global", + "tslib" + ] + }, + "@polkadot/x-textencoder@13.5.9": { + "integrity": "sha512-SG0MHnLUgn1ZxFdm0KzMdTHJ47SfqFhdIPMcGA0Mg/jt2rwrfrP3jtEIJMsHfQpHvfsNPfv55XOMmoPWuQnP/Q==", + "dependencies": [ + "@polkadot/x-global", + "tslib" + ] + }, + "@polkadot/x-ws@13.5.9": { + "integrity": "sha512-NKVgvACTIvKT8CjaQu9d0dERkZsWIZngX/4NVSjc01WHmln4F4y/zyBdYn/Z2V0Zw28cISx+lB4qxRmqTe7gbg==", + "dependencies": [ + "@polkadot/x-global", + "tslib", + "ws" + ] + }, "@scure/base@1.2.6": { "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==" }, @@ -150,6 +533,44 @@ "@scure/base" ] }, + "@substrate/connect-extension-protocol@2.2.2": { + "integrity": "sha512-t66jwrXA0s5Goq82ZtjagLNd7DPGCNjHeehRlE/gcJmJ+G56C0W+2plqOMRicJ8XGR1/YFnUSEqUFiSNbjGrAA==" + }, + "@substrate/connect-known-chains@1.10.3": { + "integrity": "sha512-OJEZO1Pagtb6bNE3wCikc2wrmvEU5x7GxFFLqqbz1AJYYxSlrPCGu4N2og5YTExo4IcloNMQYFRkBGue0BKZ4w==" + }, + "@substrate/connect@0.8.11_smoldot@2.0.26": { + "integrity": "sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw==", + "dependencies": [ + "@substrate/connect-extension-protocol", + "@substrate/connect-known-chains", + "@substrate/light-client-extension-helpers", + "smoldot" + ], + "deprecated": true + }, + "@substrate/light-client-extension-helpers@1.0.0_smoldot@2.0.26_@polkadot-api+substrate-client@0.1.4_rxjs@7.8.2": { + "integrity": "sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg==", + "dependencies": [ + "@polkadot-api/json-rpc-provider", + "@polkadot-api/json-rpc-provider-proxy", + "@polkadot-api/observable-client", + "@polkadot-api/substrate-client", + "@substrate/connect-extension-protocol", + "@substrate/connect-known-chains", + "rxjs", + "smoldot" + ] + }, + "@substrate/ss58-registry@1.51.0": { + "integrity": "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==" + }, + "@types/bn.js@5.2.0": { + "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", + "dependencies": [ + "@types/node@24.2.0" + ] + }, "@types/node@22.19.1": { "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "dependencies": [ @@ -165,6 +586,9 @@ "abitype@1.1.0": { "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==" }, + "bn.js@5.2.3": { + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==" + }, "command-exists@1.2.9": { "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, @@ -181,15 +605,37 @@ "proto-list" ] }, + "data-uri-to-buffer@4.0.1": { + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, "deep-extend@0.6.0": { "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "eventemitter3@5.0.1": { "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, + "fetch-blob@3.2.0": { + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill" + ] + }, "follow-redirects@1.15.11": { "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==" }, + "formdata-polyfill@4.0.10": { + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": [ + "fetch-blob" + ] + }, "graceful-fs@4.2.10": { "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, @@ -205,6 +651,9 @@ "js-sha3@0.8.0": { "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, + "json-stringify-safe@5.0.1": { + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, "ky@1.14.0": { "integrity": "sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==" }, @@ -214,6 +663,32 @@ "minimist@1.2.8": { "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, + "mock-socket@9.3.1": { + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nock@13.5.6": { + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", + "dependencies": [ + "debug", + "json-stringify-safe", + "propagate" + ] + }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": true + }, + "node-fetch@3.3.2": { + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": [ + "data-uri-to-buffer", + "fetch-blob", + "formdata-polyfill" + ] + }, "os-tmpdir@1.0.2": { "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" }, @@ -239,6 +714,9 @@ "semver@7.7.3" ] }, + "propagate@2.0.1": { + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==" + }, "proto-list@1.2.4": { "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, @@ -273,6 +751,15 @@ "resolve-from" ] }, + "rxjs@7.8.2": { + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": [ + "tslib" + ] + }, + "scale-ts@1.6.1": { + "integrity": "sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==" + }, "semver@5.7.2": { "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "bin": true @@ -281,6 +768,12 @@ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "bin": true }, + "smoldot@2.0.26": { + "integrity": "sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig==", + "dependencies": [ + "ws" + ] + }, "solc@0.8.30": { "integrity": "sha512-9Srk/gndtBmoUbg4CE6ypAzPQlElv8ntbnl6SigUBAzgXKn35v87sj04uZeoZWjtDkdzT0qKFcIo/wl63UMxdw==", "dependencies": [ @@ -303,6 +796,9 @@ "os-tmpdir" ] }, + "tslib@2.8.1": { + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "undici-types@6.21.0": { "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, @@ -322,6 +818,9 @@ "ws" ] }, + "web-streams-polyfill@3.3.3": { + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, "ws@8.18.3": { "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==" } @@ -336,6 +835,7 @@ "jsr:@std/path@^1.1.2", "jsr:@std/testing@^1.0.16", "npm:@parity/resolc@0.5", + "npm:@polkadot/api@^14.0.1", "npm:solc@~0.8.30", "npm:viem@^2.39.0" ] diff --git a/src/foreign-assets.test.ts b/src/foreign-assets.test.ts new file mode 100644 index 0000000..bb0390e --- /dev/null +++ b/src/foreign-assets.test.ts @@ -0,0 +1,195 @@ +/// Foreign asset ERC20 precompile integration test. +/// +/// Polkadot-specific: verifies that a foreign asset created via Substrate +/// extrinsics is reachable as an ERC20 precompile through eth-rpc. +/// +/// Requires the asset-hub-westend node (not revive-dev-node) and does NOT +/// run against geth. + +import { + ApiPromise, + Keyring, + SubmittableResult, + WsProvider, +} from '@polkadot/api' +import type { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types' +import { createPublicClient, type Hex, http } from 'viem' +import { expect } from '@std/expect' +import { ERC20Abi } from '../codegen/abi/ERC20.ts' +import { sanitizeOpts as opts } from './util.ts' + +interface ScaleU32 { + toNumber(): number +} +interface ScaleOption { + isSome: boolean + unwrap(): T +} + +const SUBSTRATE_WS = `ws://localhost:${ + Deno.env.get('SUBSTRATE_RPC_PORT') ?? '9944' +}` + +function assetLocation(parachainId: number) { + return { parents: 1, interior: { x1: [{ parachain: parachainId }] } } +} + +/// Connect to the substrate node and run a callback with the API and Alice signer. +function withApi( + fn: ( + api: ApiPromise, + alice: ReturnType, + ) => Promise, +): () => Promise { + return async () => { + const provider = new WsProvider(SUBSTRATE_WS) + const api = await ApiPromise.create({ provider }) + const keyring = new Keyring({ type: 'sr25519' }) + const alice = keyring.addFromUri('//Alice') + try { + await fn(api, alice) + } finally { + await api.disconnect() + } + } +} + +/// Submit a signed extrinsic and wait for inclusion. +function submitAndWait( + api: ApiPromise, + tx: SubmittableExtrinsic<'promise'>, + signer: AddressOrPair, +): Promise { + return new Promise((resolve, reject) => { + let unsub: (() => void) | undefined + tx.signAndSend( + signer, + ({ status, dispatchError }: SubmittableResult) => { + if (status.isInBlock || status.isFinalized) { + unsub?.() + if (dispatchError) { + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule, + ) + reject( + new Error( + `${decoded.section}.${decoded.name}: ${ + decoded.docs.join(' ') + }`, + ), + ) + } else { + reject(new Error(dispatchError.toString())) + } + } else { + resolve() + } + } + }, + ).then((u) => { + unsub = u + }) + }) +} + +type Signer = ReturnType + +/// Create a foreign asset via sudo (ForceOrigin required). +async function createForeignAsset( + api: ApiPromise, + location: ReturnType, + signer: Signer, +): Promise { + await submitAndWait( + api, + api.tx.sudo.sudo( + api.tx.foreignAssets.forceCreate( + location, + signer.address, + false, + 1, + ), + ), + signer, + ) +} + +async function getForeignIdToAssetIndex( + api: ApiPromise, + location: Record, +): Promise { + const val = await api.query.assetsPrecompiles.foreignAssetIdToAssetIndex( + location, + ) + const opt = val as unknown as ScaleOption + return opt.isSome ? opt.unwrap().toNumber() : null +} + +// --------------------------------------------------------------------------- +// ERC20 precompile works for foreign asset via eth-rpc +// --------------------------------------------------------------------------- + +Deno.test( + 'polkadot-tests: ERC20 precompile works for foreign asset', + opts, + withApi(async (api, alice) => { + const loc500 = assetLocation(50000) + + // Create foreign asset with metadata and mint tokens. + await createForeignAsset(api, loc500, alice) + await submitAndWait( + api, + api.tx.foreignAssets.setMetadata( + loc500, + 'Test Token', + 'TST', + 18, + ), + alice, + ) + await submitAndWait( + api, + api.tx.foreignAssets.mint( + loc500, + alice.address, + '1000000000000000000000', + ), + alice, + ) + + const index = await getForeignIdToAssetIndex(api, loc500) + expect(index).not.toBeNull() + + // Foreign assets use ForeignIdConfig<0x220>. + // The precompile address encodes the sequential index, not the Location. + // Layout: prefix 0x0220 at bytes 16-17, index at bytes 0-3. + const idxHex = index!.toString(16).padStart(8, '0') + const precompileAddr = + `0x${idxHex}00000000000000000000000002200000` as Hex + + const rpcPort = Deno.env.get('RPC_PORT') ?? '8545' + const publicClient = createPublicClient({ + transport: http(`http://localhost:${rpcPort}`), + }) + const read = ( + functionName: 'name' | 'symbol' | 'decimals' | 'totalSupply', + ) => publicClient.readContract({ + address: precompileAddr, + abi: ERC20Abi, + functionName, + }) + + const [name, symbol, decimals, totalSupply] = await Promise.all([ + read('name'), + read('symbol'), + read('decimals'), + read('totalSupply'), + ]) + + expect(name).toEqual('Test Token') + expect(symbol).toEqual('TST') + expect(Number(decimals)).toEqual(18) + expect((totalSupply as bigint) > 0n).toBe(true) + }), +) diff --git a/src/polkadot-tests.ts b/src/polkadot-tests.ts new file mode 100644 index 0000000..5e73ad8 --- /dev/null +++ b/src/polkadot-tests.ts @@ -0,0 +1,14 @@ +// Entry point for Polkadot-specific tests. +// These tests require the asset-hub-westend node (not revive-dev-node) and do NOT +// compare results against geth. +// +// Run with: deno task test:polkadot + +import { cleanupTests, setupTests } from './test-setup.ts' + +await setupTests() +await import('./foreign-assets.test.ts') + +globalThis.addEventListener('unload', () => { + cleanupTests() +}) diff --git a/src/test-setup.ts b/src/test-setup.ts index 803ff00..e2d80e3 100644 --- a/src/test-setup.ts +++ b/src/test-setup.ts @@ -71,7 +71,34 @@ export async function setupTests() { } } - if (Deno.env.get('START_REVIVE_DEV_NODE')) { + if (Deno.env.get('START_ASSET_HUB_WESTEND')) { + const sdkDir = Deno.env.get('POLKADOT_SDK_DIR') ?? + `${Deno.env.get('HOME')}/polkadot-sdk` + const omniNode = Deno.env.get('OMNI_NODE_PATH') ?? + `${sdkDir}/target/release/polkadot-omni-node` + const chainSpec = await buildAssetHubWestendSpec(sdkDir, omniNode) + + const nodeArgs = [ + '--dev', + `--chain=${chainSpec}`, + '--dev-block-time=3000', + '--tmp', + '--rpc-port=9944', + '--rpc-cors=all', + '--no-prometheus', + '-l=error,sc_rpc_server=info,runtime::revive=debug', + ] + + await killProcessOnPort(9944) + console.log('🚀 Start asset-hub-westend node ...') + const nodeProcess = new Deno.Command(omniNode, { + args: nodeArgs, + stdout: 'null', + stderr: 'inherit', + }).spawn() + processes.push(nodeProcess) + await waitForHealth('http://localhost:9944') + } else if (Deno.env.get('START_REVIVE_DEV_NODE')) { const devNodeArgs = [ '--dev', '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', @@ -134,3 +161,102 @@ export function cleanupTests() { } processes = [] } + +// --------------------------------------------------------------------------- +// Asset-hub-westend chain spec generation +// --------------------------------------------------------------------------- +// Parachain runtimes need two genesis storage keys injected so the scheduler +// doesn't crawl from relay block 1 to ~295M on every block. +// See: https://github.com/paritytech/node-env/blob/main/lib/chain_spec.ts + +// Pre-computed twox128 storage keys (static, never change): +// twox128("Scheduler") ++ twox128("IncompleteSince") +const SCHEDULER_INCOMPLETE_SINCE = + '0x3db7a24cfdc9de785974746c14a99df9f7be9b0bf16f84e559a58101c891d523' +// twox128("ParachainSystem") ++ twox128("LastRelayChainBlockNumber") +// TODO: no longer necessary if this issue gets fixed: https://github.com/paritytech/polkadot-sdk/pull/10807#issuecomment-3966008358 +const PARACHAIN_LAST_RELAY_BLOCK = + '0x45323df7cc47150b3930e2666b0aa313a2bca190d36bd834cc73a38fc213ecbd' + +function u32ToLeHex(n: number): string { + const buf = new Uint8Array(4) + new DataView(buf.buffer).setUint32(0, n, true) + return '0x' + [...buf].map((b) => b.toString(16).padStart(2, '0')).join('') +} + +async function runCommand( + cmd: string[], +): Promise { + const proc = new Deno.Command(cmd[0], { + args: cmd.slice(1), + stdout: 'piped', + stderr: 'piped', + }) + const output = await proc.output() + if (!output.success) { + const stderr = new TextDecoder().decode(output.stderr) + throw new Error(`Command failed: ${cmd.join(' ')}\n${stderr}`) + } + return new TextDecoder().decode(output.stdout) +} + +async function buildAssetHubWestendSpec( + sdkDir: string, + omniNode: string, +): Promise { + const runtime = + `${sdkDir}/target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm` + const basePath = '/tmp/ah-westend-base.json' + const rawPath = '/tmp/ah-westend-raw.json' + + // Step 1: Generate base chain spec (writes to file via --chain-spec-path) + console.log('📋 Generating asset-hub-westend chain spec ...') + await runCommand([ + omniNode, + 'chain-spec-builder', + '--chain-spec-path', + basePath, + 'create', + '--relay-chain', + 'dontcare', + '--para-id', + '1000', + '--runtime', + runtime, + 'named-preset', + 'development', + ]) + + // Step 1b: Patch the base spec to set Alice as sudo key + const baseSpec = JSON.parse(await Deno.readTextFile(basePath)) + const ALICE_SS58 = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY' + baseSpec.genesis ??= {} + baseSpec.genesis.runtimeGenesis ??= {} + baseSpec.genesis.runtimeGenesis.patch ??= {} + baseSpec.genesis.runtimeGenesis.patch.sudo = { key: ALICE_SS58 } + await Deno.writeTextFile(basePath, JSON.stringify(baseSpec, null, 2)) + + // Step 2: Convert to raw format + const rawSpec = await runCommand([ + omniNode, + 'build-spec', + '--raw', + '--chain', + basePath, + ]) + + // Step 3: Inject scheduler keys so the parachain doesn't stall + const spec = JSON.parse(rawSpec) + const RELAY_BLOCK_TIME_MS = 6000 + const SAFETY_OFFSET_MS = 2 * 3600_000 // Start 2h behind "now" to avoid future-block issues + const relayBlock = Math.floor( + (Date.now() - SAFETY_OFFSET_MS) / RELAY_BLOCK_TIME_MS, + ) + const value = u32ToLeHex(relayBlock) + spec.genesis.raw.top[SCHEDULER_INCOMPLETE_SINCE] = value + spec.genesis.raw.top[PARACHAIN_LAST_RELAY_BLOCK] = value + + await Deno.writeTextFile(rawPath, JSON.stringify(spec)) + console.log('📋 Chain spec ready') + return rawPath +}