// SPDX-License-Identifier: MIT pragma solidity >=0.8.0; import {DSTestPlus} from "./utils/DSTestPlus.sol"; import {LibString} from "../utils/LibString.sol"; contract LibStringTest is DSTestPlus { function testToString() public { assertEq(LibString.toString(uint256(0)), "0"); assertEq(LibString.toString(uint256(1)), "1"); assertEq(LibString.toString(uint256(17)), "17"); assertEq(LibString.toString(uint256(99999999)), "99999999"); assertEq(LibString.toString(uint256(99999999999)), "99999999999"); assertEq(LibString.toString(uint256(2342343923423)), "2342343923423"); assertEq(LibString.toString(uint256(98765685434567)), "98765685434567"); } function testToStringIntPositive() public { assertEq(LibString.toString(int256(0)), "0"); assertEq(LibString.toString(int256(1)), "1"); assertEq(LibString.toString(int256(17)), "17"); assertEq(LibString.toString(int256(99999999)), "99999999"); assertEq(LibString.toString(int256(99999999999)), "99999999999"); assertEq(LibString.toString(int256(2342343923423)), "2342343923423"); assertEq(LibString.toString(int256(98765685434567)), "98765685434567"); } function testToStringIntNegative() public { assertEq(LibString.toString(int256(-0)), "0"); assertEq(LibString.toString(int256(-17)), "-17"); assertEq(LibString.toString(int256(-99999999)), "-99999999"); assertEq(LibString.toString(int256(-99999999999)), "-99999999999"); assertEq(LibString.toString(int256(-2342343923423)), "-2342343923423"); assertEq(LibString.toString(int256(-98765685434567)), "-98765685434567"); } function testDifferentiallyFuzzToString(uint256 value, bytes calldata brutalizeWith) public brutalizeMemory(brutalizeWith) { string memory libString = LibString.toString(value); string memory oz = toStringOZ(value); assertEq(bytes(libString).length, bytes(oz).length); assertEq(libString, oz); } function testDifferentiallyFuzzToStringInt(int256 value, bytes calldata brutalizeWith) public brutalizeMemory(brutalizeWith) { string memory libString = LibString.toString(value); string memory oz = toStringOZ(value); assertEq(bytes(libString).length, bytes(oz).length); assertEq(libString, oz); } function testToStringOverwrite() public { string memory str = LibString.toString(uint256(1)); bytes32 data; bytes32 expected; assembly { // Imagine a high level allocation writing something to the current free memory. // Should have sufficient higher order bits for this to be visible mstore(mload(0x40), not(0)) // Correctly allocate 32 more bytes, to avoid more interference mstore(0x40, add(mload(0x40), 32)) data := mload(add(str, 32)) // the expected value should be the uft-8 encoding of 1 (49), // followed by clean bits. We achieve this by taking the value and // shifting left to the end of the 32 byte word expected := shl(248, 49) } assertEq(data, expected); } function testToStringDirty() public { uint256 freememptr; // Make the next 4 bytes of the free memory dirty assembly { let dirty := not(0) freememptr := mload(0x40) mstore(freememptr, dirty) mstore(add(freememptr, 32), dirty) mstore(add(freememptr, 64), dirty) mstore(add(freememptr, 96), dirty) mstore(add(freememptr, 128), dirty) } string memory str = LibString.toString(uint256(1)); uint256 len; bytes32 data; bytes32 expected; assembly { freememptr := str len := mload(str) data := mload(add(str, 32)) // the expected value should be the uft-8 encoding of 1 (49), // followed by clean bits. We achieve this by taking the value and // shifting left to the end of the 32 byte word expected := shl(248, 49) } emit log_named_uint("str: ", freememptr); emit log_named_uint("len: ", len); emit log_named_bytes32("data: ", data); assembly { freememptr := mload(0x40) } emit log_named_uint("memptr: ", freememptr); assertEq(data, expected); } } function toStringOZ(int256 value) pure returns (string memory) { return string(abi.encodePacked(value < 0 ? "-" : "", toStringOZ(absOZ(value)))); } function toStringOZ(uint256 value) pure returns (string memory) { if (value == 0) { return "0"; } uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; } return string(buffer); } function absOZ(int256 n) pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } }