Skip to content

Commit ce6c205

Browse files
committed
fix(sorobanUtils): enum conversion of primitive types would invalid args
1 parent b726d6c commit ce6c205

File tree

1 file changed

+63
-38
lines changed

1 file changed

+63
-38
lines changed

src/debug/util/sorobanUtils.ts

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ const isMap = (arg: unknown) => {
221221
Array.isArray(arg) &&
222222
arg.every((obj: AnyObject) => {
223223
// Check if object has exactly two keys: "0" and "1"
224-
const keys = Object.keys(obj as object);
224+
const keys = Object.keys(obj);
225225
if (keys.length !== 2 || !keys.includes("0") || !keys.includes("1")) {
226226
return false;
227227
}
@@ -276,7 +276,6 @@ const getScValFromArg = (arg: unknown, scVals: xdr.ScVal[]): xdr.ScVal => {
276276

277277
return xdr.ScVal.scvVec(arrayScVals);
278278
}
279-
280279
return getScValsFromArgs({ arg: arg as AnyObject }, scVals || [])[0];
281280
};
282281

@@ -293,19 +292,27 @@ const convertEnumToScVal = (
293292
obj: Record<string, unknown>,
294293
scVals?: xdr.ScVal[],
295294
) => {
296-
// TUPLE CASE
295+
// Tuple Case
297296
if (obj.tag && obj.values) {
298297
const tagVal = nativeToScVal(obj.tag, { type: "symbol" });
299298
const valuesVal = convertValuesToScVals(
300299
obj.values as unknown[],
301300
scVals || [],
302301
);
303302
const tupleScValsVec = xdr.ScVal.scvVec([tagVal, ...valuesVal]);
304-
305303
return tupleScValsVec;
306304
}
307305

308-
// ENUM CASE
306+
// Enum Integer Variant Case
307+
if (obj.enum) {
308+
return nativeToScVal(obj.enum, { type: "u32" });
309+
}
310+
311+
if (!obj.tag) {
312+
// If no tag is present, we assume it's a primitive value
313+
return getScValFromArg(obj, scVals || []);
314+
}
315+
// Enum Case Unit Case
309316
const tagVec = [obj.tag];
310317
return nativeToScVal(tagVec, { type: "symbol" });
311318
};
@@ -407,12 +414,35 @@ const convertObjectToMap = (
407414

408415
return { mapVal, mapType };
409416
};
410-
411417
type TupleValue = {
412418
value: unknown;
413419
type: string;
414420
};
415421

422+
// Helper function to detect if string is base64 or hex
423+
const detectBytesEncoding = (value: string): "base64" | "hex" => {
424+
const hexRegex = /^[0-9a-fA-F]+$/;
425+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
426+
427+
if (hexRegex.test(value) && value.length % 2 === 0) {
428+
return "hex";
429+
}
430+
431+
if (base64Regex.test(value) && value.length % 4 === 0) {
432+
try {
433+
const decoded = Buffer.from(value, "base64");
434+
return decoded.toString("base64").replace(/=+$/, "") ===
435+
value.replace(/=+$/, "")
436+
? "base64"
437+
: "hex";
438+
} catch {
439+
return "hex";
440+
}
441+
}
442+
443+
return "base64";
444+
};
445+
416446
const convertTupleToScVal = (tupleArray: TupleValue[]) => {
417447
const tupleScVals = tupleArray.map((v) => {
418448
if (v.type === "bool") {
@@ -431,45 +461,46 @@ const convertTupleToScVal = (tupleArray: TupleValue[]) => {
431461
};
432462

433463
type PrimitiveArg = { type: string; value: unknown };
434-
type EnumArg = { tag: string; values?: unknown[] };
435464

436465
const getScValFromPrimitive = (v: PrimitiveArg) => {
437466
if (v.type === "bool") {
438-
const boolValue = v.value === "true";
467+
const boolValue = v.value === "true" ? true : false;
439468
return nativeToScVal(boolValue);
440469
}
441470
if (v.type === "bytes" && typeof v.value === "string") {
442-
return nativeToScVal(new Uint8Array(Buffer.from(v.value, "base64")));
471+
const encoding = detectBytesEncoding(v.value);
472+
return nativeToScVal(new Uint8Array(Buffer.from(v.value, encoding)));
443473
}
444474
return nativeToScVal(v.value, { type: v.type });
445475
};
446476

447-
const getScValsFromArgs = (
477+
export const getScValsFromArgs = (
448478
args: SorobanInvokeValue["args"],
449479
scVals: xdr.ScVal[] = [],
450480
): xdr.ScVal[] => {
451-
// PRIMITIVE CASE
481+
// Primitive Case
452482
if (
453483
Object.values(args).every(
454-
(v): v is PrimitiveArg =>
455-
typeof v === "object" && v !== null && "type" in v && "value" in v,
484+
(v: unknown) => (v as AnyObject).type && (v as AnyObject).value,
456485
)
457486
) {
458-
const primitiveScVals = Object.values(args).map((v) =>
459-
getScValFromPrimitive(v as PrimitiveArg),
460-
);
487+
const primitiveScVals = Object.values(args).map((v) => {
488+
return getScValFromPrimitive(v as PrimitiveArg);
489+
});
490+
461491
return primitiveScVals;
462492
}
463493

464-
// ENUM (VOID AND COMPLEX ONE LIKE TUPLE) CASE
494+
// Enum (Void and Complex One Like Tuple) Case
465495
if (
466496
Object.values(args).some(
467-
(v): v is EnumArg => typeof v === "object" && v !== null && "tag" in v,
497+
(v: unknown) => (v as AnyObject).tag || (v as AnyObject).enum,
468498
)
469499
) {
470-
const enumScVals = Object.values(args).map((v) =>
471-
convertEnumToScVal(v as EnumArg, scVals),
472-
);
500+
const enumScVals = Object.values(args).map((v) => {
501+
return convertEnumToScVal(v as AnyObject, scVals);
502+
});
503+
473504
return enumScVals;
474505
}
475506

@@ -478,15 +509,15 @@ const getScValsFromArgs = (
478509

479510
// Check if it's an array of map objects
480511
if (Array.isArray(argValue)) {
481-
// MAP CASE
512+
// Map Case
482513
if (isMap(argValue)) {
483514
const { mapVal, mapType } = convertObjectToMap(argValue);
484515
const mapScVal = nativeToScVal(mapVal, { type: mapType });
485516
scVals.push(mapScVal);
486517
return scVals;
487518
}
488519

489-
// VEC CASE #1: array of objects or complicated tuple case
520+
// Vec Case #1: array of objects or complicated tuple case
490521
if (argValue.some((v) => typeof Object.values(v)[0] === "object")) {
491522
const arrayScVals = argValue.map((v) => {
492523
if (v.tag) {
@@ -501,7 +532,7 @@ const getScValsFromArgs = (
501532
return scVals;
502533
}
503534

504-
// VEC CASE #2: array of primitives
535+
// Vec Case #2: array of primitives
505536
const isVecArray = argValue.every((v) => {
506537
return v.type === argValue[0].type;
507538
});
@@ -511,7 +542,8 @@ const getScValsFromArgs = (
511542
if (v.type === "bool") {
512543
acc.push(v.value === "true" ? true : false);
513544
} else if (v.type === "bytes") {
514-
acc.push(new Uint8Array(Buffer.from(v.value, "base64")));
545+
const encoding = detectBytesEncoding(v.value);
546+
acc.push(new Uint8Array(Buffer.from(v.value, encoding)));
515547
} else {
516548
acc.push(v.value);
517549
}
@@ -526,8 +558,8 @@ const getScValsFromArgs = (
526558
return scVals;
527559
}
528560

529-
// TUPLE CASE
530-
const isTupleArray = argValue.every((v: AnyObject) => v.type && v.value);
561+
// Tuple Case
562+
const isTupleArray = argValue.every((v) => v.type && v.value);
531563
if (isTupleArray) {
532564
const tupleScValsVec = convertTupleToScVal(argValue);
533565

@@ -536,25 +568,18 @@ const getScValsFromArgs = (
536568
}
537569
}
538570

539-
// OBJECT CASE
571+
// Object Case
540572
if (
541-
Object.values(argValue as object).every(
542-
(v: AnyObject) => v.type && v.value,
573+
Object.values(argValue as AnyObject).every(
574+
(v) => (v as AnyObject).type && (v as AnyObject).value,
543575
)
544576
) {
545577
const convertedObj = convertObjectToScVal(argValue as AnyObject);
546578
scVals.push(nativeToScVal(convertedObj));
547579
return scVals;
548580
}
549581

550-
if (
551-
typeof argValue === "object" &&
552-
argValue &&
553-
"type" in argValue &&
554-
"value" in argValue &&
555-
argValue.type &&
556-
argValue.value
557-
) {
582+
if ((argValue as AnyObject).type && (argValue as AnyObject).value) {
558583
scVals.push(getScValFromPrimitive(argValue as PrimitiveArg));
559584
}
560585
}

0 commit comments

Comments
 (0)