Repository: 0xs34n/starknet.js (ts)

Issue:
decodeShortString : wrong answer of the function
**Describe the bug**
Hello,
I noted a problem with the function `utils/shortstrings.ts/decodeShortString`. 
If I use it with an hex string ('0x321....456'), everything is fine.
But if the str:string is  an integer string ('1542233...56'), the function accept it, process without fail, and send back a wrong answer.
```typescript
export function decodeShortString(str: string) {
  return removeHexPrefix(str).replace(/.{2}/g, (hex) => String.fromCharCode(parseInt(hex, 16)));
}
```

**To Reproduce the problem :**
```typescript
const myText = "decimal number fail!";
console.log("myText =", myText);
const encodedText: string = shortString.encodeShortString(myText);
console.log("encodedText =", encodedText);
const myTextDecoded = shortString.decodeShortString(encodedText);
console.log("finalString =", myTextDecoded);
const decimalEncoded: string = BigInt(encodedText).toString(10);
console.log("decimalEncoded =", decimalEncoded);
const wrongTextDecoded : string= shortString.decodeShortString(decimalEncoded);
console.log("wrongTextDecoded =", wrongTextDecoded);
```
Result : 
```
myText = decimal number fail!
encodedText = 0x646563696d616c206e756d626572206661696c21
finalString = decimal number fail!
decimalEncoded = 573160112338783013729123589963347449102752902177
wrongTextDecoded = W1`#8x0r#Xc4tI'R
```


**Expected behavior**
This function should handle properly both formats.

**Screenshots**
N/A

**Desktop (please complete the following information):**

- Browser & version : N/A
- Node version : "ts-node": "^10.9.1"
- StarkNet.js version : 4.21.0
- Network [ : N/A

**Additional context**
A PR is ready to send.


Pull request description:
decodeShortString was not handling properly Integer Strings
Fix #501 

## Motivation and Resolution

The function decodeShortString returns a wrong answer  if the input is a string representing an integer.
See issue #501 for example of bug.

Now, the answer is correct

## Usage related changes

No change for users ; just a correct answer of the function.
Guides have been updated.

## Development related changes

Test of this function has been updated. Please check that usage of `toMach` is correct (/__tests__/utils/shortString.test.ts, line 24).

## Checklist:

- [X] Performed a self-review of the code
- [X] Rebased to the last commit of the target branch (or merged it into my branch)
- [X] Linked the issues which this PR resolves
- [X] Updated the docs (www)
- [X] Updated the tests
- [X] All tests are passing


Patch:
diff --git a/src/utils/shortString.ts b/src/utils/shortString.ts
index c597a6f2..2a682ec7 100644
--- a/src/utils/shortString.ts
+++ b/src/utils/shortString.ts
@@ -1,4 +1,5 @@
 import { addHexPrefix, removeHexPrefix } from './encode';
+import { isHex } from './number';
 
 export function isASCII(str: string) {
   // eslint-disable-next-line no-control-regex
@@ -10,12 +11,47 @@ export function isShortString(str: string) {
   return str.length <= 31;
 }
 
-export function encodeShortString(str: string) {
+// function to check if string is a decimal
+export function isDecimalString(decim: string): boolean {
+  return /^[0-9]*$/i.test(decim);
+}
+
+/**
+ * Convert an ASCII string to an hexadecimal string.
+ * @param str - ASCII string -
+ * 31 characters maxi. Ex : "uri/item23.jpg"
+ * @returns a string representing an Hex number 248 bits max.
+ * @Example
+ * ```typescript
+ * const myEncodedString: string = encodeShortString("uri/pict/t38.jpg");
+ * ```
+ * returns : string : "0x7572692f706963742f7433382e6a7067"
+ */
+export function encodeShortString(str: string): string {
   if (!isASCII(str)) throw new Error(`${str} is not an ASCII string`);
   if (!isShortString(str)) throw new Error(`${str} is too long`);
   return addHexPrefix(str.replace(/./g, (char) => char.charCodeAt(0).toString(16)));
 }
 
-export function decodeShortString(str: string) {
-  return removeHexPrefix(str).replace(/.{2}/g, (hex) => String.fromCharCode(parseInt(hex, 16)));
+/**
+ * Convert an hexadecimal or decimal string to an ASCII string.
+ * @param str - string - representing a 248 bits max number.
+ *
+ * Ex : hex : "0x1A4F64EA56" or decimal : "236942575435676423"
+ * @returns a string with 31 characters max.
+ * @Example
+ * ```typescript
+ * const myDecodedString: string = decodeShortString("0x7572692f706963742f7433382e6a7067");
+ * ```
+ * return : string : "uri/pict/t38.jpg"
+ */
+export function decodeShortString(str: string): string {
+  if (!isASCII(str)) throw new Error(`${str} is not an ASCII string`);
+  if (isHex(str)) {
+    return removeHexPrefix(str).replace(/.{2}/g, (hex) => String.fromCharCode(parseInt(hex, 16)));
+  }
+  if (isDecimalString(str)) {
+    return decodeShortString('0X'.concat(BigInt(str).toString(16)));
+  }
+  throw new Error(`${str} is not Hex or decimal`);
 }
diff --git a/www/docs/guides/define_call_message.md b/www/docs/guides/define_call_message.md
index c5b18476..8c50158f 100644
--- a/www/docs/guides/define_call_message.md
+++ b/www/docs/guides/define_call_message.md
@@ -202,3 +202,62 @@ When you perform a call, you have the result in an object :
 | Uint256 (256 bits max)                | `func getV()->(balance:Uint256)`           | BN                                | `const balance = uint256.uint256toBN(result.balance)`              |
 | array of felt                         | `func getV()->(list_len:felt, list:felt*)` | BN[]                              | `const list= result.list`                                          |
 | shortString (31 ASCII characters max) | `func getV()->(title:felt)`                | string                            | `const title:string = shortString.decodeShortString(result.title)` |
+
+## Handle Strings :
+
+In Javascript/Typecript, the max length of a string is nearly limitless. In Cairo, a string is limited to only 31 characters, and is called a ShortString.
+
+### Encode ShortString :
+
+From JS to Cairo, you need to encode this ShortString to a number on 248 bits :
+
+```typescript
+const myText = "uri/pict/t38.jpg"; // 31 chars max
+const encodedText: string = shortString.encodeShortString(myText);
+```
+
+the result is Hex number string : "0x7572692f706963742f7433382e6a7067"
+
+### Decode ShortString :
+
+From Cairo to JS, you need to decode a BN (big number) to a string of 31 character max.
+
+```typescript
+const myShortString= new BN("156113730760229877043789998731456835687"); // or result of a Contract.call
+const myTextDecoded = shortString.decodeShortString(myShortString);
+```
+
+the result is : "uri/pict/t38.jpg"
+
+### LongString
+
+How to handle a string with more than 31 characters :
+
+1. The Cairo contract has to manage this string as array of ShortString (array of felt).
+2. The JS code has to split/encode the string before call/invoke.
+3. The JS code has to decode/merge the BNs received from a call.
+
+```typescript
+function splitString(myString: string): string[] {
+    const myShortStrings: string[] = [];
+    while (myString.length > 0) {
+        myShortStrings.push(myString.slice(0, 31));
+        myString = myString.slice(31);
+    }
+    return (myShortStrings);
+}
+let myString = "uri:myProject/atosmotor/recurr/monkey148.jpg";
+// encoding
+const myShortStrings = splitString(myString);
+const myShortStringsEncoded = myShortStrings.map((shortStr) => {
+    return shortString.encodeShortString(shortStr)
+}); // to use as input in call/invoke/deploy
+
+// decoding from a call
+// receiving a BN[]
+const stringsCoded: BN[] = result.token_uri;
+const myShortStringsDecoded = stringsCoded.map((shortStr: BN) => {
+    return shortString.decodeShortString(shortStr.toString())
+});
+const finalString = myShortStringsDecoded.join("");
+```


Test patch:
diff --git a/__tests__/utils/shortString.test.ts b/__tests__/utils/shortString.test.ts
index e6262e01..3be94903 100644
--- a/__tests__/utils/shortString.test.ts
+++ b/__tests__/utils/shortString.test.ts
@@ -4,19 +4,38 @@ describe('shortString', () => {
   test('should convert string to number', () => {
     expect(encodeShortString('hello')).toMatchInlineSnapshot(`"0x68656c6c6f"`);
   });
-  test('should convert number to string', () => {
-    expect(decodeShortString('0x68656c6c6f')).toMatchInlineSnapshot(`"hello"`);
-  });
-  test('should throw if string is too long', () => {
+
+  test('should throw if string to encode is too long', () => {
     expect(() =>
       encodeShortString('hello world hello world hello world hello world hello world hello world')
     ).toThrowErrorMatchingInlineSnapshot(
       `"hello world hello world hello world hello world hello world hello world is too long"`
     );
   });
-  test('should throw if string contains non ascii chars', () => {
+
+  test('should throw if string to encode contains non ascii chars', () => {
     expect(() => encodeShortString('hello\uD83D\uDE00')).toThrowErrorMatchingInlineSnapshot(
       `"hello😀 is not an ASCII string"`
     );
   });
+
+  test('should convert hex number string to string', () => {
+    expect(decodeShortString('0x68656c6c6f')).toMatchInlineSnapshot(`"hello"`);
+  });
+
+  test('should convert decimal number string to string', () => {
+    expect(decodeShortString('448378203247')).toMatch(`hello`);
+  });
+
+  test('should throw if decode input is not ascii chars', () => {
+    expect(() => decodeShortString('hello\uD83D\uDE00')).toThrowErrorMatchingInlineSnapshot(
+      `"hello😀 is not an ASCII string"`
+    );
+  });
+
+  test('should throw if decode input is not Hex string or numerical string', () => {
+    expect(() => decodeShortString('Test')).toThrowErrorMatchingInlineSnapshot(
+      `"Test is not Hex or decimal"`
+    );
+  });
 });