quantumcoin 7.0.13 → 7.0.14
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/examples/node_modules/.bin/esbuild +16 -0
- package/examples/node_modules/.bin/esbuild.cmd +17 -0
- package/examples/node_modules/.bin/esbuild.ps1 +28 -0
- package/examples/node_modules/.bin/sdkgen +16 -0
- package/examples/node_modules/.bin/sdkgen.cmd +17 -0
- package/examples/node_modules/.bin/sdkgen.ps1 +28 -0
- package/examples/node_modules/.bin/tsx +16 -0
- package/examples/node_modules/.bin/tsx.cmd +17 -0
- package/examples/node_modules/.bin/tsx.ps1 +28 -0
- package/examples/node_modules/.package-lock.json +144 -0
- package/examples/node_modules/@esbuild/win32-x64/README.md +3 -0
- package/examples/node_modules/@esbuild/win32-x64/esbuild.exe +0 -0
- package/examples/node_modules/@esbuild/win32-x64/package.json +20 -0
- package/examples/node_modules/esbuild/LICENSE.md +21 -0
- package/examples/node_modules/esbuild/README.md +3 -0
- package/examples/node_modules/esbuild/bin/esbuild +223 -0
- package/examples/node_modules/esbuild/install.js +289 -0
- package/examples/node_modules/esbuild/lib/main.d.ts +716 -0
- package/examples/node_modules/esbuild/lib/main.js +2532 -0
- package/examples/node_modules/esbuild/package.json +49 -0
- package/examples/node_modules/get-tsconfig/LICENSE +21 -0
- package/examples/node_modules/get-tsconfig/README.md +235 -0
- package/examples/node_modules/get-tsconfig/dist/index.cjs +7 -0
- package/examples/node_modules/get-tsconfig/dist/index.d.cts +2088 -0
- package/examples/node_modules/get-tsconfig/dist/index.d.mts +2088 -0
- package/examples/node_modules/get-tsconfig/dist/index.mjs +7 -0
- package/examples/node_modules/get-tsconfig/package.json +46 -0
- package/examples/node_modules/quantum-coin-js-sdk/LICENSE +21 -0
- package/examples/node_modules/quantum-coin-js-sdk/LICENSE-wasm_exec.js.txt +30 -0
- package/examples/node_modules/quantum-coin-js-sdk/README.md +1675 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/README.md +14 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/conversion-example.js +19 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-create-contract.js +396 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-encode-decode-rlp.js +225 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-event-pack-unpack.js +391 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-misc.js +101 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-rpc-send-signRawTransaction.js +318 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-rpc-send.js +116 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-send.js +70 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-token-pack-unpack.js +961 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-wallet-version4.js +35 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example-wallet.js +43 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/example.js +405 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/package-lock.json +134 -0
- package/examples/node_modules/quantum-coin-js-sdk/example/package.json +15 -0
- package/examples/node_modules/quantum-coin-js-sdk/index.d.ts +1031 -0
- package/examples/node_modules/quantum-coin-js-sdk/index.js +3144 -0
- package/examples/node_modules/quantum-coin-js-sdk/package.json +34 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-32.json +1 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-36.json +1 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-48.json +1 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/generate-verify-vectors.js +91 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/non-transactional.preinit.test.js +41 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/non-transactional.test.js +1389 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/sign-raw-keytype5-context-null.test.js +107 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/sign-raw-transaction.test.js +196 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/sign-verify.test.js +311 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/transactional.relay.test.js +131 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/transactional.rpc.test.js +103 -0
- package/examples/node_modules/quantum-coin-js-sdk/tests/verify-vectors.json +95035 -0
- package/examples/node_modules/quantum-coin-js-sdk/wasmBase64.d.ts +9 -0
- package/examples/node_modules/quantum-coin-js-sdk/wasmBase64.js +16 -0
- package/examples/node_modules/quantum-coin-js-sdk/wasm_exec.d.ts +0 -0
- package/examples/node_modules/quantum-coin-js-sdk/wasm_exec.js +587 -0
- package/examples/node_modules/resolve-pkg-maps/LICENSE +21 -0
- package/examples/node_modules/resolve-pkg-maps/README.md +216 -0
- package/examples/node_modules/resolve-pkg-maps/dist/index.cjs +1 -0
- package/examples/node_modules/resolve-pkg-maps/dist/index.d.cts +11 -0
- package/examples/node_modules/resolve-pkg-maps/dist/index.d.mts +11 -0
- package/examples/node_modules/resolve-pkg-maps/dist/index.mjs +1 -0
- package/examples/node_modules/resolve-pkg-maps/package.json +42 -0
- package/examples/node_modules/seed-words/.github/workflows/publish-npmjs.yaml +22 -0
- package/examples/node_modules/seed-words/BUILD.md +7 -0
- package/examples/node_modules/seed-words/LICENSE +121 -0
- package/examples/node_modules/seed-words/README.md +67 -0
- package/examples/node_modules/seed-words/dist/seedwords.d.ts +39 -0
- package/examples/node_modules/seed-words/package.json +27 -0
- package/examples/node_modules/seed-words/seedwords.js +315 -0
- package/examples/node_modules/seed-words/seedwords.txt +65536 -0
- package/examples/node_modules/seed-words/tsconfig.json +21 -0
- package/examples/node_modules/tsx/LICENSE +21 -0
- package/examples/node_modules/tsx/README.md +32 -0
- package/examples/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
- package/examples/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
- package/examples/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
- package/examples/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
- package/examples/node_modules/tsx/dist/cjs/index.cjs +1 -0
- package/examples/node_modules/tsx/dist/cjs/index.mjs +1 -0
- package/examples/node_modules/tsx/dist/cli.cjs +54 -0
- package/examples/node_modules/tsx/dist/cli.mjs +55 -0
- package/examples/node_modules/tsx/dist/client-BQVF1NaW.mjs +1 -0
- package/examples/node_modules/tsx/dist/client-D6NvIMSC.cjs +1 -0
- package/examples/node_modules/tsx/dist/esm/api/index.cjs +1 -0
- package/examples/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
- package/examples/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
- package/examples/node_modules/tsx/dist/esm/api/index.mjs +1 -0
- package/examples/node_modules/tsx/dist/esm/index.cjs +2 -0
- package/examples/node_modules/tsx/dist/esm/index.mjs +2 -0
- package/examples/node_modules/tsx/dist/get-pipe-path-BHW2eJdv.mjs +1 -0
- package/examples/node_modules/tsx/dist/get-pipe-path-BoR10qr8.cjs +1 -0
- package/examples/node_modules/tsx/dist/index-7AaEi15b.mjs +14 -0
- package/examples/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
- package/examples/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
- package/examples/node_modules/tsx/dist/index-gckBtVBf.cjs +14 -0
- package/examples/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
- package/examples/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
- package/examples/node_modules/tsx/dist/loader.cjs +1 -0
- package/examples/node_modules/tsx/dist/loader.mjs +1 -0
- package/examples/node_modules/tsx/dist/node-features-_8ZFwP_x.mjs +1 -0
- package/examples/node_modules/tsx/dist/node-features-roYmp9jK.cjs +1 -0
- package/examples/node_modules/tsx/dist/package-CeBgXWuR.mjs +1 -0
- package/examples/node_modules/tsx/dist/package-Dxt5kIHw.cjs +1 -0
- package/examples/node_modules/tsx/dist/patch-repl.cjs +1 -0
- package/examples/node_modules/tsx/dist/patch-repl.mjs +1 -0
- package/examples/node_modules/tsx/dist/preflight.cjs +1 -0
- package/examples/node_modules/tsx/dist/preflight.mjs +1 -0
- package/examples/node_modules/tsx/dist/register-2sWVXuRQ.cjs +1 -0
- package/examples/node_modules/tsx/dist/register-B7jrtLTO.mjs +1 -0
- package/examples/node_modules/tsx/dist/register-CFH5oNdT.mjs +4 -0
- package/examples/node_modules/tsx/dist/register-D46fvsV_.cjs +4 -0
- package/examples/node_modules/tsx/dist/repl.cjs +3 -0
- package/examples/node_modules/tsx/dist/repl.mjs +3 -0
- package/examples/node_modules/tsx/dist/require-D4F1Lv60.cjs +1 -0
- package/examples/node_modules/tsx/dist/require-DQxpCAr4.mjs +1 -0
- package/examples/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
- package/examples/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
- package/examples/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
- package/examples/node_modules/tsx/dist/temporary-directory-CwHp0_NW.mjs +1 -0
- package/examples/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
- package/examples/node_modules/tsx/package.json +68 -0
- package/examples/package-lock.json +6 -6
- package/examples/package.json +1 -1
- package/generate-sdk.js +30 -9
- package/package.json +2 -2
- package/src/abi/interface.js +11 -2
- package/src/abi/js-abi-coder.js +61 -2
- package/src/contract/contract.js +53 -5
- package/src/generator/index.js +152 -13
- package/src/providers/provider.js +138 -5
- package/src/utils/rlp.js +13 -1
- package/src/wallet/wallet.js +91 -8
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/_test-wallet.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/_test-wallet.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/deploy.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/deploy.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/offline-signing.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/offline-signing.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/write-operations.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/write-operations.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/package-lock.json +6 -6
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/package.json +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/src/AllSolidityTypes__factory.js +3 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/test/e2e/AllSolidityTypes.e2e.test.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/_test-wallet.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/_test-wallet.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/deploy.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/deploy.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/offline-signing.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/offline-signing.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/write-operations.js +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/write-operations.ts +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/package-lock.json +6 -6
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/package.json +1 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/src/AllSolidityTypes__factory.ts +3 -1
- package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/test/e2e/AllSolidityTypes.e2e.test.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/_test-wallet.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/_test-wallet.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/deploy.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/deploy.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/offline-signing.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/offline-signing.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/write-operations.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/write-operations.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/package-lock.json +6 -6
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/package.json +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/src/SimpleERC20.js +9 -3
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/src/SimpleERC20__factory.js +3 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/test/e2e/SimpleERC20.e2e.test.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/_test-wallet.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/_test-wallet.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/deploy.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/deploy.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/offline-signing.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/offline-signing.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/write-operations.js +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/write-operations.ts +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/package-lock.json +6 -6
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/package.json +1 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/src/SimpleERC20.ts +9 -3
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/src/SimpleERC20__factory.ts +3 -1
- package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/test/e2e/SimpleERC20.e2e.test.js +1 -1
- package/test/e2e/generator-interface.e2e.test.js +6 -4
- package/test/e2e/generator-interface.e2e.test.ts +6 -4
- package/test/security/abi-decoder-bounds.test.js +122 -0
- package/test/security/contract-overrides.test.js +112 -0
- package/test/security/generator-injection.test.js +195 -0
- package/test/security/malformed-input.test.js +26 -27
- package/test/security/rpc-numeric-bounds.test.js +81 -0
- package/test/security/rpc-trust.test.js +202 -0
- package/test/unit/abi-interface.test.js +12 -5
- package/test/unit/abi-interface.test.ts +8 -1
- package/test/unit/address-wallet.test.js +31 -0
- package/test/unit/encoding-units-rlp.test.js +35 -0
- package/test/unit/populate-transaction.test.js +33 -0
package/src/contract/contract.js
CHANGED
|
@@ -37,6 +37,40 @@ function _isOverridesLike(value) {
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Fields a caller is allowed to set via `overrides`. Protected routing /
|
|
41
|
+
// payload fields (`to`, `data`, `from`) are intentionally excluded so that a
|
|
42
|
+
// caller-supplied overrides object can never redirect the transaction to a
|
|
43
|
+
// different contract or replace the encoded calldata. Unknown keys are dropped.
|
|
44
|
+
const _ALLOWED_OVERRIDE_KEYS = [
|
|
45
|
+
"value",
|
|
46
|
+
"gasLimit",
|
|
47
|
+
"gasPrice",
|
|
48
|
+
"maxFeePerGas",
|
|
49
|
+
"maxPriorityFeePerGas",
|
|
50
|
+
"nonce",
|
|
51
|
+
"chainId",
|
|
52
|
+
"remarks",
|
|
53
|
+
"signingContext",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Return a copy of `overrides` containing only the allow-listed fields.
|
|
58
|
+
* Drops `to`, `data`, `from`, and any unknown/prototype keys so the protected
|
|
59
|
+
* fields computed by the contract layer always win.
|
|
60
|
+
* @param {any} overrides
|
|
61
|
+
* @returns {Record<string, any>}
|
|
62
|
+
*/
|
|
63
|
+
function _sanitizeOverrides(overrides) {
|
|
64
|
+
const out = {};
|
|
65
|
+
if (!_isOverridesLike(overrides)) return out;
|
|
66
|
+
for (const key of _ALLOWED_OVERRIDE_KEYS) {
|
|
67
|
+
if (Object.prototype.hasOwnProperty.call(overrides, key) && overrides[key] !== undefined) {
|
|
68
|
+
out[key] = overrides[key];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
40
74
|
/**
|
|
41
75
|
* BaseContract placeholder (ethers-like).
|
|
42
76
|
*/
|
|
@@ -228,7 +262,8 @@ class Contract extends BaseContract {
|
|
|
228
262
|
}
|
|
229
263
|
|
|
230
264
|
const data = this.interface.encodeFunctionData(methodName, callArgs);
|
|
231
|
-
|
|
265
|
+
// Protected fields (to/data) must win over caller-supplied overrides.
|
|
266
|
+
return { ..._sanitizeOverrides(overrides), to: this.address, data };
|
|
232
267
|
}
|
|
233
268
|
|
|
234
269
|
/**
|
|
@@ -241,7 +276,8 @@ class Contract extends BaseContract {
|
|
|
241
276
|
async call(methodName, args, overrides) {
|
|
242
277
|
if (!this.provider) throw makeError("missing provider", "UNKNOWN_ERROR", { operation: "call" });
|
|
243
278
|
const data = this.interface.encodeFunctionData(methodName, args);
|
|
244
|
-
|
|
279
|
+
// Protected fields (to/data) must win over caller-supplied overrides.
|
|
280
|
+
const tx = { ..._sanitizeOverrides(overrides), to: this.address, data };
|
|
245
281
|
const raw = await this.provider.call(tx, "latest");
|
|
246
282
|
const decoded = this.interface.decodeFunctionResult(methodName, raw);
|
|
247
283
|
return decoded;
|
|
@@ -257,7 +293,8 @@ class Contract extends BaseContract {
|
|
|
257
293
|
async send(methodName, args, overrides) {
|
|
258
294
|
if (!this.signer) throw makeError("missing signer", "UNKNOWN_ERROR", { operation: "send" });
|
|
259
295
|
const data = this.interface.encodeFunctionData(methodName, args);
|
|
260
|
-
|
|
296
|
+
// Protected fields (to/data) must win over caller-supplied overrides.
|
|
297
|
+
const tx = { ..._sanitizeOverrides(overrides), to: this.address, data };
|
|
261
298
|
const resp = await this.signer.sendTransaction(tx);
|
|
262
299
|
return new ContractTransactionResponse(resp);
|
|
263
300
|
}
|
|
@@ -273,9 +310,19 @@ class Contract extends BaseContract {
|
|
|
273
310
|
if (!this.provider) throw makeError("missing provider", "UNKNOWN_ERROR", { operation: "queryFilter" });
|
|
274
311
|
const name = typeof event === "string" ? event : event?.name;
|
|
275
312
|
assertArgument(typeof name === "string", "invalid event", "event", event);
|
|
276
|
-
|
|
313
|
+
|
|
314
|
+
// Bind the query to the requested event's topic0 so a malicious node cannot
|
|
315
|
+
// return same-address logs for a *different* event. We both constrain the RPC
|
|
316
|
+
// filter and verify the returned logs' topic0 client-side.
|
|
317
|
+
const topic0 = this.interface.getEventTopic(name);
|
|
318
|
+
const filter = { address: this.address, topics: [topic0], fromBlock, toBlock };
|
|
277
319
|
const logs = await this.provider.getLogs(filter);
|
|
278
|
-
return logs
|
|
320
|
+
return logs
|
|
321
|
+
.filter((l) => {
|
|
322
|
+
const t = Array.isArray(l && l.topics) ? l.topics[0] : null;
|
|
323
|
+
return typeof t === "string" && normalizeHex(t).toLowerCase() === topic0.toLowerCase();
|
|
324
|
+
})
|
|
325
|
+
.map((l) => new EventLog(l));
|
|
279
326
|
}
|
|
280
327
|
|
|
281
328
|
on(event, callback) {
|
|
@@ -356,5 +403,6 @@ module.exports = {
|
|
|
356
403
|
ContractTransactionResponse,
|
|
357
404
|
ContractTransactionReceipt,
|
|
358
405
|
EventLog,
|
|
406
|
+
_sanitizeOverrides,
|
|
359
407
|
};
|
|
360
408
|
|
package/src/generator/index.js
CHANGED
|
@@ -80,8 +80,9 @@ function _collectTupleRegistry(contractName, abi) {
|
|
|
80
80
|
const key = _tupleKey(param);
|
|
81
81
|
if (byKey.has(key)) return byKey.get(key);
|
|
82
82
|
|
|
83
|
-
const suggested =
|
|
84
|
-
_tupleBaseNameFromInternalType(contractName, param && param.internalType) || `${contractName}Tuple${++counter}
|
|
83
|
+
const suggested = _safeTypeIdent(
|
|
84
|
+
_tupleBaseNameFromInternalType(contractName, param && param.internalType) || `${contractName}Tuple${++counter}`,
|
|
85
|
+
);
|
|
85
86
|
let baseName = suggested;
|
|
86
87
|
if (usedNames.has(baseName) && usedNames.get(baseName) !== key) {
|
|
87
88
|
let n = 1;
|
|
@@ -376,6 +377,93 @@ function _safeIdent(name) {
|
|
|
376
377
|
return (name || "arg").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
377
378
|
}
|
|
378
379
|
|
|
380
|
+
// NatSpec/doc text is attacker-controllable (it comes from arbitrary Solidity
|
|
381
|
+
// source). It is emitted into generated `/** ... */` JSDoc blocks, so a `*/` in the
|
|
382
|
+
// text would close the comment early and turn whatever follows into executable code
|
|
383
|
+
// in the generated .js/.ts files. Neutralize the comment delimiters (and strip
|
|
384
|
+
// CR/LF so a line cannot be split) before interpolation.
|
|
385
|
+
function _escapeComment(text) {
|
|
386
|
+
return String(text == null ? "" : text)
|
|
387
|
+
.replace(/\*\//g, "* /")
|
|
388
|
+
.replace(/\/\*/g, "/ *")
|
|
389
|
+
.replace(/[\r\n]+/g, " ");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Reserved words / dangerous property names that must never be emitted as a raw
|
|
393
|
+
// identifier (class name, function name, import/require specifier, etc.).
|
|
394
|
+
const _RESERVED_IDENTIFIERS = new Set([
|
|
395
|
+
"break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do",
|
|
396
|
+
"else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof",
|
|
397
|
+
"new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while",
|
|
398
|
+
"with", "yield", "enum", "await", "implements", "interface", "let", "package", "private",
|
|
399
|
+
"protected", "public", "static", "null", "true", "false",
|
|
400
|
+
"__proto__", "constructor", "prototype",
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Assert that `name` is safe to interpolate verbatim into generated source code
|
|
405
|
+
* (class names, function names, type names, require/import specifiers, file names).
|
|
406
|
+
*
|
|
407
|
+
* This is the primary defense against code-injection and path-traversal
|
|
408
|
+
* via attacker-controlled ABI / artifact `name` fields: only strict JS/TS
|
|
409
|
+
* identifiers are allowed, which by construction cannot contain quotes, newlines,
|
|
410
|
+
* path separators, `..`, or other breakout characters.
|
|
411
|
+
*
|
|
412
|
+
* @param {any} name
|
|
413
|
+
* @param {string=} kind Human-readable description for error messages.
|
|
414
|
+
* @returns {string} the validated name
|
|
415
|
+
*/
|
|
416
|
+
function _assertSafeIdentifier(name, kind) {
|
|
417
|
+
const label = kind || "identifier";
|
|
418
|
+
if (typeof name !== "string" || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
|
|
419
|
+
throw new Error(`Unsafe ${label} for code generation: ${JSON.stringify(name)}`);
|
|
420
|
+
}
|
|
421
|
+
if (_RESERVED_IDENTIFIERS.has(name)) {
|
|
422
|
+
throw new Error(`${label} must not be a reserved word: ${JSON.stringify(name)}`);
|
|
423
|
+
}
|
|
424
|
+
return name;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Coerce an arbitrary string (e.g. a struct name derived from an attacker-controlled
|
|
429
|
+
* `internalType`) into a safe type identifier. Unlike `_assertSafeIdentifier` this
|
|
430
|
+
* never throws; it sanitizes so auto-generated type names always remain valid.
|
|
431
|
+
* @param {any} s
|
|
432
|
+
* @returns {string}
|
|
433
|
+
*/
|
|
434
|
+
function _safeTypeIdent(s) {
|
|
435
|
+
let v = String(s == null ? "" : s).replace(/[^A-Za-z0-9_$]/g, "_");
|
|
436
|
+
if (!/^[A-Za-z_$]/.test(v)) v = "_" + v;
|
|
437
|
+
return v || "_Tuple";
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Validate every rendered function name in an ABI. Only `function` entries are
|
|
442
|
+
* emitted as method identifiers, so those are the names that must be safe.
|
|
443
|
+
* @param {any[]} abi
|
|
444
|
+
*/
|
|
445
|
+
function _assertSafeAbiNames(abi) {
|
|
446
|
+
for (const f of abi || []) {
|
|
447
|
+
if (f && f.type === "function" && f.name != null) {
|
|
448
|
+
_assertSafeIdentifier(f.name, "ABI function name");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Assert that `filePath` resolves to a location inside `outDir` (defense-in-depth).
|
|
455
|
+
* @param {string} outDir
|
|
456
|
+
* @param {string} filePath
|
|
457
|
+
*/
|
|
458
|
+
function _assertWithinDir(outDir, filePath) {
|
|
459
|
+
const root = path.resolve(outDir);
|
|
460
|
+
const resolved = path.resolve(filePath);
|
|
461
|
+
const rel = path.relative(root, resolved);
|
|
462
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
463
|
+
throw new Error(`Refusing to write outside output directory: ${resolved}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
379
467
|
function _findConstructor(abi) {
|
|
380
468
|
const ctor = abi.find((f) => f && f.type === "constructor");
|
|
381
469
|
return ctor || { type: "constructor", inputs: [] };
|
|
@@ -508,7 +596,7 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
|
|
|
508
596
|
for (const line of docs.contract.split(/\r?\n/g)) {
|
|
509
597
|
const t = line.trim();
|
|
510
598
|
if (!t) continue;
|
|
511
|
-
contractTsLines.push(` * ${t}`);
|
|
599
|
+
contractTsLines.push(` * ${_escapeComment(t)}`);
|
|
512
600
|
}
|
|
513
601
|
}
|
|
514
602
|
contractTsLines.push(` */`);
|
|
@@ -539,7 +627,12 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
|
|
|
539
627
|
` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides?: any): Promise<import("quantumcoin").TransactionRequest> => {`,
|
|
540
628
|
);
|
|
541
629
|
contractTsLines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
|
|
542
|
-
|
|
630
|
+
// Drop attacker-controllable to/data/from; protected fields win.
|
|
631
|
+
contractTsLines.push(` const safeOverrides: any = {};`);
|
|
632
|
+
contractTsLines.push(
|
|
633
|
+
` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "chainId", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
|
|
634
|
+
);
|
|
635
|
+
contractTsLines.push(` return { ...safeOverrides, to: this.address, data };`);
|
|
543
636
|
contractTsLines.push(` },`);
|
|
544
637
|
}
|
|
545
638
|
contractTsLines.push(` } as any;`);
|
|
@@ -576,7 +669,7 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
|
|
|
576
669
|
for (const line of fnDoc.split(/\r?\n/g)) {
|
|
577
670
|
const t = line.trim();
|
|
578
671
|
if (!t) continue;
|
|
579
|
-
contractTsLines.push(` * ${t}`);
|
|
672
|
+
contractTsLines.push(` * ${_escapeComment(t)}`);
|
|
580
673
|
}
|
|
581
674
|
}
|
|
582
675
|
contractTsLines.push(` */`);
|
|
@@ -628,7 +721,7 @@ function _renderContractJs({ contractName, abi, bytecode, docs }) {
|
|
|
628
721
|
for (const line of docs.contract.split(/\r?\n/g)) {
|
|
629
722
|
const t = line.trim();
|
|
630
723
|
if (!t) continue;
|
|
631
|
-
lines.push(` * ${t}`);
|
|
724
|
+
lines.push(` * ${_escapeComment(t)}`);
|
|
632
725
|
}
|
|
633
726
|
}
|
|
634
727
|
lines.push(" */");
|
|
@@ -654,7 +747,12 @@ function _renderContractJs({ contractName, abi, bytecode, docs }) {
|
|
|
654
747
|
const argsSig = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
|
|
655
748
|
lines.push(` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides) => {`);
|
|
656
749
|
lines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
|
|
657
|
-
|
|
750
|
+
// Drop attacker-controllable to/data/from; protected fields win.
|
|
751
|
+
lines.push(` const safeOverrides = {};`);
|
|
752
|
+
lines.push(
|
|
753
|
+
` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "chainId", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
|
|
754
|
+
);
|
|
755
|
+
lines.push(` return { ...safeOverrides, to: this.address, data };`);
|
|
658
756
|
lines.push(" },");
|
|
659
757
|
}
|
|
660
758
|
lines.push(" };");
|
|
@@ -679,7 +777,7 @@ function _renderContractJs({ contractName, abi, bytecode, docs }) {
|
|
|
679
777
|
for (const line of fnDoc.split(/\r?\n/g)) {
|
|
680
778
|
const t = line.trim();
|
|
681
779
|
if (!t) continue;
|
|
682
|
-
lines.push(` * ${t}`);
|
|
780
|
+
lines.push(` * ${_escapeComment(t)}`);
|
|
683
781
|
}
|
|
684
782
|
}
|
|
685
783
|
for (const p of inputs) {
|
|
@@ -843,7 +941,13 @@ function _renderFactoryTs({ contractName, abi }) {
|
|
|
843
941
|
factoryTsLines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
|
|
844
942
|
factoryTsLines.push(` const address = getCreateAddress({ from, nonce });`);
|
|
845
943
|
factoryTsLines.push(` const txReq: any = this.getDeployTransaction(${deployArgsNames});`);
|
|
846
|
-
|
|
944
|
+
// Only allow-listed override fields may reach the signer; protected fields
|
|
945
|
+
// (to/data from txReq, and the computed nonce) always win.
|
|
946
|
+
factoryTsLines.push(` const safeOverrides: any = {};`);
|
|
947
|
+
factoryTsLines.push(
|
|
948
|
+
` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
|
|
949
|
+
);
|
|
950
|
+
factoryTsLines.push(` const tx = await signer.sendTransaction({ ...txReq, ...safeOverrides, nonce });`);
|
|
847
951
|
factoryTsLines.push(` return new ${contractName}(address, signer as any, tx as any);`);
|
|
848
952
|
factoryTsLines.push(` }`);
|
|
849
953
|
factoryTsLines.push(``);
|
|
@@ -882,7 +986,13 @@ function _renderFactoryJs({ contractName, abi }) {
|
|
|
882
986
|
lines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
|
|
883
987
|
lines.push(` const address = getCreateAddress({ from, nonce });`);
|
|
884
988
|
lines.push(` const txReq = this.getDeployTransaction(${deployArgsNames});`);
|
|
885
|
-
|
|
989
|
+
// Only allow-listed override fields may reach the signer; protected fields
|
|
990
|
+
// (to/data from txReq, and the computed nonce) always win.
|
|
991
|
+
lines.push(` const safeOverrides = {};`);
|
|
992
|
+
lines.push(
|
|
993
|
+
` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
|
|
994
|
+
);
|
|
995
|
+
lines.push(` const tx = await signer.sendTransaction({ ...txReq, ...safeOverrides, nonce });`);
|
|
886
996
|
lines.push(` return new ${contractName}(address, signer, tx);`);
|
|
887
997
|
lines.push(` }`);
|
|
888
998
|
lines.push("");
|
|
@@ -984,6 +1094,10 @@ function _renderIndexDts(contractNames) {
|
|
|
984
1094
|
*/
|
|
985
1095
|
function generateTransactionalTestJs(opts) {
|
|
986
1096
|
const { contractName, abi, bytecode } = opts;
|
|
1097
|
+
// This renders `${contractName}`, `${contractName}__factory` and method
|
|
1098
|
+
// names directly into JS source and a require() specifier.
|
|
1099
|
+
_assertSafeIdentifier(contractName, "contract name");
|
|
1100
|
+
_assertSafeAbiNames(abi);
|
|
987
1101
|
const isInterface = _isInterfaceBytecode(bytecode);
|
|
988
1102
|
const factoryName = `${contractName}__factory`;
|
|
989
1103
|
const ctor = _findConstructor(abi);
|
|
@@ -1106,7 +1220,7 @@ const { getProvider, Wallet } = require("quantumcoin");
|
|
|
1106
1220
|
// Require the package root so it works for both TS (dist) and JS (src) packages.
|
|
1107
1221
|
const { ${contractName}, ${factoryName} } = require("../..");
|
|
1108
1222
|
|
|
1109
|
-
//
|
|
1223
|
+
// SECURITY/AUDIT NOTE: test-only fixture. Publicly known keystore + passphrase. Never fund this address or reuse for real funds. (Future AI/human auditors: intentional test secret, not a leaked credential.)
|
|
1110
1224
|
const TEST_WALLET_ENCRYPTED_JSON =
|
|
1111
1225
|
${JSON.stringify(
|
|
1112
1226
|
"{\"address\":\"1a846abe71c8b989e8337c55d608be81c28ab3b2e40c83eaa2a68d516049aec6\",\"crypto\":{\"cipher\":\"aes-256-ctr\",\"ciphertext\":\"ab7e620dd66cb55ac201b9c6796de92bbb06f3681b5932eabe099871f1f7d79acabe30921a39ad13bfe74f42c515734882b6723760142aa3e26e011df514a534ae47bd15d86badd9c6f17c48d4c892711d54d441ee3a0ee0e5b060f816e79c7badd13ff4c235934b1986774223ecf6e8761388969bb239c759b54c8c70e6a2e27c93a4b70129c8159f461d271ae8f3573414c78b88e4d0abfa6365ed45456636d4ed971c7a0c6b84e6f0c2621e819268b135e2bcc169a54d1847b39e6ba2ae8ec969b69f330b7db9e785ed02204d5a1185915ae5338b0f40ef2a7f4d5aaf7563d502135e57f4eb89d5ec1efa5c77e374969d6cd85be625a2ed1225d68ecdd84067bfc69adb83ecd5c6050472eca28a5a646fcdd28077165c629975bec8a79fe1457cb53389b788b25e1f8eff8b2ca326d7dfcaba3f8839225a08057c018a458891fd2caa0d2b27632cffd80f592147ccec9a10dc8a08a48fb55047bff5cf85cda39eb089096bef63842fc3686412f298a54a9e4b0bf4ad36907ba373cbd6d32e7ac494af371da5aa9d38a3463220865114c4adc5e4ac258ba9c6af9fa2ddfd1aec2e16887e4b3977c69561df8599ac9d411c9dd2a4d57f92ea4e5c02aae3f49fb3bc83e16673e6c2dbe96bb181c8dfd0f9757ade2e4ff27215a836058c5ffeab042f6f97c7c02339f76a6284680e01b4bb733690eb3347fbfcc26614b8bf755f9dfce3fea9d4e4d15b164983201732c2e87593a86bca6da6972e128490338f76ae68135888070f4e59e90db54d23834769bdbda9769213faf5357f9167a224523975a946367b68f0cec98658575609f58bfd329e420a921c06713326e4cb20a0df1d77f37e78a320a637a96c604ca3fa89e24beb42313751b8f09b14f9c14c77e4fd13fc6382505d27c771bca0d821ec7c3765acffa99d83c50140a56b0b28101c762bd682fe55cb6f23cbeb3f421d7b36021010e45ac27160dd7ead99c864a1b550c7edb1246950fe32dcc049799f9085287f0a747a6ef7a023df46a23a22f3e833bbf8d404f84344870492658256ee1dfc40fda33bb8d48fc72d4520ba9fc820c9123104a045206809037709f2a5f6723fa77d6bac5a573823d4ec3a7f1cb786a52ee2697e622e5d75962fa554d1024a6c355e21f33a63b2b72e6c4742a8b1c373aa532b40518c38c90b5373c2eb8c9d7be2a9e16047a3ee09dc9a6849deac5183ace6cfe91a9bef2ffc0a7df6ccebfd4c858c84b0e0355650d7466971e66f1e3883013e5ad1be33199b1d110b79070ac1b745ccb14cf63a08f8cca3a21c9525e626ff5f0c34746e10750fb742ad51f11f2acae3676c2111853d7250d01b77821a6ba9e04400ba2c543ca9f2d701ae6f47bfad14ffe3039ee9e71f7b2401359ade9938750ddb9c5a8b018a7929ed8d0e717ff1861446ce17535e9b17c187711190aae3388bd9490837a636c25ed4d42d7079ad1a51e13292c683d5d012abcf46965c534b83ab53f2c1f0cf5830ef7582e06863a33c19a70511df632885d63245965047ea96b56f1af5b3b94a54999f784fb9574fdfcd7c1230e07a2aaa04acd3097b2b9f8ddba05ae9734491deb5c1a513c76ed276cb78bbf4839dae3156d76af444a5805129d5df791167a9c8576a1d7f760b2d2797c4658669608706fbd0ace1be2346f74862dfc9ef518e55632e43c043186e5d070deb34d12fb9e5aba84e5cb50213dc88efd39cc35bf42455aa82d5e3b707b3140be3b8623b34fdd81d08615c188ae8438a13881fdf6bf32f2cb9ff5fa625561040c6b71d4b8eccc90bc3b99650d28dd1ee63773e49664e3d48c484996b290943635a6f2eb1ce9796d3fa144a3f00ef82faaa32d6a413668f7b521517cb68b2b017fcf56c79326fa5e4060e643631ca3f0a0dc0ed718798b6f46b130d437c33f64039e887324b6f5e604b1669d613923794edbf04b1b3caea54793b52b44b170173a4f25c7ecef3b71e2aad76e556b1cb9f1d637ec52ececfa950dd31dbb6a60828a3ad34c1beffe09eb4785786d63bad10a0b0f66ea88c57380f38ea85f018dbd7f538cf1ee7624095b9a01ec5edd528f281168af020609e651ff316aa1320a710134ddfca600cc72174dcdb846d2aa29916488aa1b537b66da92e61af526debef4eb38c984569eaf549ff2129449269b492d030cd74d885f6f5785881cc4804b4a8a09ba4ff7aefe9074ac7d0c4f05d51fe4cc0ff7388a772092b9d02d70e5433a5cf3e02f46a6bd6b818d59a07ce3b9fbbf8b5faba74563bcc5240930c2d406c9aaee3e3ce0429bf68ac2b0a57adb09414cff50817d2a48fb9fa624ab863cb0c31a8b8dc5eaf6fa68cc1d7c6c685c5a33edd5c8933b9e8ab628ee428d0743699b2ff17f25586c7ce959280bb0b8c5342251f0a30b53dbc7bf1ee426ac9619c3560f811f2268ee37f189794e2e4b3db3a2fb2e34b649e504fb467438abfd1082619cc4a0b30d66beb831077812e418d2e2148db10cf4d4a29101ca52ec445b8d83519dd7de85a98e0beae9ee537096d3f1a55a7a80cdfa93d25f07c9f98e8af18cde19ec1f99c5dd4588b717a5039ddb7f177717caf0d0fd45420a70dbd6d3146890d9e450d5224146db4c33b779e3c3a04b976c052bad042ac57dd38be45407808c0fb0d7e2a8819e6cd53c6739e6612996ddaa6f066552590aa0343bc1e62b298ff2514a0cef8be21956c2e942816f7a3a3a0935eaf9b37251409ce444c986c3817e82835555fe18239f3ae33469d7965c2bde9991fde556bd07af01df52bbde0c35bb4ef48e3b5d0db53f8ca4ed35b83f760f0a1bc4ed9f86e85d6039a17df373c85402ef956f01db00eb39c4b74bd0660d29ee746714d9780d738e05c6cca414ce3d7b40dda8036a9eea9ab1388805f913eb19bdd3f09d9e161eaa50231bd9caba61971f194332dd28c696a60458c1c6c2cc5da8b1192611c7c553e9e12fe48ce46bbb891be8bb118721c86222e671ddd1da8f0ccb2b68e02f2014b4925e904e88369aaf7466bd7033a60c265d45955944916ecbdb84bf1b522b01b0149c632e04c568a7eb627c5bb90ece052ebcf79166c28b30d23fe52da0a5ab5dea83ca479a3e3b7a9cfbbfea04dbe6137c19d067317c2ec427a8c75a6b06bec6dcd5d5c0edc9aa80b9003b8e17c088b2f3db327d3e42630d82d20120240c3ba56232280787da4aabbf5bc95a864029f00710e195f2a76460a0317d10b552fe1bea097e41d49756c680a41d6ac186e62169b6b6cd7776ea84618b5b752328a5bacaa10aa122ff9b2698b43efe73d852a899db644863c8c9bc8068ea86ea843fd6fe36272b91cdc5d5317083ef3fd1e5462a0b0d0604dc57b3bbfceb0fca4cd349625dd7b25166af30efe5ee6a0af953a74d65f4736c59918ee55a3b0d9d9d42e04c7f8a77e479109f740e20c464d5d7e3d16805f47b61f403ff7f408c9e850d9baacd8067e544536a4953480b0f9ee9cd45f41ebd67b51f78788a6470cb1e5ca72ca346ce8a50d0ca0c921d5576a4455a1afb6d0bc688004712ee122cacdb29c51e84893324c27fa4a3f1917edf5352272b4c97579a6152e4b77663d0ab532915f2eeb6a862de8b696452321b660c3f2449673d086e95a7af28845a5259b763e0fcd09f72acf7b6c811066263060e5aa5b24658e880a01fd56bda4dad5ab604e129290f7d5489728f2a40968c6168b21cebbbcd11727cc9e9160c4e92e04387d3b0d62aab06a61f26daedd9fed11816ef2180172a47f47184ac4032b88758c98a2e0fb200f70e93ba695f5ebb7a1029610ad360d3b7fa1b4640b9dc674d3625eef786da93dff19bc7991b5d6193a3896664763fde479b5dfc04812111a80782854f2cf68ca7d82765cc9eb40fba4b44640710ed6e653abf9f07b466333f4fd22784d53cf40e17120f42caa841eaa24056b237827b0f47f7257c103c35027e9f503e5acfd023e7357b600d3084d361d5ee65ba319b45c153212a54e6fed85af7e43e0a926ebcbc2edf8de7e2ec9528f00bec262ad04d5c9dafccaea06a24748d28bf1799bae0e895543084539c50b5aaa4fb50d7431d6f0c8cee2a54aaf7ee7919b55bf40adb688632e5dbe273cea09e97b19c3d8e1f4de000deb66fa1942ad03a62d3252f51992244366c156000b49c297167a6cbdedea7ebae139d295f0ad298e0864249b905b7eb812886ec70ecdb286702274b5b8574149bf3866f9e46b997ff5ed622b169a0eb071347f18d530db1663906a28f4544ee4e004ab87b65476af30ede118052ff052b8dc986ca2c93dd5d4943266a579c7698ea014f688b3e8063a107feb162d392e2177b01bff77fb5abe5feebd0607158049a5a093325b7c9ee6b4dfa7a9f65c7c2fb628920d3603a1c2dad979eaa047cd661a268af1078c9788d720e64e4ce9d12e68de1e417ef2f293323681e1071f9220e1ee43d2e29d111b870ce3439f5100ecd4551ab65ee74aa1667e564957e9bc0ae1ea193980da2a0ec2698073388c85bec25ef447f0d5e93a5203fa44dff268e5cb799ed3b66e63d5e07b487e7534f24934c73a62a243e0151843a0fd3807711a101eaa7fc71f0ba68aebb9534d57cba41b094eebfb4c31cca8eddfa426f676aa347be8a7023a4e91ddb154b35cd4d5f7dbc2e5db491de99f33fc2cff2d57029ac950e1ccd681980af6a4e8969dfe39b3c7bfcbcf8fac92f1e6ec9fe572bfa6a7d65860eab2ed10ac01a71290b52e3148e84b7376a8605cd2bb0e8681ffc54691ce087685e33921bd44d36c78291713dce17569570f62137e6904f0d68cf53aa2ec395c389a75141f08114fb293ea63950e4ffee55ec6fc83cf44876b8e7f25cdd393ff87b9eda6eb746085b61a6900de191f0ce2cb388d61ece52e78bc47368194e8e00277e0d1631e6b9d4626ef76f8522582ccd5a40be3febc699bb510acc6271d55ff0f4cf3bb7669855a72efd9ca3e1056a2fe592a5bc877cce2b1f63b58383971da87873d2d1349cf5881242cdce4e7e2c5c514755746a0e0a7c2a6d9701cde005ae3420beb17c379a3516662253554f51f0423bb1844b0b90c54ed8177ceb0e1036a6609d836e748ca06c40ca64befadc6443ec286a0ce464678e8d11eb455f7bb305acebf6cb1f50e394a9bfeb752df1687831bac9cdd811f4f112ef6658d0f8799a866374ff96c5e2b79f30e7a74f8a2bc9ed1f88f01f30e30cb78ffb2bff10108f35e910ee3be4463e9e6f0ed910e8d598326e71dfa2277ffe5579d7fe9b6018bfe295b25219eae07b3b0270665c3fa00c3e0d180812b5cd62925585de84a7c48a9a86dba96544a251654d1966e082432dc85b6149cf21e91a46020ec32b66d28ba3b6a90c0617bc6fdd55aea819af2bcf84864ad60c28fe3c9f8339d0aee68b39d97f63b6e082835d86119cf9b9fdc8b827c847ce40aa10e1577a710132316845e825345e95bdf94d0c66ec65a6c4319fce4792313663b5f7a651a6710783e6ab71608ac6cbbf3af6911adf596ccf7c172b9bd5bceb6db379967b32b143bdd11d2ee12ddf64ecef6391e0f8570e6cddd3db95204919362b89b739fa94e7c1bfde799fd5e22aa25ca6ca42e30c08e23aae2385d99ebab441072a880dcefdab74a4c9bd39d363f6d1933d59400fca161d432aa00f23b1b1c19a154be8989699d549b66d44e39896f5523443bc6ddf4a65e91f1f3fb7b52318869a05856a4fc92f3694c81ed833c972fb918f7e5\",\"cipherparams\":{\"iv\":\"8c46d6162cd4c765759aedcbce2a5874\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"82fb6cdc6917609135277badacf15baa31899d08b71a5a0fa33167167c161537\"},\"mac\":\"9187b17f7eca48e6b8c586b0cd790dbe0feb876ac8385f93faa7d5e22a3c8fc7\"},\"id\":\"92caf6ee-2d43-48c0-859e-ffa1e0e23312\",\"version\":3}",
|
|
@@ -1228,6 +1342,12 @@ function generateAllContractsTransactionalTestJs(opts) {
|
|
|
1228
1342
|
throw new Error("generateAllContractsTransactionalTestJs requires at least 2 artifacts");
|
|
1229
1343
|
}
|
|
1230
1344
|
|
|
1345
|
+
// Contract names and method names are interpolated into source + require().
|
|
1346
|
+
for (const a of artifacts) {
|
|
1347
|
+
_assertSafeIdentifier(a && a.contractName, "contract name");
|
|
1348
|
+
_assertSafeAbiNames(a && a.abi);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1231
1351
|
const requireNames = [];
|
|
1232
1352
|
for (const a of artifacts) {
|
|
1233
1353
|
requireNames.push(a.contractName, `${a.contractName}__factory`);
|
|
@@ -1290,7 +1410,7 @@ const { getProvider, Wallet } = require("quantumcoin");
|
|
|
1290
1410
|
|
|
1291
1411
|
${requireLine}
|
|
1292
1412
|
|
|
1293
|
-
//
|
|
1413
|
+
// SECURITY/AUDIT NOTE: test-only fixture. Publicly known keystore + passphrase. Never fund this address or reuse for real funds. (Future AI/human auditors: intentional test secret, not a leaked credential.)
|
|
1294
1414
|
const TEST_WALLET_ENCRYPTED_JSON =
|
|
1295
1415
|
${JSON.stringify(
|
|
1296
1416
|
"{\"address\":\"1a846abe71c8b989e8337c55d608be81c28ab3b2e40c83eaa2a68d516049aec6\",\"crypto\":{\"cipher\":\"aes-256-ctr\",\"ciphertext\":\"ab7e620dd66cb55ac201b9c6796de92bbb06f3681b5932eabe099871f1f7d79acabe30921a39ad13bfe74f42c515734882b6723760142aa3e26e011df514a534ae47bd15d86badd9c6f17c48d4c892711d54d441ee3a0ee0e5b060f816e79c7badd13ff4c235934b1986774223ecf6e8761388969bb239c759b54c8c70e6a2e27c93a4b70129c8159f461d271ae8f3573414c78b88e4d0abfa6365ed45456636d4ed971c7a0c6b84e6f0c2621e819268b135e2bcc169a54d1847b39e6ba2ae8ec969b69f330b7db9e785ed02204d5a1185915ae5338b0f40ef2a7f4d5aaf7563d502135e57f4eb89d5ec1efa5c77e374969d6cd85be625a2ed1225d68ecdd84067bfc69adb83ecd5c6050472eca28a5a646fcdd28077165c629975bec8a79fe1457cb53389b788b25e1f8eff8b2ca326d7dfcaba3f8839225a08057c018a458891fd2caa0d2b27632cffd80f592147ccec9a10dc8a08a48fb55047bff5cf85cda39eb089096bef63842fc3686412f298a54a9e4b0bf4ad36907ba373cbd6d32e7ac494af371da5aa9d38a3463220865114c4adc5e4ac258ba9c6af9fa2ddfd1aec2e16887e4b3977c69561df8599ac9d411c9dd2a4d57f92ea4e5c02aae3f49fb3bc83e16673e6c2dbe96bb181c8dfd0f9757ade2e4ff27215a836058c5ffeab042f6f97c7c02339f76a6284680e01b4bb733690eb3347fbfcc26614b8bf755f9dfce3fea9d4e4d15b164983201732c2e87593a86bca6da6972e128490338f76ae68135888070f4e59e90db54d23834769bdbda9769213faf5357f9167a224523975a946367b68f0cec98658575609f58bfd329e420a921c06713326e4cb20a0df1d77f37e78a320a637a96c604ca3fa89e24beb42313751b8f09b14f9c14c77e4fd13fc6382505d27c771bca0d821ec7c3765acffa99d83c50140a56b0b28101c762bd682fe55cb6f23cbeb3f421d7b36021010e45ac27160dd7ead99c864a1b550c7edb1246950fe32dcc049799f9085287f0a747a6ef7a023df46a23a22f3e833bbf8d404f84344870492658256ee1dfc40fda33bb8d48fc72d4520ba9fc820c9123104a045206809037709f2a5f6723fa77d6bac5a573823d4ec3a7f1cb786a52ee2697e622e5d75962fa554d1024a6c355e21f33a63b2b72e6c4742a8b1c373aa532b40518c38c90b5373c2eb8c9d7be2a9e16047a3ee09dc9a6849deac5183ace6cfe91a9bef2ffc0a7df6ccebfd4c858c84b0e0355650d7466971e66f1e3883013e5ad1be33199b1d110b79070ac1b745ccb14cf63a08f8cca3a21c9525e626ff5f0c34746e10750fb742ad51f11f2acae3676c2111853d7250d01b77821a6ba9e04400ba2c543ca9f2d701ae6f47bfad14ffe3039ee9e71f7b2401359ade9938750ddb9c5a8b018a7929ed8d0e717ff1861446ce17535e9b17c187711190aae3388bd9490837a636c25ed4d42d7079ad1a51e13292c683d5d012abcf46965c534b83ab53f2c1f0cf5830ef7582e06863a33c19a70511df632885d63245965047ea96b56f1af5b3b94a54999f784fb9574fdfcd7c1230e07a2aaa04acd3097b2b9f8ddba05ae9734491deb5c1a513c76ed276cb78bbf4839dae3156d76af444a5805129d5df791167a9c8576a1d7f760b2d2797c4658669608706fbd0ace1be2346f74862dfc9ef518e55632e43c043186e5d070deb34d12fb9e5aba84e5cb50213dc88efd39cc35bf42455aa82d5e3b707b3140be3b8623b34fdd81d08615c188ae8438a13881fdf6bf32f2cb9ff5fa625561040c6b71d4b8eccc90bc3b99650d28dd1ee63773e49664e3d48c484996b290943635a6f2eb1ce9796d3fa144a3f00ef82faaa32d6a413668f7b521517cb68b2b017fcf56c79326fa5e4060e643631ca3f0a0dc0ed718798b6f46b130d437c33f64039e887324b6f5e604b1669d613923794edbf04b1b3caea54793b52b44b170173a4f25c7ecef3b71e2aad76e556b1cb9f1d637ec52ececfa950dd31dbb6a60828a3ad34c1beffe09eb4785786d63bad10a0b0f66ea88c57380f38ea85f018dbd7f538cf1ee7624095b9a01ec5edd528f281168af020609e651ff316aa1320a710134ddfca600cc72174dcdb846d2aa29916488aa1b537b66da92e61af526debef4eb38c984569eaf549ff2129449269b492d030cd74d885f6f5785881cc4804b4a8a09ba4ff7aefe9074ac7d0c4f05d51fe4cc0ff7388a772092b9d02d70e5433a5cf3e02f46a6bd6b818d59a07ce3b9fbbf8b5faba74563bcc5240930c2d406c9aaee3e3ce0429bf68ac2b0a57adb09414cff50817d2a48fb9fa624ab863cb0c31a8b8dc5eaf6fa68cc1d7c6c685c5a33edd5c8933b9e8ab628ee428d0743699b2ff17f25586c7ce959280bb0b8c5342251f0a30b53dbc7bf1ee426ac9619c3560f811f2268ee37f189794e2e4b3db3a2fb2e34b649e504fb467438abfd1082619cc4a0b30d66beb831077812e418d2e2148db10cf4d4a29101ca52ec445b8d83519dd7de85a98e0beae9ee537096d3f1a55a7a80cdfa93d25f07c9f98e8af18cde19ec1f99c5dd4588b717a5039ddb7f177717caf0d0fd45420a70dbd6d3146890d9e450d5224146db4c33b779e3c3a04b976c052bad042ac57dd38be45407808c0fb0d7e2a8819e6cd53c6739e6612996ddaa6f066552590aa0343bc1e62b298ff2514a0cef8be21956c2e942816f7a3a3a0935eaf9b37251409ce444c986c3817e82835555fe18239f3ae33469d7965c2bde9991fde556bd07af01df52bbde0c35bb4ef48e3b5d0db53f8ca4ed35b83f760f0a1bc4ed9f86e85d6039a17df373c85402ef956f01db00eb39c4b74bd0660d29ee746714d9780d738e05c6cca414ce3d7b40dda8036a9eea9ab1388805f913eb19bdd3f09d9e161eaa50231bd9caba61971f194332dd28c696a60458c1c6c2cc5da8b1192611c7c553e9e12fe48ce46bbb891be8bb118721c86222e671ddd1da8f0ccb2b68e02f2014b4925e904e88369aaf7466bd7033a60c265d45955944916ecbdb84bf1b522b01b0149c632e04c568a7eb627c5bb90ece052ebcf79166c28b30d23fe52da0a5ab5dea83ca479a3e3b7a9cfbbfea04dbe6137c19d067317c2ec427a8c75a6b06bec6dcd5d5c0edc9aa80b9003b8e17c088b2f3db327d3e42630d82d20120240c3ba56232280787da4aabbf5bc95a864029f00710e195f2a76460a0317d10b552fe1bea097e41d49756c680a41d6ac186e62169b6b6cd7776ea84618b5b752328a5bacaa10aa122ff9b2698b43efe73d852a899db644863c8c9bc8068ea86ea843fd6fe36272b91cdc5d5317083ef3fd1e5462a0b0d0604dc57b3bbfceb0fca4cd349625dd7b25166af30efe5ee6a0af953a74d65f4736c59918ee55a3b0d9d9d42e04c7f8a77e479109f740e20c464d5d7e3d16805f47b61f403ff7f408c9e850d9baacd8067e544536a4953480b0f9ee9cd45f41ebd67b51f78788a6470cb1e5ca72ca346ce8a50d0ca0c921d5576a4455a1afb6d0bc688004712ee122cacdb29c51e84893324c27fa4a3f1917edf5352272b4c97579a6152e4b77663d0ab532915f2eeb6a862de8b696452321b660c3f2449673d086e95a7af28845a5259b763e0fcd09f72acf7b6c811066263060e5aa5b24658e880a01fd56bda4dad5ab604e129290f7d5489728f2a40968c6168b21cebbbcd11727cc9e9160c4e92e04387d3b0d62aab06a61f26daedd9fed11816ef2180172a47f47184ac4032b88758c98a2e0fb200f70e93ba695f5ebb7a1029610ad360d3b7fa1b4640b9dc674d3625eef786da93dff19bc7991b5d6193a3896664763fde479b5dfc04812111a80782854f2cf68ca7d82765cc9eb40fba4b44640710ed6e653abf9f07b466333f4fd22784d53cf40e17120f42caa841eaa24056b237827b0f47f7257c103c35027e9f503e5acfd023e7357b600d3084d361d5ee65ba319b45c153212a54e6fed85af7e43e0a926ebcbc2edf8de7e2ec9528f00bec262ad04d5c9dafccaea06a24748d28bf1799bae0e895543084539c50b5aaa4fb50d7431d6f0c8cee2a54aaf7ee7919b55bf40adb688632e5dbe273cea09e97b19c3d8e1f4de000deb66fa1942ad03a62d3252f51992244366c156000b49c297167a6cbdedea7ebae139d295f0ad298e0864249b905b7eb812886ec70ecdb286702274b5b8574149bf3866f9e46b997ff5ed622b169a0eb071347f18d530db1663906a28f4544ee4e004ab87b65476af30ede118052ff052b8dc986ca2c93dd5d4943266a579c7698ea014f688b3e8063a107feb162d392e2177b01bff77fb5abe5feebd0607158049a5a093325b7c9ee6b4dfa7a9f65c7c2fb628920d3603a1c2dad979eaa047cd661a268af1078c9788d720e64e4ce9d12e68de1e417ef2f293323681e1071f9220e1ee43d2e29d111b870ce3439f5100ecd4551ab65ee74aa1667e564957e9bc0ae1ea193980da2a0ec2698073388c85bec25ef447f0d5e93a5203fa44dff268e5cb799ed3b66e63d5e07b487e7534f24934c73a62a243e0151843a0fd3807711a101eaa7fc71f0ba68aebb9534d57cba41b094eebfb4c31cca8eddfa426f676aa347be8a7023a4e91ddb154b35cd4d5f7dbc2e5db491de99f33fc2cff2d57029ac950e1ccd681980af6a4e8969dfe39b3c7bfcbcf8fac92f1e6ec9fe572bfa6a7d65860eab2ed10ac01a71290b52e3148e84b7376a8605cd2bb0e8681ffc54691ce087685e33921bd44d36c78291713dce17569570f62137e6904f0d68cf53aa2ec395c389a75141f08114fb293ea63950e4ffee55ec6fc83cf44876b8e7f25cdd393ff87b9eda6eb746085b61a6900de191f0ce2cb388d61ece52e78bc47368194e8e00277e0d1631e6b9d4626ef76f8522582ccd5a40be3febc699bb510acc6271d55ff0f4cf3bb7669855a72efd9ca3e1056a2fe592a5bc877cce2b1f63b58383971da87873d2d1349cf5881242cdce4e7e2c5c514755746a0e0a7c2a6d9701cde005ae3420beb17c379a3516662253554f51f0423bb1844b0b90c54ed8177ceb0e1036a6609d836e748ca06c40ca64befadc6443ec286a0ce464678e8d11eb455f7bb305acebf6cb1f50e394a9bfeb752df1687831bac9cdd811f4f112ef6658d0f8799a866374ff96c5e2b79f30e7a74f8a2bc9ed1f88f01f30e30cb78ffb2bff10108f35e910ee3be4463e9e6f0ed910e8d598326e71dfa2277ffe5579d7fe9b6018bfe295b25219eae07b3b0270665c3fa00c3e0d180812b5cd62925585de84a7c48a9a86dba96544a251654d1966e082432dc85b6149cf21e91a46020ec32b66d28ba3b6a90c0617bc6fdd55aea819af2bcf84864ad60c28fe3c9f8339d0aee68b39d97f63b6e082835d86119cf9b9fdc8b827c847ce40aa10e1577a710132316845e825345e95bdf94d0c66ec65a6c4319fce4792313663b5f7a651a6710783e6ab71608ac6cbbf3af6911adf596ccf7c172b9bd5bceb6db379967b32b143bdd11d2ee12ddf64ecef6391e0f8570e6cddd3db95204919362b89b739fa94e7c1bfde799fd5e22aa25ca6ca42e30c08e23aae2385d99ebab441072a880dcefdab74a4c9bd39d363f6d1933d59400fca161d432aa00f23b1b1c19a154be8989699d549b66d44e39896f5523443bc6ddf4a65e91f1f3fb7b52318869a05856a4fc92f3694c81ed833c972fb918f7e5\",\"cipherparams\":{\"iv\":\"8c46d6162cd4c765759aedcbce2a5874\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"82fb6cdc6917609135277badacf15baa31899d08b71a5a0fa33167167c161537\"},\"mac\":\"9187b17f7eca48e6b8c586b0cd790dbe0feb876ac8385f93faa7d5e22a3c8fc7\"},\"id\":\"92caf6ee-2d43-48c0-859e-ffa1e0e23312\",\"version\":3}",
|
|
@@ -1349,6 +1469,13 @@ function generateFromArtifacts(opts) {
|
|
|
1349
1469
|
throw new Error(`Unsupported generator lang: ${lang}`);
|
|
1350
1470
|
}
|
|
1351
1471
|
|
|
1472
|
+
// Reject attacker-controlled names before they are interpolated into
|
|
1473
|
+
// generated source or used to build output file paths.
|
|
1474
|
+
for (const a of opts.artifacts || []) {
|
|
1475
|
+
_assertSafeIdentifier(a && a.contractName, "contract name");
|
|
1476
|
+
_assertSafeAbiNames(a && a.abi);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1352
1479
|
const contractNames = opts.artifacts.map((a) => a.contractName);
|
|
1353
1480
|
|
|
1354
1481
|
const typesFile = path.join(opts.outDir, lang === "ts" ? `types.ts` : `types.js`);
|
|
@@ -1370,6 +1497,12 @@ function generateFromArtifacts(opts) {
|
|
|
1370
1497
|
const contractDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}.d.ts`) : null;
|
|
1371
1498
|
const factoryDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}__factory.d.ts`) : null;
|
|
1372
1499
|
|
|
1500
|
+
// Defense-in-depth — even though contractName is a validated identifier,
|
|
1501
|
+
// ensure no output path escapes the target directory.
|
|
1502
|
+
for (const f of [contractFile, factoryFile, contractDtsFile, factoryDtsFile]) {
|
|
1503
|
+
if (f) _assertWithinDir(opts.outDir, f);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1373
1506
|
if (lang === "ts") {
|
|
1374
1507
|
fs.writeFileSync(
|
|
1375
1508
|
contractFile,
|
|
@@ -1428,5 +1561,11 @@ function generate(opts) {
|
|
|
1428
1561
|
return { contractFile, factoryFile, typesFile: res.typesFile, indexFile: res.indexFile };
|
|
1429
1562
|
}
|
|
1430
1563
|
|
|
1431
|
-
module.exports = {
|
|
1564
|
+
module.exports = {
|
|
1565
|
+
generate,
|
|
1566
|
+
generateFromArtifacts,
|
|
1567
|
+
generateTransactionalTestJs,
|
|
1568
|
+
generateAllContractsTransactionalTestJs,
|
|
1569
|
+
assertSafeIdentifier: _assertSafeIdentifier,
|
|
1570
|
+
};
|
|
1432
1571
|
|
|
@@ -44,7 +44,17 @@ function _hexToBigInt(hex) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function _hexToNumber(hex) {
|
|
47
|
-
|
|
47
|
+
// RPC quantities are untrusted. Previously `Number(bigint)` silently
|
|
48
|
+
// truncated values above 2^53, corrupting blockNumber/nonce/status/indices used
|
|
49
|
+
// in confirmation math. We keep returning a `number` (no signature/type change),
|
|
50
|
+
// but fail loudly for out-of-range values instead of returning a corrupted one.
|
|
51
|
+
// Real chain values are far below MAX_SAFE_INTEGER, so honest flows are
|
|
52
|
+
// unaffected. (Returning bigint would break field types and wait() arithmetic.)
|
|
53
|
+
const bi = _hexToBigInt(hex);
|
|
54
|
+
if (bi > BigInt(Number.MAX_SAFE_INTEGER) || bi < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
55
|
+
throw makeError("RPC quantity exceeds safe integer range", "NUMERIC_FAULT", { value: bi.toString() });
|
|
56
|
+
}
|
|
57
|
+
return Number(bi);
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
/**
|
|
@@ -52,6 +62,47 @@ function _hexToNumber(hex) {
|
|
|
52
62
|
* @param {number|string|undefined} blockTag
|
|
53
63
|
* @returns {string|undefined}
|
|
54
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* Build a lowercase Set of allowed log addresses from a filter's `address` field
|
|
67
|
+
* (string or string[]). Returns null when no address constraint is present.
|
|
68
|
+
* @param {string|string[]|undefined|null} address
|
|
69
|
+
* @returns {Set<string>|null}
|
|
70
|
+
*/
|
|
71
|
+
function _normalizeAddressFilter(address) {
|
|
72
|
+
if (address == null) return null;
|
|
73
|
+
const list = Array.isArray(address) ? address : [address];
|
|
74
|
+
const set = new Set();
|
|
75
|
+
for (const a of list) {
|
|
76
|
+
if (typeof a === "string" && a.length > 0) set.add(a.toLowerCase());
|
|
77
|
+
}
|
|
78
|
+
return set.size ? set : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check a log's topics against a requested topics filter (eth_getLogs
|
|
83
|
+
* semantics). Each filter position may be null (wildcard), a string (exact
|
|
84
|
+
* match), or an array of strings (any match). Used to drop logs a malicious node
|
|
85
|
+
* returns that do not actually match the requested event topic(s).
|
|
86
|
+
* @param {any[]} logTopics
|
|
87
|
+
* @param {(string|string[]|null)[]} filterTopics
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
function _topicsMatch(logTopics, filterTopics) {
|
|
91
|
+
if (!Array.isArray(filterTopics)) return true;
|
|
92
|
+
const topics = Array.isArray(logTopics) ? logTopics : [];
|
|
93
|
+
for (let i = 0; i < filterTopics.length; i++) {
|
|
94
|
+
const want = filterTopics[i];
|
|
95
|
+
if (want == null) continue; // wildcard position
|
|
96
|
+
const have = typeof topics[i] === "string" ? topics[i].toLowerCase() : null;
|
|
97
|
+
if (have == null) return false;
|
|
98
|
+
const allowed = (Array.isArray(want) ? want : [want])
|
|
99
|
+
.filter((t) => typeof t === "string")
|
|
100
|
+
.map((t) => t.toLowerCase());
|
|
101
|
+
if (allowed.length && !allowed.includes(have)) return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
55
106
|
function _blockTagToHex(blockTag) {
|
|
56
107
|
if (blockTag === undefined || blockTag === null) return undefined;
|
|
57
108
|
const s = String(blockTag).toLowerCase();
|
|
@@ -263,7 +314,23 @@ class AbstractProvider extends Provider {
|
|
|
263
314
|
async getTransaction(txHash) {
|
|
264
315
|
const tx = await this._perform("eth_getTransactionByHash", [txHash]);
|
|
265
316
|
if (!tx) return null;
|
|
266
|
-
|
|
317
|
+
const result = new TransactionResponse(tx, this);
|
|
318
|
+
// Do not trust the node to return the transaction we actually asked for.
|
|
319
|
+
// When the response carries a hash, it must match the requested hash;
|
|
320
|
+
// otherwise a malicious node could substitute a different transaction.
|
|
321
|
+
if (
|
|
322
|
+
typeof txHash === "string" &&
|
|
323
|
+
typeof result.hash === "string" &&
|
|
324
|
+
result.hash.length > 0 &&
|
|
325
|
+
result.hash.toLowerCase() !== txHash.toLowerCase()
|
|
326
|
+
) {
|
|
327
|
+
throw makeError("RPC node returned a transaction whose hash does not match the requested hash", "UNKNOWN_ERROR", {
|
|
328
|
+
operation: "getTransaction",
|
|
329
|
+
expected: txHash,
|
|
330
|
+
got: result.hash,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
267
334
|
}
|
|
268
335
|
|
|
269
336
|
/**
|
|
@@ -273,7 +340,23 @@ class AbstractProvider extends Provider {
|
|
|
273
340
|
async getTransactionReceipt(txHash) {
|
|
274
341
|
const receipt = await this._perform("eth_getTransactionReceipt", [txHash]);
|
|
275
342
|
if (!receipt) return null;
|
|
276
|
-
|
|
343
|
+
const result = new TransactionReceipt(receipt, this);
|
|
344
|
+
// The receipt must belong to the transaction we asked about. A node must
|
|
345
|
+
// not be able to confirm an unrelated/fabricated transaction (which would let
|
|
346
|
+
// wait() report success for the wrong tx).
|
|
347
|
+
if (
|
|
348
|
+
typeof txHash === "string" &&
|
|
349
|
+
typeof result.transactionHash === "string" &&
|
|
350
|
+
result.transactionHash.length > 0 &&
|
|
351
|
+
result.transactionHash.toLowerCase() !== txHash.toLowerCase()
|
|
352
|
+
) {
|
|
353
|
+
throw makeError("RPC node returned a receipt whose hash does not match the requested hash", "UNKNOWN_ERROR", {
|
|
354
|
+
operation: "getTransactionReceipt",
|
|
355
|
+
expected: txHash,
|
|
356
|
+
got: result.transactionHash,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
277
360
|
}
|
|
278
361
|
|
|
279
362
|
/**
|
|
@@ -299,15 +382,47 @@ class AbstractProvider extends Provider {
|
|
|
299
382
|
/**
|
|
300
383
|
* Broadcasts a signed transaction.
|
|
301
384
|
* @param {TransactionRequest|string} tx
|
|
385
|
+
* @param {{ expectedHash?: string }=} opts Optional. When `expectedHash` is set,
|
|
386
|
+
* the hash returned by the node (and the fetched-back transaction's hash) must
|
|
387
|
+
* match it, otherwise an error is thrown (do not trust the RPC node to
|
|
388
|
+
* broadcast the exact transaction that was signed).
|
|
302
389
|
* @returns {Promise<TransactionResponse>}
|
|
303
390
|
*/
|
|
304
|
-
async sendTransaction(tx) {
|
|
391
|
+
async sendTransaction(tx, opts) {
|
|
305
392
|
// For QuantumCoin.js, tx is expected to be a signed raw transaction hex string.
|
|
306
393
|
const raw = typeof tx === "string" ? tx : tx?.raw;
|
|
307
394
|
if (typeof raw !== "string") throw makeError("sendTransaction requires a signed raw transaction string", "INVALID_ARGUMENT", { tx });
|
|
395
|
+
const expectedHash =
|
|
396
|
+
opts && typeof opts.expectedHash === "string" && opts.expectedHash.length > 0 ? opts.expectedHash.toLowerCase() : null;
|
|
397
|
+
|
|
308
398
|
const hash = await this._perform("eth_sendRawTransaction", [raw]);
|
|
399
|
+
|
|
400
|
+
// The node returns the hash it claims to have accepted. If we know the
|
|
401
|
+
// hash of the transaction we signed, reject any mismatch — a malicious or
|
|
402
|
+
// buggy node must not be able to silently substitute a different transaction.
|
|
403
|
+
if (expectedHash != null && typeof hash === "string" && hash.length > 0) {
|
|
404
|
+
if (hash.toLowerCase() !== expectedHash) {
|
|
405
|
+
throw makeError("RPC node returned a transaction hash that does not match the signed transaction", "UNKNOWN_ERROR", {
|
|
406
|
+
operation: "sendTransaction",
|
|
407
|
+
expected: expectedHash,
|
|
408
|
+
got: hash,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
309
413
|
// Fetch back transaction (best-effort)
|
|
310
414
|
const result = await this.getTransaction(hash);
|
|
415
|
+
|
|
416
|
+
// Verify the fetched-back transaction's hash too, so a node cannot return
|
|
417
|
+
// a fabricated transaction object under the correct hash lookup.
|
|
418
|
+
if (result && expectedHash != null && typeof result.hash === "string" && result.hash.toLowerCase() !== expectedHash) {
|
|
419
|
+
throw makeError("RPC node returned a transaction whose hash does not match the signed transaction", "UNKNOWN_ERROR", {
|
|
420
|
+
operation: "sendTransaction",
|
|
421
|
+
expected: expectedHash,
|
|
422
|
+
got: result.hash,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
311
426
|
return result || new TransactionResponse({ hash }, this);
|
|
312
427
|
}
|
|
313
428
|
|
|
@@ -376,7 +491,25 @@ class AbstractProvider extends Provider {
|
|
|
376
491
|
if (fromBlock !== undefined) normalized.fromBlock = fromBlock;
|
|
377
492
|
if (toBlock !== undefined) normalized.toBlock = toBlock;
|
|
378
493
|
const logs = await this._perform("eth_getLogs", [normalized]);
|
|
379
|
-
|
|
494
|
+
const mapped = (logs || []).map((l) => new Log(l, this));
|
|
495
|
+
|
|
496
|
+
// A malicious node could return logs emitted by contracts other than the
|
|
497
|
+
// one(s) requested. When the filter constrains the address, drop any log whose
|
|
498
|
+
// address is not in that set so foreign-contract events cannot be injected.
|
|
499
|
+
const allowed = _normalizeAddressFilter(filter.address);
|
|
500
|
+
// A malicious node could return logs for a different event (different
|
|
501
|
+
// topic0) or from foreign contracts. Drop anything that does not match the
|
|
502
|
+
// requested address and topic constraints.
|
|
503
|
+
const hasTopicFilter = Array.isArray(filter.topics) && filter.topics.some((t) => t != null);
|
|
504
|
+
if (allowed || hasTopicFilter) {
|
|
505
|
+
return mapped.filter((l) => {
|
|
506
|
+
if (!l) return false;
|
|
507
|
+
if (allowed && !(typeof l.address === "string" && allowed.has(l.address.toLowerCase()))) return false;
|
|
508
|
+
if (hasTopicFilter && !_topicsMatch(l.topics, filter.topics)) return false;
|
|
509
|
+
return true;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
return mapped;
|
|
380
513
|
}
|
|
381
514
|
}
|
|
382
515
|
|
package/src/utils/rlp.js
CHANGED
|
@@ -103,9 +103,14 @@ function _encode(value) {
|
|
|
103
103
|
function _readLen(bytes, offset, lenOfLen) {
|
|
104
104
|
if (lenOfLen === 0) return 0;
|
|
105
105
|
if (offset + lenOfLen > bytes.length) throw new Error("RLP: insufficient data for length");
|
|
106
|
+
// Canonical encoding forbids a leading zero byte in the length field.
|
|
107
|
+
if (bytes[offset] === 0) throw new Error("RLP: non-canonical length (leading zero byte)");
|
|
108
|
+
// Accumulate with multiplication (not `<< 8`, which is a signed 32-bit op
|
|
109
|
+
// that overflows/wraps for 4+ byte lengths) and reject impossibly large lengths.
|
|
106
110
|
let len = 0;
|
|
107
111
|
for (let i = 0; i < lenOfLen; i++) {
|
|
108
|
-
len =
|
|
112
|
+
len = len * 256 + bytes[offset + i];
|
|
113
|
+
if (len > Number.MAX_SAFE_INTEGER) throw new Error("RLP: length exceeds maximum safe integer");
|
|
109
114
|
}
|
|
110
115
|
return len;
|
|
111
116
|
}
|
|
@@ -128,6 +133,9 @@ function _decode(bytes, start, end, depth) {
|
|
|
128
133
|
const dataStart = start + 1;
|
|
129
134
|
const dataEnd = dataStart + len;
|
|
130
135
|
if (dataEnd > end) throw new Error("RLP: out of bounds");
|
|
136
|
+
// Canonical encoding requires a single byte < 0x80 to be encoded as
|
|
137
|
+
// itself, never as a 1-byte string.
|
|
138
|
+
if (len === 1 && bytes[dataStart] < 0x80) throw new Error("RLP: non-canonical single byte encoding");
|
|
131
139
|
const out = bytesToHex(bytes.slice(dataStart, dataEnd));
|
|
132
140
|
return { value: out, next: dataEnd };
|
|
133
141
|
}
|
|
@@ -136,6 +144,8 @@ function _decode(bytes, start, end, depth) {
|
|
|
136
144
|
if (prefix <= 0xbf) {
|
|
137
145
|
const lenOfLen = prefix - 0xb7;
|
|
138
146
|
const len = _readLen(bytes, start + 1, lenOfLen);
|
|
147
|
+
// Lengths <= 55 must use the short-string form.
|
|
148
|
+
if (len <= 55) throw new Error("RLP: non-canonical long string (length fits short form)");
|
|
139
149
|
const dataStart = start + 1 + lenOfLen;
|
|
140
150
|
const dataEnd = dataStart + len;
|
|
141
151
|
if (dataEnd > end) throw new Error("RLP: out of bounds");
|
|
@@ -163,6 +173,8 @@ function _decode(bytes, start, end, depth) {
|
|
|
163
173
|
// Long list
|
|
164
174
|
const lenOfLen = prefix - 0xf7;
|
|
165
175
|
const len = _readLen(bytes, start + 1, lenOfLen);
|
|
176
|
+
// Lengths <= 55 must use the short-list form.
|
|
177
|
+
if (len <= 55) throw new Error("RLP: non-canonical long list (length fits short form)");
|
|
166
178
|
const dataStart = start + 1 + lenOfLen;
|
|
167
179
|
const dataEnd = dataStart + len;
|
|
168
180
|
if (dataEnd > end) throw new Error("RLP: out of bounds");
|