-
Notifications
You must be signed in to change notification settings - Fork 9
fix: enum conversion of primitive types would invalidate args #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -221,7 +221,7 @@ const isMap = (arg: unknown) => { | |
| Array.isArray(arg) && | ||
| arg.every((obj: AnyObject) => { | ||
| // Check if object has exactly two keys: "0" and "1" | ||
| const keys = Object.keys(obj as object); | ||
| const keys = Object.keys(obj); | ||
| if (keys.length !== 2 || !keys.includes("0") || !keys.includes("1")) { | ||
| return false; | ||
| } | ||
|
|
@@ -276,7 +276,6 @@ const getScValFromArg = (arg: unknown, scVals: xdr.ScVal[]): xdr.ScVal => { | |
|
|
||
| return xdr.ScVal.scvVec(arrayScVals); | ||
| } | ||
|
|
||
| return getScValsFromArgs({ arg: arg as AnyObject }, scVals || [])[0]; | ||
| }; | ||
|
|
||
|
|
@@ -293,19 +292,27 @@ const convertEnumToScVal = ( | |
| obj: Record<string, unknown>, | ||
| scVals?: xdr.ScVal[], | ||
| ) => { | ||
| // TUPLE CASE | ||
| // Tuple Case | ||
| if (obj.tag && obj.values) { | ||
| const tagVal = nativeToScVal(obj.tag, { type: "symbol" }); | ||
| const valuesVal = convertValuesToScVals( | ||
| obj.values as unknown[], | ||
| scVals || [], | ||
| ); | ||
| const tupleScValsVec = xdr.ScVal.scvVec([tagVal, ...valuesVal]); | ||
|
|
||
| return tupleScValsVec; | ||
| } | ||
|
|
||
| // ENUM CASE | ||
| // Enum Integer Variant Case | ||
| if (obj.enum) { | ||
| return nativeToScVal(obj.enum, { type: "u32" }); | ||
| } | ||
|
|
||
| if (!obj.tag) { | ||
| // If no tag is present, we assume it's a primitive value | ||
| return getScValFromArg(obj, scVals || []); | ||
| } | ||
| // Enum Case Unit Case | ||
| const tagVec = [obj.tag]; | ||
| return nativeToScVal(tagVec, { type: "symbol" }); | ||
| }; | ||
|
|
@@ -407,12 +414,35 @@ const convertObjectToMap = ( | |
|
|
||
| return { mapVal, mapType }; | ||
| }; | ||
|
|
||
| type TupleValue = { | ||
| value: unknown; | ||
| type: string; | ||
| }; | ||
|
|
||
| // Helper function to detect if string is base64 or hex | ||
| const detectBytesEncoding = (value: string): "base64" | "hex" => { | ||
| const hexRegex = /^[0-9a-fA-F]+$/; | ||
| const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/; | ||
|
|
||
| if (hexRegex.test(value) && value.length % 2 === 0) { | ||
| return "hex"; | ||
| } | ||
|
|
||
| if (base64Regex.test(value) && value.length % 4 === 0) { | ||
| try { | ||
| const decoded = Buffer.from(value, "base64"); | ||
| return decoded.toString("base64").replace(/=+$/, "") === | ||
| value.replace(/=+$/, "") | ||
| ? "base64" | ||
| : "hex"; | ||
| } catch { | ||
| return "hex"; | ||
| } | ||
| } | ||
|
|
||
| return "base64"; | ||
| }; | ||
|
|
||
| const convertTupleToScVal = (tupleArray: TupleValue[]) => { | ||
| const tupleScVals = tupleArray.map((v) => { | ||
| if (v.type === "bool") { | ||
|
|
@@ -431,45 +461,46 @@ const convertTupleToScVal = (tupleArray: TupleValue[]) => { | |
| }; | ||
|
|
||
| type PrimitiveArg = { type: string; value: unknown }; | ||
| type EnumArg = { tag: string; values?: unknown[] }; | ||
|
|
||
| const getScValFromPrimitive = (v: PrimitiveArg) => { | ||
| if (v.type === "bool") { | ||
| const boolValue = v.value === "true"; | ||
| const boolValue = v.value === "true" ? true : false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you gain anything from adding this ternary? I wouldn't block on asking it to be removed, but I'm curious. |
||
| return nativeToScVal(boolValue); | ||
| } | ||
| if (v.type === "bytes" && typeof v.value === "string") { | ||
| return nativeToScVal(new Uint8Array(Buffer.from(v.value, "base64"))); | ||
| const encoding = detectBytesEncoding(v.value); | ||
| return nativeToScVal(new Uint8Array(Buffer.from(v.value, encoding))); | ||
| } | ||
| return nativeToScVal(v.value, { type: v.type }); | ||
| }; | ||
|
|
||
| const getScValsFromArgs = ( | ||
| export const getScValsFromArgs = ( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be exported? |
||
| args: SorobanInvokeValue["args"], | ||
| scVals: xdr.ScVal[] = [], | ||
| ): xdr.ScVal[] => { | ||
| // PRIMITIVE CASE | ||
| // Primitive Case | ||
| if ( | ||
| Object.values(args).every( | ||
| (v): v is PrimitiveArg => | ||
| typeof v === "object" && v !== null && "type" in v && "value" in v, | ||
| (v: unknown) => (v as AnyObject).type && (v as AnyObject).value, | ||
| ) | ||
| ) { | ||
| const primitiveScVals = Object.values(args).map((v) => | ||
| getScValFromPrimitive(v as PrimitiveArg), | ||
| ); | ||
| const primitiveScVals = Object.values(args).map((v) => { | ||
| return getScValFromPrimitive(v as PrimitiveArg); | ||
| }); | ||
|
|
||
| return primitiveScVals; | ||
| } | ||
|
|
||
| // ENUM (VOID AND COMPLEX ONE LIKE TUPLE) CASE | ||
| // Enum (Void and Complex One Like Tuple) Case | ||
| if ( | ||
| Object.values(args).some( | ||
| (v): v is EnumArg => typeof v === "object" && v !== null && "tag" in v, | ||
| (v: unknown) => (v as AnyObject).tag || (v as AnyObject).enum, | ||
| ) | ||
| ) { | ||
| const enumScVals = Object.values(args).map((v) => | ||
| convertEnumToScVal(v as EnumArg, scVals), | ||
| ); | ||
| const enumScVals = Object.values(args).map((v) => { | ||
| return convertEnumToScVal(v as AnyObject, scVals); | ||
| }); | ||
|
|
||
| return enumScVals; | ||
| } | ||
|
|
||
|
|
@@ -478,15 +509,15 @@ const getScValsFromArgs = ( | |
|
|
||
| // Check if it's an array of map objects | ||
| if (Array.isArray(argValue)) { | ||
| // MAP CASE | ||
| // Map Case | ||
| if (isMap(argValue)) { | ||
| const { mapVal, mapType } = convertObjectToMap(argValue); | ||
| const mapScVal = nativeToScVal(mapVal, { type: mapType }); | ||
| scVals.push(mapScVal); | ||
| return scVals; | ||
| } | ||
|
|
||
| // VEC CASE #1: array of objects or complicated tuple case | ||
| // Vec Case #1: array of objects or complicated tuple case | ||
| if (argValue.some((v) => typeof Object.values(v)[0] === "object")) { | ||
| const arrayScVals = argValue.map((v) => { | ||
| if (v.tag) { | ||
|
|
@@ -501,7 +532,7 @@ const getScValsFromArgs = ( | |
| return scVals; | ||
| } | ||
|
|
||
| // VEC CASE #2: array of primitives | ||
| // Vec Case #2: array of primitives | ||
| const isVecArray = argValue.every((v) => { | ||
| return v.type === argValue[0].type; | ||
| }); | ||
|
|
@@ -511,7 +542,8 @@ const getScValsFromArgs = ( | |
| if (v.type === "bool") { | ||
| acc.push(v.value === "true" ? true : false); | ||
| } else if (v.type === "bytes") { | ||
| acc.push(new Uint8Array(Buffer.from(v.value, "base64"))); | ||
| const encoding = detectBytesEncoding(v.value); | ||
| acc.push(new Uint8Array(Buffer.from(v.value, encoding))); | ||
| } else { | ||
| acc.push(v.value); | ||
| } | ||
|
|
@@ -526,8 +558,8 @@ const getScValsFromArgs = ( | |
| return scVals; | ||
| } | ||
|
|
||
| // TUPLE CASE | ||
| const isTupleArray = argValue.every((v: AnyObject) => v.type && v.value); | ||
| // Tuple Case | ||
| const isTupleArray = argValue.every((v) => v.type && v.value); | ||
| if (isTupleArray) { | ||
| const tupleScValsVec = convertTupleToScVal(argValue); | ||
|
|
||
|
|
@@ -536,25 +568,18 @@ const getScValsFromArgs = ( | |
| } | ||
| } | ||
|
|
||
| // OBJECT CASE | ||
| // Object Case | ||
| if ( | ||
| Object.values(argValue as object).every( | ||
| (v: AnyObject) => v.type && v.value, | ||
| Object.values(argValue as AnyObject).every( | ||
| (v) => (v as AnyObject).type && (v as AnyObject).value, | ||
| ) | ||
| ) { | ||
| const convertedObj = convertObjectToScVal(argValue as AnyObject); | ||
| scVals.push(nativeToScVal(convertedObj)); | ||
| return scVals; | ||
| } | ||
|
|
||
| if ( | ||
| typeof argValue === "object" && | ||
| argValue && | ||
| "type" in argValue && | ||
| "value" in argValue && | ||
| argValue.type && | ||
| argValue.value | ||
| ) { | ||
| if ((argValue as AnyObject).type && (argValue as AnyObject).value) { | ||
|
Comment on lines
-550
to
+582
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it all works, great, but I'm wary of all these changes from type guards to casting. Normally, I try to avoid using the |
||
| scVals.push(getScValFromPrimitive(argValue as PrimitiveArg)); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These lines should be equivalent.