packages/solidity-devops/test/libs/StringUtils.t.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {StringUtilsHarness} from "../harnesses/StringUtilsHarness.sol";
import {Test} from "forge-std/Test.sol";
/// @notice Test suite for the StringUtils library.
contract StringUtilsTest is Test {
StringUtilsHarness public libHarness;
bytes public temp;
function setUp() public {
libHarness = new StringUtilsHarness();
}
function testLength() public view {
assertEq(libHarness.length(""), 0);
assertEq(libHarness.length("0"), 1);
assertEq(libHarness.length("0123456789"), 10);
}
// ════════════════════════════════════════════════ SLICING ══════════════════════════════════════════════════════
function testSubstring() public view {
string memory str = "0123456789";
// Empty string
assertEq(libHarness.substring(str, 0, 0), "");
assertEq(libHarness.substring(str, 6, 6), "");
assertEq(libHarness.substring(str, 9, 9), "");
// Single character
assertEq(libHarness.substring(str, 0, 1), "0");
assertEq(libHarness.substring(str, 5, 6), "5");
assertEq(libHarness.substring(str, 9, 10), "9");
// A few characters
assertEq(libHarness.substring(str, 0, 2), "01");
assertEq(libHarness.substring(str, 2, 5), "234");
assertEq(libHarness.substring(str, 5, 10), "56789");
// Full string
assertEq(libHarness.substring(str, 0, 10), str);
}
function testSubstringRevertsWhenStartIndexOutOfBounds() public {
string memory str = "0123456789";
vm.expectRevert("StringUtils: Start index out of bounds");
libHarness.substring(str, 11, 10);
}
function testSubstringRevertsWhenEndIndexOutOfBounds() public {
string memory str = "0123456789";
vm.expectRevert("StringUtils: End index out of bounds");
libHarness.substring(str, 0, 11);
}
function testSubstringRevertsWhenStartIndexGreaterThanEndIndex() public {
string memory str = "0123456789";
vm.expectRevert("StringUtils: Invalid range");
libHarness.substring(str, 5, 4);
}
function testSuffix() public view {
string memory str = "0123456789";
assertEq(libHarness.suffix(str, 10), "");
assertEq(libHarness.suffix(str, 9), "9");
assertEq(libHarness.suffix(str, 5), "56789");
assertEq(libHarness.suffix(str, 0), str);
}
function testSuffixRevertsWhenStartIndexOutOfBounds() public {
string memory str = "0123456789";
vm.expectRevert("StringUtils: Start index out of bounds");
libHarness.suffix(str, 11);
}
function testPrefix() public view {
string memory str = "0123456789";
assertEq(libHarness.prefix(str, 0), "");
assertEq(libHarness.prefix(str, 1), "0");
assertEq(libHarness.prefix(str, 5), "01234");
assertEq(libHarness.prefix(str, 10), str);
}
function testPrefixRevertsWhenEndIndexOutOfBounds() public {
string memory str = "0123456789";
vm.expectRevert("StringUtils: End index out of bounds");
libHarness.prefix(str, 11);
}
// ═══════════════════════════════════════════════ CONCATENATION ═══════════════════════════════════════════════════
function appendToTemp(string memory str) internal {
// Push all symbols from str to temp
for (uint256 i = 0; i < bytes(str).length; i++) {
temp.push(bytes(str)[i]);
}
}
function testConcatTwoStrings(string memory a, string memory b) public {
appendToTemp(a);
appendToTemp(b);
assertEq(libHarness.concat(a, b), string(temp));
}
function testConcatThreeStrings(string memory a, string memory b, string memory c) public {
appendToTemp(a);
appendToTemp(b);
appendToTemp(c);
assertEq(libHarness.concat(a, b, c), string(temp));
}
function testConcatFourStrings(string memory a, string memory b, string memory c, string memory d) public {
appendToTemp(a);
appendToTemp(b);
appendToTemp(c);
appendToTemp(d);
assertEq(libHarness.concat(a, b, c, d), string(temp));
}
function testConcatFiveStrings(
string memory a,
string memory b,
string memory c,
string memory d,
string memory e
)
public
{
appendToTemp(a);
appendToTemp(b);
appendToTemp(c);
appendToTemp(d);
appendToTemp(e);
assertEq(libHarness.concat(a, b, c, d, e), string(temp));
}
function testConcatSixStrings(
string memory a,
string memory b,
string memory c,
string memory d,
string memory e,
string memory f
)
public
{
appendToTemp(a);
appendToTemp(b);
appendToTemp(c);
appendToTemp(d);
appendToTemp(e);
appendToTemp(f);
assertEq(libHarness.concat(a, b, c, d, e, f), string(temp));
}
function testDuplicate(string memory a, uint256 times) public {
times = times % 10;
for (uint256 i = 0; i < times; i++) {
appendToTemp(a);
}
assertEq(libHarness.duplicate(a, times), string(temp));
}
// ════════════════════════════════════════════════ COMPARISON ═════════════════════════════════════════════════════
function testEquals(string memory a, string memory b) public view {
if (libHarness.equals(a, b)) {
assertEq(a, b);
} else {
assertNotEq(a, b);
}
}
function testEqualsToClone(string memory a) public view {
bytes memory clone = new bytes(bytes(a).length);
for (uint256 i = 0; i < bytes(a).length; i++) {
clone[i] = bytes(a)[i];
}
assertTrue(libHarness.equals(a, string(clone)));
}
function testIndexOf() public view {
string memory str = "01201234012345";
assertEq(libHarness.indexOf("", ""), 0);
assertEq(libHarness.indexOf("", "0"), type(uint256).max);
assertEq(libHarness.indexOf(str, ""), 0);
// Single character
assertEq(libHarness.indexOf(str, "0"), 0);
assertEq(libHarness.indexOf(str, "2"), 2);
assertEq(libHarness.indexOf(str, "4"), 7);
// Multiple characters
assertEq(libHarness.indexOf(str, "01"), 0);
assertEq(libHarness.indexOf(str, "012"), 0);
assertEq(libHarness.indexOf(str, "123"), 4);
assertEq(libHarness.indexOf(str, "2345"), 10);
// Whole string
assertEq(libHarness.indexOf(str, str), 0);
// Not found
assertEq(libHarness.indexOf(str, "6"), type(uint256).max);
assertEq(libHarness.indexOf(str, "02"), type(uint256).max);
assertEq(libHarness.indexOf(str, "01230"), type(uint256).max);
}
function testLastIndexOf() public view {
string memory str = "01201234012345";
assertEq(libHarness.lastIndexOf("", ""), 0);
assertEq(libHarness.lastIndexOf("", "0"), type(uint256).max);
assertEq(libHarness.lastIndexOf(str, ""), 14);
// Single character
assertEq(libHarness.lastIndexOf(str, "0"), 8);
assertEq(libHarness.lastIndexOf(str, "2"), 10);
assertEq(libHarness.lastIndexOf(str, "4"), 12);
// Multiple characters
assertEq(libHarness.lastIndexOf(str, "01"), 8);
assertEq(libHarness.lastIndexOf(str, "012"), 8);
assertEq(libHarness.lastIndexOf(str, "123"), 9);
assertEq(libHarness.lastIndexOf(str, "2345"), 10);
// Whole string
assertEq(libHarness.lastIndexOf(str, str), 0);
// Not found
assertEq(libHarness.lastIndexOf(str, "6"), type(uint256).max);
assertEq(libHarness.lastIndexOf(str, "02"), type(uint256).max);
assertEq(libHarness.lastIndexOf(str, "01230"), type(uint256).max);
}
// ════════════════════════════════════════════ INTEGER CONVERSION ═════════════════════════════════════════════════
function testToUint() public view {
assertEq(libHarness.toUint("0"), 0);
assertEq(libHarness.toUint("1"), 1);
assertEq(libHarness.toUint("42"), 42);
assertEq(libHarness.toUint("1234567890"), 1_234_567_890);
assertEq(
libHarness.toUint("115792089237316195423570985008687907853269984665640564039457584007913129639935"),
type(uint256).max
);
}
function testToUintRevertsWhenNotADigit() public {
vm.expectRevert("StringUtils: Not a digit");
libHarness.toUint("0a");
}
function testFromUint() public view {
assertEq(libHarness.fromUint(0), "0");
assertEq(libHarness.fromUint(1), "1");
assertEq(libHarness.fromUint(42), "42");
assertEq(libHarness.fromUint(1_234_567_890), "1234567890");
assertEq(
libHarness.fromUint(type(uint256).max),
"115792089237316195423570985008687907853269984665640564039457584007913129639935"
);
}
function testUintRoundtrip(uint256 val) public view {
assertEq(libHarness.toUint(libHarness.fromUint(val)), val);
}
// ═════════════════════════════════════════════ FLOAT CONVERSION ══════════════════════════════════════════════════
function testFromFloat() public view {
// Zero decimals
assertEq(libHarness.fromFloat(0, 0), "0.0");
assertEq(libHarness.fromFloat(1, 0), "1.0");
assertEq(libHarness.fromFloat(42, 0), "42.0");
assertEq(libHarness.fromFloat(1_234_567_890, 0), "1234567890.0");
assertEq(
libHarness.fromFloat(type(uint256).max, 0),
"115792089237316195423570985008687907853269984665640564039457584007913129639935.0"
);
// Six decimals
assertEq(libHarness.fromFloat(0, 6), "0.000000");
assertEq(libHarness.fromFloat(1, 6), "0.000001");
assertEq(libHarness.fromFloat(42, 6), "0.000042");
assertEq(libHarness.fromFloat(1_234_567_890, 6), "1234.567890");
assertEq(
libHarness.fromFloat(type(uint256).max, 6),
"115792089237316195423570985008687907853269984665640564039457584007913129.639935"
);
// 18 decimals
assertEq(libHarness.fromFloat(0, 18), "0.000000000000000000");
assertEq(libHarness.fromFloat(1, 18), "0.000000000000000001");
assertEq(libHarness.fromFloat(42, 18), "0.000000000000000042");
assertEq(libHarness.fromFloat(1_234_567_890, 18), "0.000000001234567890");
assertEq(
libHarness.fromFloat(type(uint256).max, 18),
"115792089237316195423570985008687907853269984665640564039457.584007913129639935"
);
}
function testFromWei() public view {
assertEq(libHarness.fromWei(0), "0.000000000000000000");
assertEq(libHarness.fromWei(1), "0.000000000000000001");
assertEq(libHarness.fromWei(42), "0.000000000000000042");
assertEq(libHarness.fromWei(1_234_567_890), "0.000000001234567890");
assertEq(
libHarness.fromWei(type(uint256).max),
"115792089237316195423570985008687907853269984665640564039457.584007913129639935"
);
}
}