@@ -20,16 +20,16 @@ library ICS20Lib {
2020 bytes internal constant FAILED_ACKNOWLEDGEMENT_JSON = bytes ('{"error":"failed"} ' );
2121 bytes32 internal constant KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON = keccak256 (SUCCESSFUL_ACKNOWLEDGEMENT_JSON);
2222
23- uint256 private constant CHAR_DOUBLE_QUOTE = 0x22 ;
24- uint256 private constant CHAR_SLASH = 0x2f ;
23+ uint256 private constant CHAR_DOUBLE_QUOTE = 0x22 ; // '"'
24+ uint256 private constant CHAR_SLASH = 0x2f ; // "/"
2525 uint256 private constant CHAR_BACKSLASH = 0x5c ;
26- uint256 private constant CHAR_F = 0x66 ;
27- uint256 private constant CHAR_R = 0x72 ;
28- uint256 private constant CHAR_N = 0x6e ;
29- uint256 private constant CHAR_B = 0x62 ;
30- uint256 private constant CHAR_T = 0x74 ;
31- uint256 private constant CHAR_CLOSING_BRACE = 0x7d ;
32- uint256 private constant CHAR_M = 0x6d ;
26+ uint256 private constant CHAR_F = 0x66 ; // "f"
27+ uint256 private constant CHAR_R = 0x72 ; // "r"
28+ uint256 private constant CHAR_N = 0x6e ; // "n"
29+ uint256 private constant CHAR_B = 0x62 ; // "b"
30+ uint256 private constant CHAR_T = 0x74 ; // "t"
31+ uint256 private constant CHAR_CLOSING_BRACE = 0x7d ; // "}"
32+ uint256 private constant CHAR_M = 0x6d ; // "m"
3333
3434 bytes16 private constant HEX_DIGITS = "0123456789abcdef " ;
3535
@@ -47,6 +47,11 @@ library ICS20Lib {
4747
4848 /**
4949 * @dev marshalJSON marshals PacketData into JSON bytes with escaping.
50+ * @param escapedDenom is the denom string escaped.
51+ * @param amount is the amount field.
52+ * @param escapedSender is the sender string escaped.
53+ * @param escapedReceiver is the receiver string escaped.
54+ * @param escapedMemo is the memo string escaped.
5055 */
5156 function marshalJSON (
5257 string memory escapedDenom ,
@@ -72,6 +77,10 @@ library ICS20Lib {
7277
7378 /**
7479 * @dev marshalJSON marshals PacketData into JSON bytes with escaping.
80+ * @param escapedDenom is the denom string escaped.
81+ * @param amount is the amount field.
82+ * @param escapedSender is the sender string escaped.
83+ * @param escapedReceiver is the receiver string escaped.
7584 */
7685 function marshalJSON (
7786 string memory escapedDenom ,
@@ -94,12 +103,16 @@ library ICS20Lib {
94103
95104 /**
96105 * @dev unmarshalJSON unmarshals JSON bytes into PacketData.
106+ * @param bz the JSON bytes to unmarshal. It must be either of the following JSON formats. It is assumed that string fields are escaped.
107+ * 1. {"amount":"<uint256>","denom":"<string>","memo":"<string>","receiver":"<string>","sender":"<string>"}
108+ * 2. {"amount":"<uint256>","denom":"<string>","receiver":"<string>","sender":"<string>"}
97109 */
98110 function unmarshalJSON (bytes calldata bz ) internal pure returns (PacketData memory ) {
99111 PacketData memory pd;
100112 uint256 pos = 0 ;
101113
102114 unchecked {
115+ // SAFETY: `pos` never overflow because it is always less than `bz.length`.
103116 if (bytes32 (bz[pos:pos + 11 ]) != bytes32 ('{"amount":" ' )) {
104117 revert IICS20Errors.ICS20JSONUnexpectedBytes (pos, bytes32 ('{"amount":" ' ), bytes32 (bz[pos:pos + 11 ]));
105118 }
@@ -135,35 +148,51 @@ library ICS20Lib {
135148 }
136149
137150 /**
138- * @dev parseUint256String parses `bz` from a position `pos` to produce a uint256.
151+ * @dev parseUint256String parses `bz` from a position `pos` to produce a uint256 value.
152+ * The parse will stop parsing when it encounters a non-digit character.
153+ * @param bz the byte array to parse.
154+ * @param pos the position to start parsing.
155+ * @return ret the parsed uint256 value.
156+ * @return pos the new position after parsing.
139157 */
140158 function parseUint256String (bytes calldata bz , uint256 pos ) internal pure returns (uint256 , uint256 ) {
141159 uint256 ret = 0 ;
142- unchecked {
143- for (; pos < bz.length ; pos++ ) {
144- uint256 c = uint256 (uint8 (bz[pos]));
145- if (c < 48 || c > 57 ) {
146- break ;
147- }
148- ret = ret * 10 + (c - 48 );
160+ uint256 bzLen = bz.length ;
161+ for (; pos < bzLen; pos++ ) {
162+ uint256 c = uint256 (uint8 (bz[pos]));
163+ if (c < 48 || c > 57 ) {
164+ break ;
149165 }
150- if (pos >= bz.length || uint256 (uint8 (bz[pos])) != CHAR_DOUBLE_QUOTE) {
151- revert IICS20Errors.ICS20JSONStringClosingDoubleQuoteNotFound (pos, bz[pos]);
166+ unchecked {
167+ // SAFETY: we assume that the amount is uint256, so `ret` never overflows.
168+ ret = ret * 10 + (c - 48 );
152169 }
170+ }
171+ if (uint256 (uint8 (bz[pos])) != CHAR_DOUBLE_QUOTE) {
172+ revert IICS20Errors.ICS20JSONStringClosingDoubleQuoteNotFound (pos, bz[pos]);
173+ }
174+ unchecked {
175+ // SAFETY: `pos` is always less than `bz.length`.
153176 return (ret, pos + 1 );
154177 }
155178 }
156179
157180 /**
158181 * @dev parseString parses `bz` from a position `pos` to produce a string.
182+ * @param bz the byte array to parse.
183+ * @param pos the position to start parsing.
184+ * @return parsedStr the parsed string.
185+ * @return position the new position after parsing.
159186 */
160187 function parseString (bytes calldata bz , uint256 pos ) internal pure returns (string memory , uint256 ) {
188+ uint256 bzLen = bz.length ;
161189 unchecked {
162- for (uint256 i = pos; i < bz.length ; i++ ) {
190+ // SAFETY: i + 1 <= bzLen <= type(uint256).max
191+ for (uint256 i = pos; i < bzLen; i++ ) {
163192 uint256 c = uint256 (uint8 (bz[i]));
164193 if (c == CHAR_DOUBLE_QUOTE) {
165194 return (string (bz[pos:i]), i + 1 );
166- } else if (c == CHAR_BACKSLASH && i + 1 < bz. length ) {
195+ } else if (c == CHAR_BACKSLASH && i + 1 < bzLen ) {
167196 i++ ;
168197 c = uint256 (uint8 (bz[i]));
169198 if (
@@ -178,14 +207,19 @@ library ICS20Lib {
178207 revert IICS20Errors.ICS20JSONStringUnclosed (bz, pos);
179208 }
180209
210+ /**
211+ * @dev isEscapedJSONString checks if a string is escaped JSON.
212+ */
181213 function isEscapedJSONString (string calldata s ) internal pure returns (bool ) {
182214 bytes memory bz = bytes (s);
183- unchecked {
184- for (uint256 i = 0 ; i < bz.length ; i++ ) {
215+ uint256 bzLen = bz.length ;
216+ for (uint256 i = 0 ; i < bzLen; i++ ) {
217+ unchecked {
185218 uint256 c = uint256 (uint8 (bz[i]));
186219 if (c == CHAR_DOUBLE_QUOTE) {
187220 return false ;
188- } else if (c == CHAR_BACKSLASH && i + 1 < bz.length ) {
221+ } else if (c == CHAR_BACKSLASH && i + 1 < bzLen) {
222+ // SAFETY: i + 1 <= bzLen <= type(uint256).max
189223 i++ ;
190224 c = uint256 (uint8 (bz[i]));
191225 if (
@@ -200,29 +234,35 @@ library ICS20Lib {
200234 return true ;
201235 }
202236
237+ /**
238+ * @dev isEscapeNeededString checks if a given string needs to be escaped.
239+ * @param bz the byte array to check.
240+ */
203241 function isEscapeNeededString (bytes memory bz ) internal pure returns (bool ) {
204- unchecked {
205- for (uint256 i = 0 ; i < bz.length ; i++ ) {
206- uint256 c = uint256 (uint8 (bz[i]));
207- if (c == CHAR_DOUBLE_QUOTE) {
208- return true ;
209- }
242+ uint256 bzLen = bz.length ;
243+ for (uint256 i = 0 ; i < bzLen; i++ ) {
244+ uint256 c = uint256 (uint8 (bz[i]));
245+ if (c == CHAR_DOUBLE_QUOTE) {
246+ return true ;
210247 }
211248 }
212249 return false ;
213250 }
214251
215252 /**
216253 * @dev addressToHexString converts an address to a hex string.
254+ * @param addr the address to convert.
255+ * @return the hex string.
217256 */
218257 function addressToHexString (address addr ) internal pure returns (string memory ) {
219258 uint256 localValue = uint256 (uint160 (addr));
220259 bytes memory buffer = new bytes (42 );
221260 buffer[0 ] = "0 " ;
222261 buffer[1 ] = "x " ;
223262 unchecked {
224- for (int256 i = 41 ; i >= 2 ; -- i) {
225- buffer[uint256 (i)] = HEX_DIGITS[localValue & 0xf ];
263+ // SAFETY: `i` is always greater than or equal to 1.
264+ for (uint256 i = 41 ; i >= 2 ; -- i) {
265+ buffer[i] = HEX_DIGITS[localValue & 0xf ];
226266 localValue >>= 4 ;
227267 }
228268 }
@@ -231,6 +271,8 @@ library ICS20Lib {
231271
232272 /**
233273 * @dev hexStringToAddress converts a hex string to an address.
274+ * @param addrHexString the hex string to convert. It must be 42 characters long and start with "0x".
275+ * @return the address and a boolean indicating whether the conversion was successful.
234276 */
235277 function hexStringToAddress (string memory addrHexString ) internal pure returns (address , bool ) {
236278 bytes memory addrBytes = bytes (addrHexString);
@@ -240,9 +282,10 @@ library ICS20Lib {
240282 return (address (0 ), false );
241283 }
242284 uint256 addr = 0 ;
243- unchecked {
244- for (uint256 i = 2 ; i < 42 ; i++ ) {
245- uint256 c = uint256 (uint8 (addrBytes[i]));
285+ for (uint256 i = 2 ; i < 42 ; i++ ) {
286+ uint256 c = uint256 (uint8 (addrBytes[i]));
287+ unchecked {
288+ // SAFETY: we assume that the address is a valid ethereum addrress, so `addr` never overflows.
246289 if (c >= 48 && c <= 57 ) {
247290 addr = addr * 16 + (c - 48 );
248291 } else if (c >= 97 && c <= 102 ) {
0 commit comments