Skip to main content

AdvancedStrings Library

The AdvancedStrings library provides efficient type conversion utilities, primarily for converting Solidity types to their string representations. This is essential for constructing human-readable messages for signature verification.

Overview

Library Type: Pure functions
License: EVVM-NONCOMMERCIAL-1.0
Import Path: @evvm/testnet-contracts/library/utils/AdvancedStrings.sol

Key Features

  • uint256 to string conversion
  • Address to checksummed string
  • Bytes to hex string conversion
  • String equality comparison
  • Gas-optimized implementations

Functions

uintToString

function uintToString(uint256 value) internal pure returns (string memory)

Description: Converts a uint256 to its decimal string representation

Parameters:

  • value: The uint256 number to convert

Returns: String representation (e.g., "12345")

Example:

string memory str = AdvancedStrings.uintToString(12345);
// str = "12345"

string memory zero = AdvancedStrings.uintToString(0);
// zero = "0"

string memory large = AdvancedStrings.uintToString(type(uint256).max);
// large = "115792089237316195423570985008687907853269984665640564039457584007913129639935"

Implementation Details:

  • Uses Math.log10 to calculate string length
  • Builds string from right to left
  • Gas-optimized with assembly memory operations

Use Cases:

  • Constructing signature messages
  • Building event data
  • Creating human-readable identifiers

addressToString

function addressToString(address _address) internal pure returns (string memory)

Description: Converts an address to its hex string representation with 0x prefix

Parameters:

  • _address: The address to convert

Returns: Hex string in format "0x..." (42 characters)

Example:

address user = 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2;
string memory str = AdvancedStrings.addressToString(user);
// str = "0x742d35cc6634c0532925a3b844bc9e7595f0beb2"

address zero = address(0);
string memory zeroStr = AdvancedStrings.addressToString(zero);
// zeroStr = "0x0000000000000000000000000000000000000000"

Format:

  • Always 42 characters (2 for "0x" + 40 for address)
  • Lowercase hex representation
  • Zero-padded to full length

Use Cases:

  • Converting addresses for signature messages
  • Logging and debugging
  • Creating readable address representations

equal

function equal(string memory a, string memory b) internal pure returns (bool)

Description: Compares two strings for equality

Parameters:

  • a: First string
  • b: Second string

Returns: true if strings are identical, false otherwise

Example:

bool same = AdvancedStrings.equal("hello", "hello");
// same = true

bool different = AdvancedStrings.equal("hello", "world");
// different = false

bool empty = AdvancedStrings.equal("", "");
// empty = true

Implementation:

  1. Compares lengths first (gas optimization)
  2. If lengths match, compares keccak256 hashes

Use Cases:

  • String validation
  • Command/action matching
  • Input verification

bytesToString

function bytesToString(bytes memory data) internal pure returns (string memory)

Description: Converts bytes to hex string representation with 0x prefix

Parameters:

  • data: Bytes array to convert

Returns: Hex string in format "0x..." (2 + data.length * 2 characters)

Example:

bytes memory data = hex"deadbeef";
string memory str = AdvancedStrings.bytesToString(data);
// str = "0xdeadbeef"

bytes memory empty = "";
string memory emptyStr = AdvancedStrings.bytesToString(empty);
// emptyStr = "0x"

Format:

  • Starts with "0x"
  • Each byte becomes 2 hex characters
  • Lowercase hex representation

Use Cases:

  • Converting signature bytes to readable format
  • Logging transaction data
  • Debugging byte arrays

bytes32ToString

function bytes32ToString(bytes32 data) internal pure returns (string memory)

Description: Converts bytes32 to hex string representation with 0x prefix

Parameters:

  • data: bytes32 value to convert

Returns: Hex string in format "0x..." (66 characters: 2 + 32 * 2)

Example:

bytes32 hash = keccak256("hello");
string memory str = AdvancedStrings.bytes32ToString(hash);
// str = "0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"

Format:

  • Always 66 characters (2 for "0x" + 64 for bytes32)
  • Lowercase hex representation
  • Zero-padded

Use Cases:

  • Converting hash values to strings
  • Logging bytes32 data
  • Creating readable hash representations

Common Use Cases

Use Case 1: Constructing Signature Messages

function buildSignatureMessage(
uint256 evvmId,
string memory action,
uint256 param1,
uint256 param2,
uint256 nonce
) internal pure returns (string memory) {
return string.concat(
AdvancedStrings.uintToString(evvmId),
",",
action,
",",
AdvancedStrings.uintToString(param1),
",",
AdvancedStrings.uintToString(param2),
",",
AdvancedStrings.uintToString(nonce)
);
}

// Example: "123,orderCoffee,2,1000000000000000000,456789"
string memory message = buildSignatureMessage(123, "orderCoffee", 2, 1 ether, 456789);

Use Case 2: Address-Based Messages

function createTransferMessage(
address from,
address to,
uint256 amount
) internal pure returns (string memory) {
return string.concat(
"Transfer from ",
AdvancedStrings.addressToString(from),
" to ",
AdvancedStrings.addressToString(to),
" amount: ",
AdvancedStrings.uintToString(amount)
);
}

// Example: "Transfer from 0x742d...beb2 to 0x123a...def4 amount: 1000000000000000000"

