Skip to content

Repository files navigation

StrSlice & Slice library for Solidity

  • Types: StrSlice for strings, Slice for bytes, StrChar for characters
  • Gas efficient
  • Versioned releases, available for both foundry and hardhat
  • Simple imports, you only need e.g. StrSlice and toSlice
  • StrSlice enforces UTF-8 character boundaries; StrChar validates character encoding
  • Clean, well-documented and thoroughly-tested source code
  • Optional PRBTest extension with assertions like assertContains and assertLt for both slices and native bytes, string
  • Slice and StrSlice are value types, not structs
  • Low-level functions like memchr, memcmp, memmove etc

Install

Node

yarn add @dk1a/solidity-stringutils

Forge

forge install --no-commit dk1a/solidity-stringutils

StrSlice

import { StrSlice, toSlice } from"@dk1a/solidity-stringutils/src/StrSlice.sol";
using { toSlice } forstring;
/// @dev Returns the content of brackets, or empty string if not foundfunction extractFromBrackets(stringmemorystuffInBrackets) purereturns (StrSlice extracted) {
StrSlice s = stuffInBrackets.toSlice();
bool found;
(found, , s) = s.splitOnce(toSlice("("));
if (!found) returntoSlice("");
(found, s, ) = s.rsplitOnce(toSlice(")"));
if (!found) returntoSlice("");
return s;
}
/*assertEq( extractFromBrackets("((1 + 2) + 3) + 4"), toSlice("(1 + 2) + 3"));*/

See ExamplesTest.

Internally StrSlice uses Slice and extends it with logic for multibyte UTF-8 where necessary.

MethodDescription
lenlength in bytes
isEmptytrue if len == 0
toStringcopy slice contents to a new string
keccakequal to keccak256(s.toString()), but cheaper
concatenate
addConcatenate 2 slices into a new string
joinJoin slice array on self as separator
compare
cmp0 for eq, < 0 for lt, > 0 for gt
eq,ne==, != (more efficient than cmp)
lt,lte<, <=
gt,gte>, >=
index
isCharBoundarytrue if given index is an allowed boundary
getget 1 UTF-8 character at given index
splitAt(slice[:index], slice[index:])
getSubsliceslice[start:end]
search
findindex of the start of the first match
rfindindex of the start of the last match
return type(uint256).max for no matches
containstrue if a match is found
startsWithtrue if starts with pattern
endsWithtrue if ends with pattern
modify
stripPrefixreturns subslice without the prefix
stripSuffixreturns subslice without the suffix
splitOncesplit into 2 subslices on the first match
rsplitOncesplit into 2 subslices on the last match
replacenexperimental replace n matches
replacen requires 0 < pattern.len() <= to.len()
iterate
charscharacter iterator over the slice
ascii
isAsciitrue if all chars are ASCII
dangerous
asSliceget underlying Slice
ptrget memory pointer

Indexes are in bytes, not characters. Indexing methods revert if isCharBoundary is false.

StrCharsIter

Returned by chars method of StrSlice

import { StrSlice, toSlice, StrCharsIter } from"@dk1a/solidity-stringutils/src/StrSlice.sol";
using { toSlice } forstring;
/// @dev Returns a StrSlice of `str` with the 2 first UTF-8 characters removed/// reverts on invalid UTF8function removeFirstTwoChars(stringmemorystr) purereturns (StrSlice) {
StrCharsIter memory chars = str.toSlice().chars();
for (uint256 i; i <2; i++) {
if (chars.isEmpty()) break;
chars.next();
}
return chars.asStr();
}
/*assertEq(removeFirstTwoChars(unicode"📎!こんにちは"), unicode"こんにちは");*/
MethodDescription
asStrget underlying StrSlice of the remainder
lenremainder length in bytes
isEmptytrue if len == 0
nextadvance the iterator, return the next StrChar
nextBackadvance from the back, return the next StrChar
countreturns the number of UTF-8 characters
validateUtf8returns true if the sequence is valid UTF-8
dangerous
unsafeNextadvance unsafely, return the next StrChar
unsafeCountunsafely count chars, read the source for caveats
ptrget memory pointer

count, validateUtf8, unsafeCount consume the iterator in O(n).

Safe methods revert on an invalid UTF-8 byte sequence.

unsafeNext does NOT check if the iterator is empty, may underflow! Does not revert on invalid UTF-8. If returned StrChar is invalid, it will have length 0. Otherwise length 1-4.

Internally next, unsafeNext, count all use _nextRaw. It's very efficient, but very unsafe and complicated. Read the source and import it separately if you need it.

StrChar

Represents a single UTF-8 encoded character. Internally it's bytes32 with leading byte at MSB.

It's returned by some methods of StrSlice and StrCharsIter.

MethodDescription
lencharacter length in bytes
toBytes32returns the underlying bytes32 value
toStringcopy the character to a new string
toCodePointreturns the unicode code point (ord in python)
cmp0 for eq, < 0 for lt, > 0 for gt
eq,ne==, !=
lt,lte<, <=
gt,gte>, >=
isValidUtf8usually true
isAsciitrue if the char is ASCII

Import StrChar__ (static function lib) to use StrChar__.fromCodePoint for code point to StrChar conversion.

len can return 0only for invalid UTF-8 characters. But some invalid chars may have non-zero len! (use isValidUtf8 to check validity). Note that 0x00 is a valid 1-byte UTF-8 character, its len is 1.

isValidUtf8 can be false if the character was formed with an unsafe method (fromUnchecked, wrap).

Slice

import { Slice, toSlice } from"@dk1a/solidity-stringutils/src/Slice.sol";
using { toSlice } forbytes;
function findZeroByte(bytesmemoryb) purereturns (uint256index) {
return b.toSlice().find(
bytes(hex"00").toSlice()
);
}

See using {...} for Slice global in the source for a function summary. Many are shared between Slice and StrSlice, but there are differences.

Internally Slice has very minimal assembly, instead using memcpy, memchr, memcmp and others; if you need the low-level functions, see src/utils/.

Assertions (PRBTest extension)

import { PRBTest } from"@prb/test/src/PRBTest.sol";
import { Assertions } from"@dk1a/solidity-stringutils/src/test/Assertions.sol";
contractStrSliceTestisPRBTest, Assertions {
function testContains() public {
bytesmemory b1 ="12345";
bytesmemory b2 ="3";
assertContains(b1, b2);
}
function testLt() public {
stringmemory s1 ="123";
stringmemory s2 ="124";
assertLt(s1, s2);
}
}

You can completely ignore slices if all you want is e.g. assertContains for native bytes/string.

Acknowledgements

  • Arachnid/solidity-stringutils - I basically wanted to make an updated version of solidity-stringutils
  • rust - most similarities are in names and general structure; the implementation can't really be similar (solidity doesn't even have generics)
  • paulrberg/prb-math - good template for solidity data structure libraries with using {...} for ... global
  • brockelmore/memmove - good assembly memory management examples

About

StrSlice & Slice library for Solidity

Topics

Resources

Stars

22 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages