@@ -20,16 +20,16 @@ library ICS20Lib {
20
20
bytes internal constant FAILED_ACKNOWLEDGEMENT_JSON = bytes ('{"error":"failed"} ' );
21
21
bytes32 internal constant KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON = keccak256 (SUCCESSFUL_ACKNOWLEDGEMENT_JSON);
22
22
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 ; // "/"
25
25
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"
33
33
34
34
bytes16 private constant HEX_DIGITS = "0123456789abcdef " ;
35
35
@@ -47,6 +47,11 @@ library ICS20Lib {
47
47
48
48
/**
49
49
* @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.
50
55
*/
51
56
function marshalJSON (
52
57
string memory escapedDenom ,
@@ -72,6 +77,10 @@ library ICS20Lib {
72
77
73
78
/**
74
79
* @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.
75
84
*/
76
85
function marshalJSON (
77
86
string memory escapedDenom ,
@@ -94,12 +103,16 @@ library ICS20Lib {
94
103
95
104
/**
96
105
* @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>"}
97
109
*/
98
110
function unmarshalJSON (bytes calldata bz ) internal pure returns (PacketData memory ) {
99
111
PacketData memory pd;
100
112
uint256 pos = 0 ;
101
113
102
114
unchecked {
115
+ // SAFETY: `pos` never overflow because it is always less than `bz.length`.
103
116
if (bytes32 (bz[pos:pos + 11 ]) != bytes32 ('{"amount":" ' )) {
104
117
revert IICS20Errors.ICS20JSONUnexpectedBytes (pos, bytes32 ('{"amount":" ' ), bytes32 (bz[pos:pos + 11 ]));
105
118
}
@@ -135,35 +148,51 @@ library ICS20Lib {
135
148
}
136
149
137
150
/**
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.
139
157
*/
140
158
function parseUint256String (bytes calldata bz , uint256 pos ) internal pure returns (uint256 , uint256 ) {
141
159
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 ;
149
165
}
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 );
152
169
}
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`.
153
176
return (ret, pos + 1 );
154
177
}
155
178
}
156
179
157
180
/**
158
181
* @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.
159
186
*/
160
187
function parseString (bytes calldata bz , uint256 pos ) internal pure returns (string memory , uint256 ) {
188
+ uint256 bzLen = bz.length ;
161
189
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++ ) {
163
192
uint256 c = uint256 (uint8 (bz[i]));
164
193
if (c == CHAR_DOUBLE_QUOTE) {
165
194
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 ) {
167
196
i++ ;
168
197
c = uint256 (uint8 (bz[i]));
169
198
if (
@@ -178,14 +207,19 @@ library ICS20Lib {
178
207
revert IICS20Errors.ICS20JSONStringUnclosed (bz, pos);
179
208
}
180
209
210
+ /**
211
+ * @dev isEscapedJSONString checks if a string is escaped JSON.
212
+ */
181
213
function isEscapedJSONString (string calldata s ) internal pure returns (bool ) {
182
214
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 {
185
218
uint256 c = uint256 (uint8 (bz[i]));
186
219
if (c == CHAR_DOUBLE_QUOTE) {
187
220
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
189
223
i++ ;
190
224
c = uint256 (uint8 (bz[i]));
191
225
if (
@@ -200,29 +234,35 @@ library ICS20Lib {
200
234
return true ;
201
235
}
202
236
237
+ /**
238
+ * @dev isEscapeNeededString checks if a given string needs to be escaped.
239
+ * @param bz the byte array to check.
240
+ */
203
241
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 ;
210
247
}
211
248
}
212
249
return false ;
213
250
}
214
251
215
252
/**
216
253
* @dev addressToHexString converts an address to a hex string.
254
+ * @param addr the address to convert.
255
+ * @return the hex string.
217
256
*/
218
257
function addressToHexString (address addr ) internal pure returns (string memory ) {
219
258
uint256 localValue = uint256 (uint160 (addr));
220
259
bytes memory buffer = new bytes (42 );
221
260
buffer[0 ] = "0 " ;
222
261
buffer[1 ] = "x " ;
223
262
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 ];
226
266
localValue >>= 4 ;
227
267
}
228
268
}
@@ -231,6 +271,8 @@ library ICS20Lib {
231
271
232
272
/**
233
273
* @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.
234
276
*/
235
277
function hexStringToAddress (string memory addrHexString ) internal pure returns (address , bool ) {
236
278
bytes memory addrBytes = bytes (addrHexString);
@@ -240,9 +282,10 @@ library ICS20Lib {
240
282
return (address (0 ), false );
241
283
}
242
284
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.
246
289
if (c >= 48 && c <= 57 ) {
247
290
addr = addr * 16 + (c - 48 );
248
291
} else if (c >= 97 && c <= 102 ) {
0 commit comments