diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 2f891aaa3..450c7985e 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -54,7 +54,7 @@ library ShortStrings { * @dev Decode a `ShortString` back to a "normal" string. */ function toString(ShortString sstr) internal pure returns (string memory) { - uint256 len = length(sstr); + uint256 len = byteLength(sstr); // using `new string(len)` would work locally but is not memory safe. string memory str = new string(32); /// @solidity memory-safe-assembly @@ -68,7 +68,7 @@ library ShortStrings { /** * @dev Return the length of a `ShortString`. */ - function length(ShortString sstr) internal pure returns (uint256) { + function byteLength(ShortString sstr) internal pure returns (uint256) { uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF; if (result > 31) { revert InvalidShortString(); @@ -98,4 +98,18 @@ library ShortStrings { return store; } } + + /** + * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}. + * + * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of + * actual characters as the UTF-8 encoding of a single character can span over multiple bytes. + */ + function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) { + if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) { + return byteLength(value); + } else { + return bytes(store).length; + } + } } diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 27d52a5d7..f5cd82fbd 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -23,7 +23,7 @@ contract('ShortStrings', function () { const encoded = await this.mock.$toShortString(str); expect(decode(encoded)).to.be.equal(str); - const length = await this.mock.$length(encoded); + const length = await this.mock.$byteLength(encoded); expect(length.toNumber()).to.be.equal(str.length); const decoded = await this.mock.$toString(encoded); @@ -44,6 +44,9 @@ contract('ShortStrings', function () { await expectRevertCustomError(promise, 'InvalidShortString()'); } + const length = await this.mock.$byteLengthWithFallback(ret0, 0); + expect(length.toNumber()).to.be.equal(str.length); + const recovered = await this.mock.$toStringWithFallback(ret0, 0); expect(recovered).to.be.equal(str); });