forked from Arachnid/solidity-stringutils
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Basic string utilities for Solidity, optimized for low gas usage. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/** | ||
* @title String utility functions for Solidity contracts. | ||
* @author Nick Johnson <[email protected]> | ||
* | ||
* @dev All functions are UTF-8 friendly, if input strings are valid UTF-8. | ||
* Offsets and sizes are specified in bytes, not characters, and so will | ||
* not respect UTF-8 character boundaries; be careful to only pass values | ||
* that you know are between characters. | ||
*/ | ||
contract StringUtils { | ||
function readWord(bytes a, uint idx) private returns (bytes32 word) { | ||
assembly { | ||
word := mload(add(add(a, idx), 32)) | ||
} | ||
} | ||
|
||
/** | ||
* @dev Compares two strings, returning a negative number if a is smaller, | ||
* a positive number if a is larger, and zero if the strings are equal. | ||
* @param a The first string to compare. | ||
* @param b The second string to compare. | ||
* @return An integer whose sign indicates the value of the comparison. | ||
*/ | ||
function strcmp(string a, string b) internal returns (int) { | ||
uint shortest = bytes(a).length; | ||
if (bytes(b).length < bytes(a).length) | ||
shortest = bytes(b).length; | ||
|
||
for (uint idx = 0; idx < shortest; idx += 32) { | ||
var diff = int( | ||
uint(readWord(bytes(a), idx)) - uint(readWord(bytes(b), idx))); | ||
if (diff != 0) | ||
return diff; | ||
} | ||
return int(bytes(a).length - bytes(b).length); | ||
} | ||
|
||
/** | ||
* @dev Finds an occurrence of a substring in a string, returning its index, | ||
* or -1 if the substring is not found. | ||
* @param haystack The string to search. | ||
* @param needle The string to look for. | ||
* @param idx The string index at which to start searching. | ||
* @return The index of the first character of the substring, or -1 if not | ||
* found. | ||
*/ | ||
function strstr(string haystack, string needle, uint idx) internal | ||
returns (int) | ||
{ | ||
uint needleSize = bytes(needle).length; | ||
bytes32 hash; | ||
assembly { | ||
hash := sha3(add(needle, 32), needleSize) | ||
} | ||
for(; idx <= bytes(haystack).length - needleSize; idx++) { | ||
bytes32 testHash; | ||
assembly { | ||
testHash := sha3(add(add(haystack, idx), 32), needleSize) | ||
} | ||
if (hash == testHash) | ||
return int(idx); | ||
} | ||
return -1; | ||
} | ||
|
||
/** | ||
* @dev Copies part of one string into another. If the requested range | ||
* extends past the end of the source or target strings, the range will | ||
* be truncated. If src and dest are the same, the ranges must either | ||
* not overlap, or idx must be less than start. | ||
* @param dest The destination string to copy into. | ||
* @param idx The start index in the destination string. | ||
* @param src The string to copy from. | ||
* @param start The index into the source string to start copying. | ||
* @param len The number of bytes to copy. | ||
*/ | ||
function strncpy(string dest, uint idx, string src, uint start, uint len) | ||
internal | ||
{ | ||
if (idx + len > bytes(dest).length) | ||
len = bytes(dest).length - idx; | ||
if (start > bytes(src).length) | ||
return; | ||
if (start + len > bytes(src).length) | ||
len = bytes(src).length - start; | ||
|
||
// From here, we treat idx and start as memory offsets for dest and idx. | ||
// Skip over the first word, which contains the length of each string. | ||
idx += 32; | ||
start += 32; | ||
|
||
// Copy word-length chunks while possible | ||
for(; len >= 32; len -= 32) { | ||
assembly { | ||
mstore(add(dest, idx), mload(add(src, start))) | ||
} | ||
idx += 32; | ||
start += 32; | ||
} | ||
|
||
// Copy remaining bytes | ||
uint mask = 256 ** (32 - len) - 1; | ||
assembly { | ||
let destaddr := add(dest, idx) | ||
let srcpart := and(mload(add(src, start)), bnot(mask)) | ||
let destpart := and(mload(destaddr), mask) | ||
mstore(destaddr, or(destpart, srcpart)) | ||
} | ||
} | ||
|
||
/** | ||
* @dev Returns a substring starting at idx and continuing until the first | ||
* occurrence of delim. If delim is not found, returns the remainder of | ||
* the string. | ||
* @param str The string to return a substring of. | ||
* @param delim The delimiter to search for. | ||
* @param idx The start index. | ||
* @return A newly allocated string consisting of bytes between idx and the | ||
* first occurrence of delim. | ||
*/ | ||
function strsep(string str, string delim, uint idx) internal | ||
returns (string ret) | ||
{ | ||
int endIdx = strstr(str, delim, idx); | ||
if (endIdx == -1) { | ||
endIdx = int(bytes(str).length); | ||
} | ||
ret = new string(uint(endIdx) - idx); | ||
strncpy(ret, 0, str, idx, uint(endIdx) - idx); | ||
} | ||
|
||
/** | ||
* @dev Returns the length of a string, in characters. | ||
* @param str The string to return the length of. | ||
* @return The length of the string, in characters. | ||
*/ | ||
function strchrlen(string str) internal returns (uint len) { | ||
bytes memory strdata = bytes(str); | ||
for (uint i = 0; i < strdata.length; i++) | ||
// Don't count continuation bytes, of the form 0b10xxxxxx | ||
if (strdata[i] & 0xC0 != 0x80) | ||
len += 1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import 'dapple/test.sol'; | ||
import 'StringUtils.sol'; | ||
|
||
contract StringUtilsTest is Test, StringUtils { | ||
function abs(int x) returns (int) { | ||
if(x < 0) | ||
return -x; | ||
return x; | ||
} | ||
|
||
function sign(int x) returns (int) { | ||
return x/abs(x); | ||
} | ||
|
||
function assertEq(string a, string b) { | ||
assertEq(strcmp(a, b), 0); | ||
} | ||
|
||
function testStrcmp() logs_gas { | ||
assertEq(sign(strcmp("foobie", "foobie")), 0); | ||
assertEq(sign(strcmp("foobie", "foobif")), -1); | ||
assertEq(sign(strcmp("foobie", "foobid")), 1); | ||
assertEq(sign(strcmp("foobie", "foobies")), -1); | ||
assertEq(sign(strcmp("foobie", "foobi")), 1); | ||
assertEq(sign(strcmp("foobie", "doobie")), 1); | ||
assertEq(sign(strcmp("01234567890123456789012345678901", "012345678901234567890123456789012")), -1); | ||
} | ||
|
||
function testStrstr() logs_gas { | ||
assertEq(strstr("abracadabra", "bra", 0), 1); | ||
assertEq(strstr("abracadabra", "bra", 2), 8); | ||
assertEq(strstr("abracadabra", "rab", 0), -1); | ||
assertEq(strstr("ABC ABCDAB ABCDABCDABDE", "ABCDABD", 0), 15); | ||
} | ||
|
||
function testStrncpy() logs_gas { | ||
string memory target = "0123456789"; | ||
|
||
// Basic nonoverlapping copy | ||
strncpy(target, 0, target, 5, 5); | ||
assertEq(target, "5678956789"); | ||
|
||
// Truncate input range | ||
strncpy(target, 0, target, 8, 5); | ||
assertEq(target, "8978956789"); | ||
|
||
// Truncate output range | ||
strncpy(target, 8, target, 1, 5); | ||
assertEq(target, "8978956797"); | ||
|
||
// Overlapping copy | ||
strncpy(target, 0, target, 2, 8); | ||
assertEq(target, "7895679797"); | ||
|
||
// Copy a longer string | ||
string memory longer = "0123456789012345678901234567890123456789012345"; | ||
strncpy(longer, 0, longer, 1, 45); | ||
assertEq(longer, "1234567890123456789012345678901234567890123455"); | ||
} | ||
|
||
function testStrsep() logs_gas { | ||
assertEq(strsep("www.google.com", ".", 0), "www"); | ||
assertEq(strsep("www.google.com", ".", 4), "google"); | ||
assertEq(strsep("www.google.com", ".", 11), "com"); | ||
assertEq(strsep("www.google.com", ".", 15), ""); | ||
assertEq(strsep("foo->bar->baz", "->", 0), "foo"); | ||
assertEq(strsep("foo->bar->baz", "->", 5), "bar"); | ||
} | ||
|
||
function testStrchrlen() logs_gas { | ||
assertEq(strchrlen(""), 0); | ||
assertEq(strchrlen("foobar"), 6); | ||
assertEq(strchrlen("I ♥ ethereum"), 12); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
version: 1.0.0 | ||
tags: [] | ||
layout: | ||
sol_sources: . | ||
build_dir: build | ||
dependencies: {} | ||
ignore: [] | ||
name: ethereum-stringutils |