Skip to content

Commit

Permalink
add parse string to int
Browse files Browse the repository at this point in the history
  • Loading branch information
thedavidmeister committed Nov 1, 2024
1 parent ad06da8 commit f48f77d
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/lib/parse/LibParseChar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ library LibParseChar {
}

/// Checks if the cursor points at a char of the given mask, and is in range
/// of end.
/// of end. If the cursor is at or past the end, the result is `0`.
/// @param cursor The current position in the data.
/// @param end The end of the data.
/// @param mask The mask to check against.
/// @return `1` if the cursor points at a char of the given mask and is in
/// range of end, `0` otherwise.
function isMask(uint256 cursor, uint256 end, uint256 mask) internal pure returns (uint256 result) {
assembly ("memory-safe") {
//slither-disable-next-line incorrect-shift
Expand Down
86 changes: 86 additions & 0 deletions src/lib/parse/LibParseDecimal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 thedavidmeister
pragma solidity ^0.8.25;

library LibParseDecimal {
/// @notice Convert a decimal ASCII string in a memory region to an
/// 18 decimal fixed point `uint256`.
/// DOES NOT check that the string contains valid decimal characters. You can
/// use `LibParseChar.skipMask` to easily bound some valid decimal characters.
/// DOES check for overflow in the fixed point representation.
/// @param start The start of the memory region containing the decimal ASCII
/// string.
/// @param end The end of the memory region containing the decimal ASCII
/// string.
/// @return success Whether the conversion was successful. If false, this is
/// due to an overflow.
/// @return value The fixed point decimal representation of the ASCII string.
/// ALWAYS check `success` before using `value`, otherwise you cannot
/// distinguish between `0` and a failed conversion.
function unsafeDecimalStringToInt(uint256 start, uint256 end) internal pure returns (bool, uint256) {
unchecked {
// The ASCII byte can be translated to a numeric digit by subtracting
// the digit offset.
uint256 digitOffset = uint256(uint8(bytes1("0")));
uint256 exponent = 0;
uint256 cursor;
cursor = end - 1;
uint256 value = 0;

// Anything under 10^77 is safe to raise to its power of 10 without
// overflowing a uint256.
while (cursor >= start && exponent < 77) {
// We don't need to check the bounds of the byte because
// we know it is a decimal literal as long as the bounds
// are correct (calculated in `boundLiteral`).
assembly ("memory-safe") {
value := add(value, mul(sub(byte(0, mload(cursor)), digitOffset), exp(10, exponent)))
}
exponent++;
cursor--;
}

// If we didn't consume the entire literal, then we have
// to check if the remaining digit is safe to multiply
// by 10 without overflowing a uint256.
if (cursor >= start) {
{
uint256 digit;
assembly ("memory-safe") {
digit := sub(byte(0, mload(cursor)), digitOffset)
}
// If the digit is greater than 1, then we know that
// multiplying it by 10^77 will overflow a uint256.
if (digit > 1) {
return (false, 0);
} else {
uint256 scaled = digit * (10 ** exponent);
if (value + scaled < value) {
return (false, 0);
}
value += scaled;
}
cursor--;
}

{
// If we didn't consume the entire literal, then only
// leading zeros are allowed.
while (cursor >= start) {
//slither-disable-next-line similar-names
uint256 decimalCharByte;
assembly ("memory-safe") {
decimalCharByte := byte(0, mload(cursor))
}
if (decimalCharByte != uint256(uint8(bytes1("0")))) {
return (false, 0);
}
cursor--;
}
}
}

return (true, value);
}
}
}

0 comments on commit f48f77d

Please sign in to comment.