Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions src/commands/contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import {CodeAction, CodeOptions} from "./code";
import {EstimateFeesAction, EstimateFeesOptions} from "./estimateFees";
import {addWalletModeOption} from "../../lib/wallet/walletOption";

const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
const ADDR_PREFIX_RE = /^addr#([0-9a-fA-F]{40})$/;
const BYTES_PREFIX_RE = /^b#([0-9a-fA-F]*)$/;
const HEX_RE = /^0x[0-9a-fA-F]+$/;
const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/i;
const ADDR_PREFIX_RE = /^addr#([0-9a-fA-F]{40})$/i;
const BYTES_PREFIX_RE = /^b#([0-9a-fA-F]*)$/i;
const HEX_RE = /^0x[0-9a-fA-F]+$/i;

function hexToBytes(hex: string): Uint8Array {
const bytes = new Uint8Array(hex.length / 2);
const bytes = new Uint8Array(Math.floor(hex.length / 2));
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
Expand Down Expand Up @@ -50,11 +50,17 @@ export function parseScalar(value: string): unknown {
if (ADDRESS_RE.test(value)) return new CalldataAddress(hexToBytes(value.slice(2)));

const bytesMatch = value.match(BYTES_PREFIX_RE);
if (bytesMatch) return hexToBytes(bytesMatch[1]);
if (bytesMatch) {
if (bytesMatch[1].length % 2 !== 0) {
return value;
}
return hexToBytes(bytesMatch[1]);
}

if (HEX_RE.test(value)) return BigInt(value);
if (!isNaN(Number(value)) && Number.isSafeInteger(Number(value))) return Number(value);
if (!isNaN(Number(value))) return BigInt(value);
if (value.trim() !== "" && !isNaN(Number(value)) && Number.isSafeInteger(Number(value)))
return Number(value);
if (value.trim() !== "" && !isNaN(Number(value))) return BigInt(value);

return value;
}
Expand Down Expand Up @@ -122,14 +128,14 @@ export function initializeContractsCommands(program: Command) {
.option("--valid-until <unixTimestamp>", "Unix timestamp after which the transaction is invalid")
.option("--args <args...>", ARGS_HELP, parseArg, []),
).action(async (options: DeployOptions) => {
const deployer = new DeployAction();
if (options.contract) {
await deployer.deploy(options);
} else {
const deployScriptsOptions: DeployScriptsOptions = {rpc: options.rpc};
await deployer.deployScripts(deployScriptsOptions);
}
});
const deployer = new DeployAction();
if (options.contract) {
await deployer.deploy(options);
} else {
const deployScriptsOptions: DeployScriptsOptions = {rpc: options.rpc};
await deployer.deployScripts(deployScriptsOptions);
}
});

program
.command("call <contractAddress> <method>")
Expand All @@ -154,9 +160,9 @@ export function initializeContractsCommands(program: Command) {
.option("--valid-until <unixTimestamp>", "Unix timestamp after which the transaction is invalid")
.option("--args <args...>", ARGS_HELP, parseArg, []),
).action(async (contractAddress: string, method: string, options: WriteOptions) => {
const writeAction = new WriteAction();
await writeAction.write({contractAddress, method, ...options});
});
const writeAction = new WriteAction();
await writeAction.write({contractAddress, method, ...options});
});

program
.command("estimate-fees [contractAddress] [method]")
Expand Down
34 changes: 34 additions & 0 deletions tests/commands/parseArg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ describe("parseScalar", () => {
expect(parseScalar("someString")).toBe("someString");
});

test("parses empty string and whitespace strings without numeric coercion to 0", () => {
expect(parseScalar("")).toBe("");
expect(parseScalar(" ")).toBe(" ");
expect(parseScalar("\t")).toBe("\t");
});

test("parses uppercase hex and addresses case-insensitively", () => {
expect(parseScalar("0X1234")).toBe(BigInt("0x1234"));
const upperAddr = parseScalar("0X6857Ed54CbafaA74Fc0357145eC0ee1536ca45A0");
expect(upperAddr).toBeInstanceOf(CalldataAddress);
const prefixAddr = parseScalar("ADDR#6857Ed54CbafaA74Fc0357145eC0ee1536ca45A0");
expect(prefixAddr).toBeInstanceOf(CalldataAddress);
});

test("handles odd-length byte prefix without byte truncation", () => {
expect(parseScalar("b#abc")).toBe("b#abc");
expect(parseScalar("B#deadbeef")).toBeInstanceOf(Uint8Array);
});

test("does not treat invalid hex as address", () => {
const invalid = "0xZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
expect(parseScalar(invalid)).toBe(invalid);
Expand Down Expand Up @@ -103,6 +122,11 @@ describe("coerceValue", () => {
const result = coerceValue(["0x6857Ed54CbafaA74Fc0357145eC0ee1536ca45A0"]) as any[];
expect(result[0]).toBeInstanceOf(CalldataAddress);
});

test("preserves empty strings in objects and arrays without coercing to zero", () => {
expect(coerceValue({name: ""})).toEqual({name: ""});
expect(coerceValue(["", "non-empty"])).toEqual(["", "non-empty"]);
});
});

describe("parseArg", () => {
Expand All @@ -112,6 +136,11 @@ describe("parseArg", () => {
expect(parseArg("hello")).toEqual(["hello"]);
});

test("parses empty string argument as a string rather than zero", () => {
expect(parseArg("")).toEqual([""]);
expect(parseArg(" ")).toEqual([" "]);
});

test("parses null", () => {
expect(parseArg("null")).toEqual([null]);
});
Expand All @@ -126,6 +155,11 @@ describe("parseArg", () => {
expect(result).toEqual([{key: "value", n: 42}]);
});

test("parses JSON with empty strings preserving string types", () => {
const result = parseArg('{"key": "", "count": 0}');
expect(result).toEqual([{key: "", count: 0}]);
});

test("parses nested JSON with address strings", () => {
const result = parseArg('{"to": "0x6857Ed54CbafaA74Fc0357145eC0ee1536ca45A0", "amount": 100}');
expect(result).toHaveLength(1);
Expand Down