From c17b32662662f3974b1455f15c7772c5dac46b6e Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 27 Jan 2023 15:19:43 +0100 Subject: [PATCH 01/16] add string and bytes support to the StorageSlots library --- .changeset/modern-games-exist.md | 5 ++ contracts/mocks/StorageSlotMock.sol | 38 ++++++++++- contracts/utils/StorageSlot.sol | 51 +++++++++++++- test/utils/StorageSlot.test.js | 100 ++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 .changeset/modern-games-exist.md diff --git a/.changeset/modern-games-exist.md b/.changeset/modern-games-exist.md new file mode 100644 index 00000000000..bd89b4f1658 --- /dev/null +++ b/.changeset/modern-games-exist.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`StorageSlot`: Add support for `string` and `bytes`. diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index 5d099fca83d..1da577c19fe 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "../utils/StorageSlot.sol"; contract StorageSlotMock { - using StorageSlot for bytes32; + using StorageSlot for *; function setBoolean(bytes32 slot, bool value) public { slot.getBooleanSlot().value = value; @@ -38,4 +38,40 @@ contract StorageSlotMock { function getUint256(bytes32 slot) public view returns (uint256) { return slot.getUint256Slot().value; } + + mapping(uint256 => string) public stringMap; + + function setString(bytes32 slot, string calldata value) public { + slot.getStringSlot().value = value; + } + + function setStringStorage(uint256 key, string calldata value) public { + stringMap[key].getStringSlot().value = value; + } + + function getString(bytes32 slot) public view returns (string memory) { + return slot.getStringSlot().value; + } + + function getStringStorage(uint256 key) public view returns (string memory) { + return stringMap[key].getStringSlot().value; + } + + mapping(uint256 => bytes) public bytesMap; + + function setBytes(bytes32 slot, bytes calldata value) public { + slot.getBytesSlot().value = value; + } + + function setBytesStorage(uint256 key, bytes calldata value) public { + bytesMap[key].getBytesSlot().value = value; + } + + function getBytes(bytes32 slot) public view returns (bytes memory) { + return slot.getBytesSlot().value; + } + + function getBytesStorage(uint256 key) public view returns (bytes memory) { + return bytesMap[key].getBytesSlot().value; + } } diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index d23363bd632..22776f6f429 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -27,7 +27,8 @@ pragma solidity ^0.8.0; * } * ``` * - * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ + * _Available since v4.1 for `address`, `bool`, `bytes32` and `uint256`._ + * _Available since v4.9 for `string` and `bytes`._ */ library StorageSlot { struct AddressSlot { @@ -46,6 +47,14 @@ library StorageSlot { uint256 value; } + struct StringSlot { + string value; + } + + struct BytesSlot { + bytes value; + } + /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ @@ -85,4 +94,44 @@ library StorageSlot { r.slot := slot } } + + /** + * @dev Returns an `StringSlot` with member `value` located at `slot`. + */ + function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` representation of the string storage pointer `store`. + */ + function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } + + /** + * @dev Returns an `BytesSlot` with member `value` located at `slot`. + */ + function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. + */ + function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } } diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js index 9d428875f44..6ae7345d696 100644 --- a/test/utils/StorageSlot.test.js +++ b/test/utils/StorageSlot.test.js @@ -107,4 +107,104 @@ contract('StorageSlot', function (accounts) { }); }); }); + + describe('string storage slot', function () { + beforeEach(async function () { + this.value = "lorem ipsum"; + }); + + it('set', async function () { + await this.store.setString(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setString(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getString(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getString(otherSlot)).to.be.equal(''); + }); + }); + }); + + describe('string storage pointer', function () { + beforeEach(async function () { + this.value = "lorem ipsum"; + }); + + it('set', async function () { + await this.store.setStringStorage(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setStringStorage(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.stringMap(slot)).to.be.equal(this.value); + expect(await this.store.getStringStorage(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.stringMap(otherSlot)).to.be.equal(''); + expect(await this.store.getStringStorage(otherSlot)).to.be.equal(''); + }); + }); + }); + + describe('bytes storage slot', function () { + beforeEach(async function () { + this.value = web3.utils.randomHex(128); + }); + + it('set', async function () { + await this.store.setBytes(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setBytes(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getBytes(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getBytes(otherSlot)).to.be.equal(null); + }); + }); + }); + + describe('bytes storage pointer', function () { + beforeEach(async function () { + this.value = web3.utils.randomHex(128); + }); + + it('set', async function () { + await this.store.setBytesStorage(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setBytesStorage(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.bytesMap(slot)).to.be.equal(this.value); + expect(await this.store.getBytesStorage(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.bytesMap(otherSlot)).to.be.equal(null); + expect(await this.store.getBytesStorage(otherSlot)).to.be.equal(null); + }); + }); + }); }); From 7c767a3821261a7155924c473190d7330aaabd89 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 27 Jan 2023 15:59:27 +0100 Subject: [PATCH 02/16] add ShortStrings library --- contracts/utils/ShortStrings.sol | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 contracts/utils/ShortStrings.sol diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol new file mode 100644 index 00000000000..d0b97059865 --- /dev/null +++ b/contracts/utils/ShortStrings.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.8; + +import "./StorageSlot.sol"; + +/** + * @dev Short string operations. + */ +library ShortStrings { + type ShortString is bytes32; + + error StringTooLong(string str); + + /** + * @dev Encode a string of at most 31 chars into a `ShortString`. + * + * This will trigger a `StringTooLong` error is the input string is too long. + */ + function toShortString(string memory str) internal pure returns (ShortString) { + bytes memory bstr = bytes(str); + if (bstr.length > 31) { + revert StringTooLong(str); + } + return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length)); + } + + /** + * @dev Decode a `ShortString` back to a "normal" string. + */ + function toString(ShortString sstr) internal pure returns (string memory) { + uint256 len = length(sstr); + // using `new string(len)` would work locally but is not memory safe. + string memory str = new string(32); + /// @solidity memory-safe-assembly + assembly { + mstore(str, len) + mstore(add(str, 0x20), sstr) + } + return str; + } + + /** + * @dev Return the length of a `ShortString`. + */ + function length(ShortString sstr) internal pure returns (uint256) { + return uint256(ShortString.unwrap(sstr)) & 0xFF; + } + + /** + * @dev Encode a string into a `ShortString`, or write it to storage if it is too long. + */ + function setWithFallback(string memory value, string storage store) internal returns (ShortString) { + if (bytes(value).length < 32) { + return toShortString(value); + } else { + StorageSlot.getStringSlot(store).value = value; + return toShortString(""); + } + } + + /** + * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. + */ + function getWithFallback(ShortString value, string storage store) internal pure returns (string memory) { + if (length(value) > 0) { + return toString(value); + } else { + return store; + } + } +} From 014851050ccd4642d36a90262ddf5c1b709df086 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 27 Jan 2023 16:08:35 +0100 Subject: [PATCH 03/16] fix lint --- test/utils/StorageSlot.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js index 6ae7345d696..846512ed2ad 100644 --- a/test/utils/StorageSlot.test.js +++ b/test/utils/StorageSlot.test.js @@ -110,7 +110,7 @@ contract('StorageSlot', function (accounts) { describe('string storage slot', function () { beforeEach(async function () { - this.value = "lorem ipsum"; + this.value = 'lorem ipsum'; }); it('set', async function () { @@ -134,7 +134,7 @@ contract('StorageSlot', function (accounts) { describe('string storage pointer', function () { beforeEach(async function () { - this.value = "lorem ipsum"; + this.value = 'lorem ipsum'; }); it('set', async function () { From f0ce4b5e6556309dce18925e0d1179d6c72f191b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 1 Feb 2023 23:40:21 +0100 Subject: [PATCH 04/16] add changeset --- .changeset/violet-frogs-hide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/violet-frogs-hide.md diff --git a/.changeset/violet-frogs-hide.md b/.changeset/violet-frogs-hide.md new file mode 100644 index 00000000000..5efccc173ce --- /dev/null +++ b/.changeset/violet-frogs-hide.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ShortStrings`: New library for handling shortstring storage in immutable variables. From e33f0b8279deffa0a5338ef1611b9f83e61995c5 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 2 Feb 2023 10:51:46 +0100 Subject: [PATCH 05/16] update dependency --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51d350ac78a..a663055fdff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "glob": "^8.0.3", "graphlib": "^2.1.8", "hardhat": "^2.9.1", - "hardhat-exposed": "^0.3.0", + "hardhat-exposed": "^0.3.1", "hardhat-gas-reporter": "^1.0.4", "hardhat-ignore-warnings": "^0.2.0", "keccak256": "^1.0.2", @@ -7923,9 +7923,9 @@ } }, "node_modules/hardhat-exposed": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.0.tgz", - "integrity": "sha512-1p2Aou7QW3VVI0iJhh3q9hgPyF66zggeW7v/PrcipniQqaXK+KxJnnJvzGsLvXYzB8lVp23GIK7MXoTjjyXkHQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.1.tgz", + "integrity": "sha512-qyHXdS3NmzrtXF+XL547BMsTAK+IEMW9OOYMH4d362DlPn4L2B2KXKG6OpuJczxYjgMFJ0LunLxNgu6jtRvjRg==", "dev": true, "dependencies": { "micromatch": "^4.0.4", @@ -22753,9 +22753,9 @@ } }, "hardhat-exposed": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.0.tgz", - "integrity": "sha512-1p2Aou7QW3VVI0iJhh3q9hgPyF66zggeW7v/PrcipniQqaXK+KxJnnJvzGsLvXYzB8lVp23GIK7MXoTjjyXkHQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.1.tgz", + "integrity": "sha512-qyHXdS3NmzrtXF+XL547BMsTAK+IEMW9OOYMH4d362DlPn4L2B2KXKG6OpuJczxYjgMFJ0LunLxNgu6jtRvjRg==", "dev": true, "requires": { "micromatch": "^4.0.4", diff --git a/package.json b/package.json index de2951bd193..66d44888aca 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "glob": "^8.0.3", "graphlib": "^2.1.8", "hardhat": "^2.9.1", - "hardhat-exposed": "^0.3.0", + "hardhat-exposed": "^0.3.1", "hardhat-gas-reporter": "^1.0.4", "hardhat-ignore-warnings": "^0.2.0", "keccak256": "^1.0.2", From 7162f0879a2c22a2c37e72cf71680c31f393360c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 2 Feb 2023 11:24:32 +0100 Subject: [PATCH 06/16] add tests --- test/utils/ShortStrings.test.js | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/utils/ShortStrings.test.js diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js new file mode 100644 index 00000000000..5bc7423507e --- /dev/null +++ b/test/utils/ShortStrings.test.js @@ -0,0 +1,42 @@ +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const ShortStrings = artifacts.require('$ShortStrings'); + +contract('ShortStrings', function () { + before(async function () { + this.mock = await ShortStrings.new(); + }); + + for (const str of [0, 1, 16, 31, 32, 64, 1024].map(length => 'a'.repeat(length))) { + describe(`with string length ${str.length}`, function () { + it('encode / decode', async function () { + if (str.length < 32) { + const encoded = await this.mock.$toShortString(str); + const encoded_len = parseInt(encoded.slice(64), 16); + const encoded_str = web3.utils.toUtf8(encoded).slice(0, encoded_len); + expect(encoded_str).to.be.equal(str); + + const length = await this.mock.$length(encoded); + expect(length.toNumber()).to.be.equal(str.length); + + const decoded = await this.mock.$toString(encoded); + expect(decoded).to.be.equal(str); + } else { + await expectRevertCustomError(this.mock.$toShortString(str), `StringTooLong("${str}")`); + } + }); + + it('set / get with fallback', async function () { + const { ret0 } = await this.mock + .$setWithFallback(str, 0) + .then(({ logs }) => logs.find(({ event }) => event == 'return$setWithFallback').args); + + expect(await this.mock.$toString(ret0)).to.be.equal(str.length < 32 ? str : ''); + + const recovered = await this.mock.$getWithFallback(ret0, 0); + expect(recovered).to.be.equal(str); + }); + }); + } +}); From b798d38ba0712938bda99226f2ffa9c70656bc22 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 2 Feb 2023 11:24:55 +0100 Subject: [PATCH 07/16] add doc --- contracts/utils/README.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 7fef825a6fd..5e8af93e317 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -106,6 +106,8 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar {{Strings}} +{{ShortStrings}} + {{StorageSlot}} {{Multicall}} From 9f9053e09a1b0ef74ae0d04033a8fb98faebad28 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 2 Feb 2023 11:27:11 +0100 Subject: [PATCH 08/16] simplify --- test/utils/ShortStrings.test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 5bc7423507e..81103025e85 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -3,6 +3,10 @@ const { expectRevertCustomError } = require('../helpers/customError'); const ShortStrings = artifacts.require('$ShortStrings'); +function decode(sstr) { + return web3.utils.toUtf8(sstr).slice(0, parseInt(sstr.slice(64), 16)); +} + contract('ShortStrings', function () { before(async function () { this.mock = await ShortStrings.new(); @@ -13,9 +17,7 @@ contract('ShortStrings', function () { it('encode / decode', async function () { if (str.length < 32) { const encoded = await this.mock.$toShortString(str); - const encoded_len = parseInt(encoded.slice(64), 16); - const encoded_str = web3.utils.toUtf8(encoded).slice(0, encoded_len); - expect(encoded_str).to.be.equal(str); + expect(decode(encoded)).to.be.equal(str); const length = await this.mock.$length(encoded); expect(length.toNumber()).to.be.equal(str.length); From f1f1bf5098628f285772f2eaf88a2a0ce30fc514 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 10:20:50 +0100 Subject: [PATCH 09/16] Update .changeset/violet-frogs-hide.md Co-authored-by: Francisco --- .changeset/violet-frogs-hide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/violet-frogs-hide.md b/.changeset/violet-frogs-hide.md index 5efccc173ce..21d2bf9848c 100644 --- a/.changeset/violet-frogs-hide.md +++ b/.changeset/violet-frogs-hide.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -`ShortStrings`: New library for handling shortstring storage in immutable variables. +`ShortStrings`: Added a library for handling short strings in a gas efficient way, with fallback to storage for longer strings. From 938e65252773a44b21518f374bb57e538bb0ecf8 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 10:21:54 +0100 Subject: [PATCH 10/16] Update test/utils/ShortStrings.test.js Co-authored-by: Francisco --- test/utils/ShortStrings.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 81103025e85..2119ee53e33 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -4,7 +4,8 @@ const { expectRevertCustomError } = require('../helpers/customError'); const ShortStrings = artifacts.require('$ShortStrings'); function decode(sstr) { - return web3.utils.toUtf8(sstr).slice(0, parseInt(sstr.slice(64), 16)); + const length = parseInt(sstr.slice(64), 16); + return web3.utils.toUtf8(sstr).slice(0, length); } contract('ShortStrings', function () { From 7856efbbee332e8434c037949e777804d88796e4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 10:22:17 +0100 Subject: [PATCH 11/16] Update test/utils/ShortStrings.test.js Co-authored-by: Francisco --- test/utils/ShortStrings.test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 2119ee53e33..80c680fecfa 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -31,9 +31,8 @@ contract('ShortStrings', function () { }); it('set / get with fallback', async function () { - const { ret0 } = await this.mock - .$setWithFallback(str, 0) - .then(({ logs }) => logs.find(({ event }) => event == 'return$setWithFallback').args); + const { logs } = await this.mock.$setWithFallback(str, 0); + const { ret0 } = logs.find(({ event }) => event == 'return$setWithFallback').args); expect(await this.mock.$toString(ret0)).to.be.equal(str.length < 32 ? str : ''); From bc454a786a0d41fc55864fe1d182b3c9a558589d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 10:22:29 +0100 Subject: [PATCH 12/16] Update contracts/utils/ShortStrings.sol Co-authored-by: Francisco --- contracts/utils/ShortStrings.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index d0b97059865..18dc17a2098 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -5,7 +5,10 @@ pragma solidity ^0.8.8; import "./StorageSlot.sol"; /** - * @dev Short string operations. + * @dev This library provides functions to convert short memory strings + * into a `ShortString` type that can be used as an immutable variable. + * Strings of arbitrary length can be optimized if they are short enough by + * the addition of a storage variable used as fallback. */ library ShortStrings { type ShortString is bytes32; From 7daff43fc6a5892ff547f2aed377ea4c4fa4140d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 10:24:07 +0100 Subject: [PATCH 13/16] update --- contracts/utils/ShortStrings.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index d0b97059865..62e3247e54a 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.8; import "./StorageSlot.sol"; +type ShortString is bytes32; + /** * @dev Short string operations. */ library ShortStrings { - type ShortString is bytes32; - error StringTooLong(string str); /** @@ -55,7 +55,7 @@ library ShortStrings { return toShortString(value); } else { StorageSlot.getStringSlot(store).value = value; - return toShortString(""); + return ShortString.wrap(0); } } From 5ec62114a1294e77c37bf54aded36f23ee7cf837 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 10:27:02 +0100 Subject: [PATCH 14/16] fix test --- test/utils/ShortStrings.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 80c680fecfa..697f9c21984 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -32,7 +32,7 @@ contract('ShortStrings', function () { it('set / get with fallback', async function () { const { logs } = await this.mock.$setWithFallback(str, 0); - const { ret0 } = logs.find(({ event }) => event == 'return$setWithFallback').args); + const { ret0 } = logs.find(({ event }) => event == 'return$setWithFallback').args; expect(await this.mock.$toString(ret0)).to.be.equal(str.length < 32 ? str : ''); From 98c0ba60073568219f3074326a5e3b0ef3afeab9 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 22:53:17 +0100 Subject: [PATCH 15/16] rename functions --- contracts/utils/ShortStrings.sol | 4 ++-- test/utils/ShortStrings.test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 48fca82366a..4cbef6d6d82 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -53,7 +53,7 @@ library ShortStrings { /** * @dev Encode a string into a `ShortString`, or write it to storage if it is too long. */ - function setWithFallback(string memory value, string storage store) internal returns (ShortString) { + function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) { if (bytes(value).length < 32) { return toShortString(value); } else { @@ -65,7 +65,7 @@ library ShortStrings { /** * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. */ - function getWithFallback(ShortString value, string storage store) internal pure returns (string memory) { + function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) { if (length(value) > 0) { return toString(value); } else { diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index 697f9c21984..f41084a7e5b 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -31,12 +31,12 @@ contract('ShortStrings', function () { }); it('set / get with fallback', async function () { - const { logs } = await this.mock.$setWithFallback(str, 0); - const { ret0 } = logs.find(({ event }) => event == 'return$setWithFallback').args; + const { logs } = await this.mock.$toShortStringWithFallback(str, 0); + const { ret0 } = logs.find(({ event }) => event == 'return$toShortStringWithFallback').args; expect(await this.mock.$toString(ret0)).to.be.equal(str.length < 32 ? str : ''); - const recovered = await this.mock.$getWithFallback(ret0, 0); + const recovered = await this.mock.$toStringWithFallback(ret0, 0); expect(recovered).to.be.equal(str); }); }); From fb9aac41318402ebe7976fe7b9e5abc25fee8d9b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 3 Feb 2023 22:57:42 +0100 Subject: [PATCH 16/16] add example --- contracts/utils/ShortStrings.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 4cbef6d6d82..9f253df8267 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -11,6 +11,25 @@ type ShortString is bytes32; * into a `ShortString` type that can be used as an immutable variable. * Strings of arbitrary length can be optimized if they are short enough by * the addition of a storage variable used as fallback. + * + * Usage example: + * + * ```solidity + * contract Named { + * using ShortStrings for *; + * + * ShortString private immutable _name; + * string private _nameFallback; + * + * constructor(string memory contractName) { + * _name = contractName.toShortStringWithFallback(_nameFallback); + * } + * + * function name() external view returns (string memory) { + * return _name.toStringWithFallback(_nameFallback); + * } + * } + * ``` */ library ShortStrings { error StringTooLong(string str);