From bba266ff1671fd18cf1d27d899d294a1de88c531 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:04:10 -0500 Subject: [PATCH 1/4] feat: add operations for native bytes and string storage Add `set` and `delete_` functions to simplify handling of bytes and string storage references in `LibBytes` and `LibString`. These changes enable efficient and memory-safe operations when assigning or deleting storage references directly. Includes a helper for casting string storage to bytes storage. --- src/utils/LibBytes.sol | 56 +++++++++++++++++++++++++++++++++++++++++ src/utils/LibString.sol | 24 ++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/utils/LibBytes.sol b/src/utils/LibBytes.sol index 62b65f43ad..6604692b89 100644 --- a/src/utils/LibBytes.sol +++ b/src/utils/LibBytes.sol @@ -22,6 +22,62 @@ library LibBytes { /// @dev The constant returned when the `search` is not found in the bytes. uint256 internal constant NOT_FOUND = type(uint256).max; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* NATIVE BYTES OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the bytes array storage reference `$` to `s`. + /// A bytes array in memory cannot be assigned to a local bytes array storage reference directly. + function set(bytes storage $, bytes memory s) internal { + /// @solidity memory-safe-assembly + assembly { + let len := mload(s) + let packed := shl(1, len) + for { let i := 0 } 1 {} { + let o := add(s, 0x20) + if iszero(gt(len, 0x1f)) { + packed := or(mload(o), packed) + break + } + packed := or(packed, 1) + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + sstore(add(p, shr(5, i)), mload(add(o, i))) + i := add(i, 0x20) + if iszero(lt(i, len)) { break } + } + break + } + sstore($.slot, packed) + } + } + + /// @dev Deletes a bytes array from storage. + /// The `delete` keyword is not applicable to local bytes array storage references. + function delete_(bytes storage $) internal { + /// @solidity memory-safe-assembly + assembly { + let packed := sload($.slot) + let is_long_string := and(packed, 1) + for {} 1 {} { + sstore($.slot, 0) + if iszero(is_long_string) { break } + mstore(0, $.slot) + let ptr := keccak256(0, 0x20) + let len := shr(1, packed) + // the number of words used to store the string + let words := shr(5, add(len, 0x1f)) + let end := add(ptr, words) + for {} 1 {} { + sstore(ptr, 0) + ptr := add(ptr, 1) + if iszero(lt(ptr, end)) { break } + } + break + } + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BYTE STORAGE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/src/utils/LibString.sol b/src/utils/LibString.sol index 3bfc96b3c1..32aeef73e2 100644 --- a/src/utils/LibString.sol +++ b/src/utils/LibString.sol @@ -74,6 +74,30 @@ library LibString { /// @dev Lookup for ' \t\n\r\x0b\x0c'. uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* NATIVE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the string storage reference `$` to `s`. + /// A string in memory cannot be assigned to a local string storage reference directly. + function set(string storage $, string memory s) internal { + LibBytes.set(bytesStorage($), bytes(s)); + } + + /// @dev Deletes a string from storage. + /// The `delete` keyword is not applicable to local string storage references. + function delete_(string storage $) internal { + LibBytes.delete_(bytesStorage($)); + } + + /// @dev Helper to cast `$` to a `bytes`. + function bytesStorage(string storage $) internal pure returns (bytes storage casted) { + /// @solidity memory-safe-assembly + assembly { + casted.slot := $.slot + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STRING STORAGE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ From 1a461802937ca8c04dd7d7226b47204dbce5f721 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 9 Mar 2025 00:33:21 -0500 Subject: [PATCH 2/4] Refactor `set` function and add comprehensive tests Refactored the `set` function in `LibBytes` to handle unaligned storage writes and clean dirty bits, ensuring correctness. Introduced extensive testing, including native storage string handling, deletion, and random text scenarios, to verify the improved behavior. Added utility methods for more robust test coverage. --- src/utils/LibBytes.sol | 31 +++++++--- test/LibString.t.sol | 135 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 11 deletions(-) diff --git a/src/utils/LibBytes.sol b/src/utils/LibBytes.sol index 6604692b89..e2ba977d60 100644 --- a/src/utils/LibBytes.sol +++ b/src/utils/LibBytes.sol @@ -33,19 +33,32 @@ library LibBytes { assembly { let len := mload(s) let packed := shl(1, len) - for { let i := 0 } 1 {} { - let o := add(s, 0x20) + for {} 1 {} { if iszero(gt(len, 0x1f)) { - packed := or(mload(o), packed) + let right_aligned := mload(add(s, len)) + let zeros := shl(3, sub(0x20, len)) + let left_aligned := shl(zeros, right_aligned) + packed := or(packed, left_aligned) break } packed := or(packed, 1) mstore(0x00, $.slot) - for { let p := keccak256(0x00, 0x20) } 1 {} { - sstore(add(p, shr(5, i)), mload(add(o, i))) - i := add(i, 0x20) - if iszero(lt(i, len)) { break } + let ptr := keccak256(0x00, 0x20) + // the number of words minus 1 + let words := shr(5, sub(len, 1)) + let end := add(ptr, words) + let o := add(s, 0x20) + for {} 1 {} { + if eq(ptr, end) { break } + sstore(ptr, mload(o)) + ptr := add(ptr, 1) + o := add(o, 0x20) } + // clean and store the last word + let right_aligned := mload(add(s, len)) + let zeros := shl(3, sub(sub(o, s), len)) + let left_aligned := shl(zeros, right_aligned) + sstore(ptr, left_aligned) break } sstore($.slot, packed) @@ -63,7 +76,7 @@ library LibBytes { sstore($.slot, 0) if iszero(is_long_string) { break } mstore(0, $.slot) - let ptr := keccak256(0, 0x20) + let ptr := keccak256(0x00, 0x20) let len := shr(1, packed) // the number of words used to store the string let words := shr(5, add(len, 0x1f)) @@ -71,7 +84,7 @@ library LibBytes { for {} 1 {} { sstore(ptr, 0) ptr := add(ptr, 1) - if iszero(lt(ptr, end)) { break } + if eq(ptr, end) { break } } break } diff --git a/test/LibString.t.sol b/test/LibString.t.sol index e761351cb0..511a607272 100644 --- a/test/LibString.t.sol +++ b/test/LibString.t.sol @@ -12,6 +12,16 @@ contract SimpleStringSetAndGet { } } +contract SimpleStringSetAndGetWithNativeStorageString { + string public x; + + function setX(string calldata x_) public { + LibString.set(x, x_); + // TODO + // LibString.setCalldata(_x, x_); + } +} + contract SimpleStringSetAndGetWithStringStorage { LibString.StringStorage internal _x; @@ -25,8 +35,19 @@ contract SimpleStringSetAndGetWithStringStorage { } contract LibStringTest is SoladyTest { + function testDelete() public brutalizeMemory { + string storage $ = _getNativeStorageString(); + _set($, "Milady"); + _testDelete($); + _set($, "MiladyMiladyMiladyMiladyMiladyMiladyMilady"); + _testDelete($); + } + function testSimpleStringSetAndGetGas() public { _testSimpleStringSetAndGet(new SimpleStringSetAndGet()); + _testSimpleStringSetAndGet( + SimpleStringSetAndGet(address(new SimpleStringSetAndGetWithNativeStorageString())) + ); _testSimpleStringSetAndGet( SimpleStringSetAndGet(address(new SimpleStringSetAndGetWithStringStorage())) ); @@ -336,10 +357,10 @@ contract LibStringTest is SoladyTest { let c0 := byte(0, mload(p)) let c1 := byte(1, mload(p)) if and(gt(c1, 58), gt(and(temp, 15), 7)) { - mstore8(add(p, 1), sub(c1, 32)) + mstore8(add(p, 1), sub(c1, 32)) } if and(gt(c0, 58), gt(shr(4, temp), 7)) { - mstore8(p, sub(c0, 32)) + mstore8(p, sub(c0, 32)) } } } @@ -1580,6 +1601,19 @@ contract LibStringTest is SoladyTest { return LibString.toSmallString(s); } + function testSetAndGetNativeStorageString() public { + string memory emptyString; + _testSetAndGetNativeStorageString(emptyString); + _testSetAndGetNativeStorageString(""); + _testSetAndGetNativeStorageString("a"); + _testSetAndGetNativeStorageString("ab"); + unchecked { + for (uint256 i = 0; i != 300; ++i) { + _testSetAndGetNativeStorageString(_randomUniformString(i), false); + } + } + } + function testSetAndGetStringStorage() public { string memory emptyString; _testSetAndGetStringStorage(emptyString); @@ -1593,6 +1627,20 @@ contract LibStringTest is SoladyTest { } } + function testSetAndGetNativeStorageString(bytes32) public { + vm.pauseGasMetering(); + if (_randomChance(32)) { + assertEq(bytes(_getNativeStorageString()).length, 0); + assertEq(_get(_getNativeStorageString()), ""); + } + if (_randomChance(2)) _testSetAndGetNativeStorageString(string(_randomBytes())); + if (_randomChance(16)) _testSetAndGetNativeStorageString(string(_randomBytes())); + if (_randomChance(32)) { + _testSetAndGetNativeStorageString(_randomUniformString(_randomUniform() & 0xfff)); + } + vm.resumeGasMetering(); + } + function testSetAndGetStringStorage(bytes32) public { vm.pauseGasMetering(); if (_randomChance(32)) { @@ -1608,19 +1656,52 @@ contract LibStringTest is SoladyTest { vm.resumeGasMetering(); } + function testSetAndGetNativeStorageString2(string memory s) public { + _testSetAndGetNativeStorageString(s); + } + function testSetAndGetStringStorage2(string memory s) public { _testSetAndGetStringStorage(s); } + // TODO + // function testSetAndGetNativeStorageStringCalldata(string calldata s) public { + // LibString.setCalldata(_getNativeStorageString(), s); + // assertEq(_getNativeStorageString(), s); + // } + function testSetAndGetStringStorageCalldata(string calldata s) public { LibString.setCalldata(_getStringStorage(), s); assertEq(LibString.get(_getStringStorage()), s); } + function _testSetAndGetNativeStorageString(string memory s) internal { + _testSetAndGetNativeStorageString(s, _randomChance(8)); + } + function _testSetAndGetStringStorage(string memory s) internal { _testSetAndGetStringStorage(s, _randomChance(8)); } + function _testSetAndGetNativeStorageString(string memory s0, bool writeTo1) internal { + _set(_getNativeStorageString(0), s0); + string memory s1; + if (writeTo1) { + s1 = string(_randomBytes()); + _set(_getNativeStorageString(1), s1); + if (_randomChance(16)) { + _misalignFreeMemoryPointer(); + _brutalizeMemory(); + } + } + assertEq(_get(_getNativeStorageString(0)), s0); + if (writeTo1) { + assertEq(_get(_getNativeStorageString(1)), s1); + if (_randomChance(16)) _testDelete(_getNativeStorageString(0)); + if (_randomChance(16)) _testDelete(_getNativeStorageString(1)); + } + } + function _testSetAndGetStringStorage(string memory s0, bool writeTo1) internal { _set(_getStringStorage(0), s0); string memory s1; @@ -1640,6 +1721,34 @@ contract LibStringTest is SoladyTest { } } + function _testDelete(string storage $) internal { + uint256 length = bytes($).length; + LibString.delete_($); + assertEq($, ""); + assertEq(bytes($).length, 0); + uint256 packed; + assembly { + packed := sload($.slot) + } + assertEq(packed, 0, "Expected the length slot to be zero"); + if (length >= 32) { + uint256 p; + /// @solidity memory-safe-assembly + assembly { + mstore(0, $.slot) + p := keccak256(0, 0x20) + } + uint256 words = (length + 31) / 32; + for (uint256 i; i < words; ++i) { + uint256 word; + assembly { + word := sload(add(p, i)) + } + assertEq(word, 0, "Expected every word to be zero"); + } + } + } + function _testClear(LibString.StringStorage storage $) internal { if (_randomChance(2)) { LibString.clear($); @@ -1650,12 +1759,23 @@ contract LibStringTest is SoladyTest { assertTrue(LibString.isEmpty($)); } + function _set(string storage $, string memory s) internal { + LibString.set($, s); + assertEq(bytes($).length, bytes(s).length); + } + function _set(LibString.StringStorage storage $, string memory s) internal { LibString.set($, s); assertEq(LibString.length($), bytes(s).length); assertEq(LibString.isEmpty($), bytes(s).length == 0); } + function _get(string storage $) internal returns (string memory result) { + result = $; + _checkMemory(result); + assertEq(bytes($).length, bytes(result).length); + } + function _get(LibString.StringStorage storage $) internal returns (string memory result) { result = LibString.get($); _checkMemory(result); @@ -1663,10 +1783,21 @@ contract LibStringTest is SoladyTest { assertEq(LibString.length($), bytes(result).length); } + function _getNativeStorageString() internal pure returns (string storage) { + return _getNativeStorageString(0); + } + function _getStringStorage() internal pure returns (LibString.StringStorage storage) { return _getStringStorage(0); } + function _getNativeStorageString(uint256 o) internal pure returns (string storage $) { + /// @solidity memory-safe-assembly + assembly { + $.slot := add(0x39be4c398aefe47a0e, o) + } + } + function _getStringStorage(uint256 o) internal pure From 2ab4c75fa3ebe04c4c38d5c96e44ca79170541c4 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 9 Mar 2025 00:56:09 -0500 Subject: [PATCH 3/4] Add support for setting calldata strings and bytes in storage Introduce `setCalldata` functions in `LibString` and `LibBytes` to handle the assignment of calldata strings and bytes to storage references. Updated tests to cover the new functionality, ensuring proper handling and verification of calldata assignments. --- src/utils/LibBytes.sol | 39 +++++++++++++++++++++++++++++++++++++++ src/utils/LibString.sol | 6 ++++++ test/LibString.t.sol | 13 +++++-------- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/utils/LibBytes.sol b/src/utils/LibBytes.sol index e2ba977d60..ae3fad9276 100644 --- a/src/utils/LibBytes.sol +++ b/src/utils/LibBytes.sol @@ -65,6 +65,45 @@ library LibBytes { } } + /// @dev Sets the value of the bytes array storage reference `$` to `s`. + /// A bytes array in calldata cannot be assigned to a local bytes array storage reference directly. + function setCalldata(bytes storage $, bytes calldata s) internal { + /// @solidity memory-safe-assembly + assembly { + let len_ptr := sub(s.offset, 0x20) + let packed := shl(1, s.length) + for {} 1 {} { + if iszero(gt(s.length, 0x1f)) { + let right_aligned := calldataload(add(len_ptr, s.length)) + let zeros := shl(3, sub(0x20, s.length)) + let left_aligned := shl(zeros, right_aligned) + packed := or(packed, left_aligned) + break + } + packed := or(packed, 1) + mstore(0x00, $.slot) + let ptr := keccak256(0x00, 0x20) + // the number of words minus 1 + let words := shr(5, sub(s.length, 1)) + let end := add(ptr, words) + let o := s.offset + for {} 1 {} { + if eq(ptr, end) { break } + sstore(ptr, calldataload(o)) + ptr := add(ptr, 1) + o := add(o, 0x20) + } + // clean and store the last word + let right_aligned := calldataload(add(len_ptr, s.length)) + let zeros := shl(3, sub(sub(o, len_ptr), s.length)) + let left_aligned := shl(zeros, right_aligned) + sstore(ptr, left_aligned) + break + } + sstore($.slot, packed) + } + } + /// @dev Deletes a bytes array from storage. /// The `delete` keyword is not applicable to local bytes array storage references. function delete_(bytes storage $) internal { diff --git a/src/utils/LibString.sol b/src/utils/LibString.sol index 32aeef73e2..b4ca49fd6c 100644 --- a/src/utils/LibString.sol +++ b/src/utils/LibString.sol @@ -84,6 +84,12 @@ library LibString { LibBytes.set(bytesStorage($), bytes(s)); } + /// @dev Sets the value of the string storage reference `$` to `s`. + /// A string in calldata cannot be assigned to a local string storage reference directly. + function setCalldata(string storage $, string calldata s) internal { + LibBytes.setCalldata(bytesStorage($), bytes(s)); + } + /// @dev Deletes a string from storage. /// The `delete` keyword is not applicable to local string storage references. function delete_(string storage $) internal { diff --git a/test/LibString.t.sol b/test/LibString.t.sol index 511a607272..f5c18cfca0 100644 --- a/test/LibString.t.sol +++ b/test/LibString.t.sol @@ -16,9 +16,7 @@ contract SimpleStringSetAndGetWithNativeStorageString { string public x; function setX(string calldata x_) public { - LibString.set(x, x_); - // TODO - // LibString.setCalldata(_x, x_); + LibString.setCalldata(x, x_); } } @@ -1664,11 +1662,10 @@ contract LibStringTest is SoladyTest { _testSetAndGetStringStorage(s); } - // TODO - // function testSetAndGetNativeStorageStringCalldata(string calldata s) public { - // LibString.setCalldata(_getNativeStorageString(), s); - // assertEq(_getNativeStorageString(), s); - // } + function testSetAndGetNativeStorageStringCalldata(string calldata s) public { + LibString.setCalldata(_getNativeStorageString(), s); + assertEq(_getNativeStorageString(), s); + } function testSetAndGetStringStorageCalldata(string calldata s) public { LibString.setCalldata(_getStringStorage(), s); From e8a3c9efa2b22ae76a7de7fa7c5430f33262acb9 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 9 Mar 2025 03:14:16 -0400 Subject: [PATCH 4/4] prep all --- src/utils/g/LibBytes.sol | 108 ++++++++++++++++++++++++++++++++++++++ src/utils/g/LibString.sol | 30 +++++++++++ 2 files changed, 138 insertions(+) diff --git a/src/utils/g/LibBytes.sol b/src/utils/g/LibBytes.sol index 7d3b100a40..060c251dad 100644 --- a/src/utils/g/LibBytes.sol +++ b/src/utils/g/LibBytes.sol @@ -26,6 +26,114 @@ library LibBytes { /// @dev The constant returned when the `search` is not found in the bytes. uint256 internal constant NOT_FOUND = type(uint256).max; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* NATIVE BYTES OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the bytes array storage reference `$` to `s`. + /// A bytes array in memory cannot be assigned to a local bytes array storage reference directly. + function set(bytes storage $, bytes memory s) internal { + /// @solidity memory-safe-assembly + assembly { + let len := mload(s) + let packed := shl(1, len) + for {} 1 {} { + if iszero(gt(len, 0x1f)) { + let right_aligned := mload(add(s, len)) + let zeros := shl(3, sub(0x20, len)) + let left_aligned := shl(zeros, right_aligned) + packed := or(packed, left_aligned) + break + } + packed := or(packed, 1) + mstore(0x00, $.slot) + let ptr := keccak256(0x00, 0x20) + // the number of words minus 1 + let words := shr(5, sub(len, 1)) + let end := add(ptr, words) + let o := add(s, 0x20) + for {} 1 {} { + if eq(ptr, end) { break } + sstore(ptr, mload(o)) + ptr := add(ptr, 1) + o := add(o, 0x20) + } + // clean and store the last word + let right_aligned := mload(add(s, len)) + let zeros := shl(3, sub(sub(o, s), len)) + let left_aligned := shl(zeros, right_aligned) + sstore(ptr, left_aligned) + break + } + sstore($.slot, packed) + } + } + + /// @dev Sets the value of the bytes array storage reference `$` to `s`. + /// A bytes array in calldata cannot be assigned to a local bytes array storage reference directly. + function setCalldata(bytes storage $, bytes calldata s) internal { + /// @solidity memory-safe-assembly + assembly { + let len_ptr := sub(s.offset, 0x20) + let packed := shl(1, s.length) + for {} 1 {} { + if iszero(gt(s.length, 0x1f)) { + let right_aligned := calldataload(add(len_ptr, s.length)) + let zeros := shl(3, sub(0x20, s.length)) + let left_aligned := shl(zeros, right_aligned) + packed := or(packed, left_aligned) + break + } + packed := or(packed, 1) + mstore(0x00, $.slot) + let ptr := keccak256(0x00, 0x20) + // the number of words minus 1 + let words := shr(5, sub(s.length, 1)) + let end := add(ptr, words) + let o := s.offset + for {} 1 {} { + if eq(ptr, end) { break } + sstore(ptr, calldataload(o)) + ptr := add(ptr, 1) + o := add(o, 0x20) + } + // clean and store the last word + let right_aligned := calldataload(add(len_ptr, s.length)) + let zeros := shl(3, sub(sub(o, len_ptr), s.length)) + let left_aligned := shl(zeros, right_aligned) + sstore(ptr, left_aligned) + break + } + sstore($.slot, packed) + } + } + + /// @dev Deletes a bytes array from storage. + /// The `delete` keyword is not applicable to local bytes array storage references. + function delete_(bytes storage $) internal { + /// @solidity memory-safe-assembly + assembly { + let packed := sload($.slot) + let is_long_string := and(packed, 1) + for {} 1 {} { + sstore($.slot, 0) + if iszero(is_long_string) { break } + mstore(0, $.slot) + let ptr := keccak256(0x00, 0x20) + let len := shr(1, packed) + // the number of words used to store the string + let words := shr(5, add(len, 0x1f)) + let end := add(ptr, words) + for {} 1 {} { + sstore(ptr, 0) + ptr := add(ptr, 1) + if eq(ptr, end) { break } + } + break + } + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BYTE STORAGE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/src/utils/g/LibString.sol b/src/utils/g/LibString.sol index e89faec068..d56a4c05b2 100644 --- a/src/utils/g/LibString.sol +++ b/src/utils/g/LibString.sol @@ -78,6 +78,36 @@ library LibString { /// @dev Lookup for ' \t\n\r\x0b\x0c'. uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* NATIVE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the string storage reference `$` to `s`. + /// A string in memory cannot be assigned to a local string storage reference directly. + function set(string storage $, string memory s) internal { + LibBytes.set(bytesStorage($), bytes(s)); + } + + /// @dev Sets the value of the string storage reference `$` to `s`. + /// A string in calldata cannot be assigned to a local string storage reference directly. + function setCalldata(string storage $, string calldata s) internal { + LibBytes.setCalldata(bytesStorage($), bytes(s)); + } + + /// @dev Deletes a string from storage. + /// The `delete` keyword is not applicable to local string storage references. + function delete_(string storage $) internal { + LibBytes.delete_(bytesStorage($)); + } + + /// @dev Helper to cast `$` to a `bytes`. + function bytesStorage(string storage $) internal pure returns (bytes storage casted) { + /// @solidity memory-safe-assembly + assembly { + casted.slot := $.slot + } + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STRING STORAGE OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/