Use Case 3: String Comparison for Commands

function executeCommand(string memory command, bytes memory data) external {
if (AdvancedStrings.equal(command, "deposit")) {
handleDeposit(data);
} else if (AdvancedStrings.equal(command, "withdraw")) {
handleWithdraw(data);
} else if (AdvancedStrings.equal(command, "transfer")) {
handleTransfer(data);
} else {
revert("Unknown command");
}
}

Use Case 4: Debugging and Logging

event DebugInfo(string message);

function debugTransaction(
address user,
bytes32 txHash,
uint256 amount,
bytes memory signature
) internal {
emit DebugInfo(
string.concat(
"User: ",
AdvancedStrings.addressToString(user),
" TxHash: ",
AdvancedStrings.bytes32ToString(txHash),
" Amount: ",
AdvancedStrings.uintToString(amount),
" Sig: ",
AdvancedStrings.bytesToString(signature)
)
);
}

Integration with EVVM

Used by SignatureUtil

library SignatureUtil {
function verifySignature(
uint256 evvmID,
string memory functionName,
string memory inputs,
bytes memory signature,
address expectedSigner
) internal pure returns (bool) {
return SignatureRecover.recoverSigner(
string.concat(
AdvancedStrings.uintToString(evvmID), // Uses AdvancedStrings
",",
functionName,
",",
inputs
),
signature
) == expectedSigner;
}
}

Used by EvvmService

abstract contract EvvmService {
function validateServiceSignature(
string memory functionName,
string memory inputs,
bytes memory signature,
address expectedSigner
) internal view virtual {
if (!SignatureUtil.verifySignature(
evvm.getEvvmID(), // Converted with AdvancedStrings internally
functionName,
inputs,
signature,
expectedSigner
)) revert InvalidServiceSignature();
}
}

Gas Optimization Tips

1. Cache String Conversions

// Good - convert once
string memory amountStr = AdvancedStrings.uintToString(amount);
string memory message1 = string.concat("Message1: ", amountStr);
string memory message2 = string.concat("Message2: ", amountStr);

// Bad - convert multiple times
string memory message1 = string.concat("Message1: ", AdvancedStrings.uintToString(amount));
string memory message2 = string.concat("Message2: ", AdvancedStrings.uintToString(amount));

2. Use Constants for Fixed Strings

// Good - use string literals for fixed values
string memory token0 = getEtherAddress() == address(0) ? "ETH" : "TOKEN";

// Bad - unnecessary conversion
string memory token0 = AdvancedStrings.addressToString(address(0)); // "0x0000..."

3. Minimize String Concatenations

// Good - single concat
string memory message = string.concat(
AdvancedStrings.uintToString(a),
",",
AdvancedStrings.uintToString(b),
",",
AdvancedStrings.uintToString(c)
);

// Bad - multiple operations
string memory message = AdvancedStrings.uintToString(a);
message = string.concat(message, ",");
message = string.concat(message, AdvancedStrings.uintToString(b));
// ... more concats

Gas Costs

FunctionTypical Gas CostNotes
uintToString~500-2,000Depends on number size
addressToString~1,000Fixed cost
equal~200-500Depends on length
bytesToString~500-5,000Depends on data length
bytes32ToString~1,500Fixed cost

Best Practices

1. Use Type-Appropriate Functions

// Good - use addressToString for addresses
string memory addrStr = AdvancedStrings.addressToString(userAddress);

// Bad - converting address to uint (loses information)
string memory wrongStr = AdvancedStrings.uintToString(uint256(uint160(userAddress)));

2. Pre-Compute Static Strings

// Good - compute once in constructor or constant
string public constant ACTION_NAME = "orderCoffee";

function buildMessage(uint256 nonce) internal pure returns (string memory) {
return string.concat(
ACTION_NAME, // Use pre-defined
",",
AdvancedStrings.uintToString(nonce)
);
}

// Bad - re-create string each time
function buildMessage(uint256 nonce) internal pure returns (string memory) {
return string.concat(
"orderCoffee", // Creates new string each call
",",
AdvancedStrings.uintToString(nonce)
);
}

3. Validate Before Converting

// Good - validate first
require(amount > 0, "Invalid amount");
string memory amountStr = AdvancedStrings.uintToString(amount);

// Bad - convert then validate (wastes gas on failure)
string memory amountStr = AdvancedStrings.uintToString(amount);
require(amount > 0, "Invalid amount");

Common Patterns

Pattern 1: EVVM Signature Message Format

string memory message = string.concat(
AdvancedStrings.uintToString(evvmID),
",",
functionName,
",",
params // Pre-formatted parameter string
);

Pattern 2: Transaction Receipt String

string memory receipt = string.concat(
"From: ", AdvancedStrings.addressToString(from),
" To: ", AdvancedStrings.addressToString(to),
" Amount: ", AdvancedStrings.uintToString(amount),
" TxHash: ", AdvancedStrings.bytes32ToString(txHash)
);

Pattern 3: Parameter String Builder

function buildParams(
string memory param1,
uint256 param2,
address param3
) internal pure returns (string memory) {
return string.concat(
param1,
",",
AdvancedStrings.uintToString(param2),
",",
AdvancedStrings.addressToString(param3)
);
}

See Also