Skip to content

Commit a37d4e1

Browse files
authored
Merge pull request #299 from hyperledger-labs/improve-code-docs
Improve code docs Signed-off-by: Jun Kimura <jun.kimura@datachain.jp>
2 parents b41cdf7 + 7d449f6 commit a37d4e1

File tree

6 files changed

+130
-67
lines changed

6 files changed

+130
-67
lines changed

contracts/apps/20-transfer/ICS20Bank.sol

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ contract ICS20Bank is Context, AccessControl, IICS20Bank, IICS20Errors {
4141
revert ICS20InsufficientBalance(from, fromBalance, amount);
4242
}
4343
unchecked {
44+
// SAFETY: balance is checked above
4445
_balances[denom][from] = fromBalance - amount;
4546
}
4647
_balances[denom][to] += amount;
@@ -94,6 +95,7 @@ contract ICS20Bank is Context, AccessControl, IICS20Bank, IICS20Errors {
9495
revert ICS20InsufficientBalance(account, accountBalance, amount);
9596
}
9697
unchecked {
98+
// SAFETY: balance is checked above
9799
_balances[denom][account] = accountBalance - amount;
98100
}
99101
}

contracts/apps/20-transfer/ICS20Lib.sol

+78-35
Original file line numberDiff line numberDiff line change
@@ -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) {

contracts/core/02-client/IBCClientLib.sol

+18-16
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,29 @@ library IBCClientLib {
88
* - clientType must be in the form of `^[a-z][a-z0-9-]*[a-z0-9]$`
99
*/
1010
function validateClientType(bytes memory clientTypeBytes) internal pure returns (bool) {
11-
if (clientTypeBytes.length == 0) {
11+
uint256 byteLength = clientTypeBytes.length;
12+
if (byteLength == 0) {
1213
return false;
1314
}
14-
unchecked {
15-
for (uint256 i = 0; i < clientTypeBytes.length; i++) {
16-
uint256 c = uint256(uint8(clientTypeBytes[i]));
17-
if (0x61 <= c && c <= 0x7a) {
18-
// a-z
19-
continue;
20-
} else if (c == 0x2d) {
21-
// hyphen cannot be the first or last character
22-
if (i == 0 || i == clientTypeBytes.length - 1) {
15+
for (uint256 i = 0; i < byteLength; i++) {
16+
uint256 c = uint256(uint8(clientTypeBytes[i]));
17+
if (0x61 <= c && c <= 0x7a) {
18+
// a-z
19+
continue;
20+
} else if (c == 0x2d) {
21+
// hyphen cannot be the first or last character
22+
unchecked {
23+
// SAFETY: `byteLength` is greater than 0
24+
if (i == 0 || i == byteLength - 1) {
2325
return false;
2426
}
25-
continue;
26-
} else if (0x30 <= c && c <= 0x39) {
27-
// 0-9
28-
continue;
29-
} else {
30-
return false;
3127
}
28+
continue;
29+
} else if (0x30 <= c && c <= 0x39) {
30+
// 0-9
31+
continue;
32+
} else {
33+
return false;
3234
}
3335
}
3436
return true;

contracts/core/04-channel/IBCChannelUpgrade.sol

+2
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ abstract contract IBCChannelUpgradeBase is
340340
function toString(UpgradeHandshakeError err) internal pure returns (string memory) {
341341
bytes memory result = new bytes(1);
342342
unchecked {
343+
// SAFETY: `err` is always less than or equal to 6, so overflow never occurs
343344
result[0] = bytes1(uint8(err) + 48);
344345
}
345346
return string(result);
@@ -434,6 +435,7 @@ contract IBCChannelUpgradeInitTryAck is IBCChannelUpgradeBase, IIBCChannelUpgrad
434435
// and abort their out-of-sync upgrade without aborting our own since
435436
// the error receipt sequence is lower than ours and higher than the counterparty.
436437
unchecked {
438+
// SAFETY: `msg_.counterpartyUpgradeSequence` is always greater than 0, so underflow never occurs
437439
writeErrorReceipt(
438440
msg_.portId, msg_.channelId, expectedUpgradeSequence - 1, UpgradeHandshakeError.OutOfSync
439441
);

contracts/core/24-host/IBCHostLib.sol

+14-16
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,20 @@ library IBCHostLib {
1717
if (portIdLength < 2 || portIdLength > 128) {
1818
return false;
1919
}
20-
unchecked {
21-
for (uint256 i = 0; i < portIdLength; i++) {
22-
uint256 c = uint256(uint8(portId[i]));
23-
// return false if the character is not in one of the following categories:
24-
// a-z
25-
// 0-9
26-
// A-Z
27-
// ".", "_", "+", "-"
28-
// "#", "[", "]", "<", ">"
29-
if (
30-
!(c >= 0x61 && c <= 0x7A) && !(c >= 0x30 && c <= 0x39) && !(c >= 0x41 && c <= 0x5A)
31-
&& !(c == 0x2E || c == 0x5F || c == 0x2B || c == 0x2D)
32-
&& !(c == 0x23 || c == 0x5B || c == 0x5D || c == 0x3C || c == 0x3E)
33-
) {
34-
return false;
35-
}
20+
for (uint256 i = 0; i < portIdLength; i++) {
21+
uint256 c = uint256(uint8(portId[i]));
22+
// return false if the character is not in one of the following categories:
23+
// a-z
24+
// 0-9
25+
// A-Z
26+
// ".", "_", "+", "-"
27+
// "#", "[", "]", "<", ">"
28+
if (
29+
!(c >= 0x61 && c <= 0x7A) && !(c >= 0x30 && c <= 0x39) && !(c >= 0x41 && c <= 0x5A)
30+
&& !(c == 0x2E || c == 0x5F || c == 0x2B || c == 0x2D)
31+
&& !(c == 0x23 || c == 0x5B || c == 0x5D || c == 0x3C || c == 0x3E)
32+
) {
33+
return false;
3634
}
3735
}
3836
return true;

tests/foundry/src/ICS20.t.sol

+16
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ contract TestICS20 is Test {
106106
// This should not revert if the input is not a valid hex string.
107107
ICS20LibTestHelper.hexStringToAddress(any);
108108
}
109+
110+
function testParseUint256String() public {
111+
bytes memory maxAmountStr = bytes("115792089237316195423570985008687907853269984665640564039457584007913129639935\"");
112+
(uint256 amount, uint256 pos) = ICS20LibTestHelper.parseUint256String(maxAmountStr, 0);
113+
assertEq(amount, 115792089237316195423570985008687907853269984665640564039457584007913129639935);
114+
assertEq(pos, maxAmountStr.length);
115+
116+
bytes memory minAmountStr = bytes("1\"");
117+
(amount, pos) = ICS20LibTestHelper.parseUint256String(minAmountStr, 0);
118+
assertEq(amount, 1);
119+
assertEq(pos, minAmountStr.length);
120+
}
109121
}
110122

111123
library ICS20LibTestHelper {
@@ -128,4 +140,8 @@ library ICS20LibTestHelper {
128140
function isEscapedJSONString(string calldata s) public pure returns (bool) {
129141
return ICS20Lib.isEscapedJSONString(s);
130142
}
143+
144+
function parseUint256String(bytes calldata bz, uint256 pos) public pure returns (uint256, uint256) {
145+
return ICS20Lib.parseUint256String(bz, pos);
146+
}
131147
}

0 commit comments

Comments
 (0)