diff --git a/src/HTMLRegistry.sol b/src/HTMLRegistry.sol index 7a67d4c..62b0e61 100644 --- a/src/HTMLRegistry.sol +++ b/src/HTMLRegistry.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.13; import {SSTORE2} from "solady/utils/SSTORE2.sol"; - +import {LibBytes} from "solady/utils/LibBytes.sol"; +import {LibZip} from "solady/utils/LibZip.sol"; interface IOwnable { function owner() external view returns (address); } @@ -44,7 +45,11 @@ contract HTMLRegistry { function _html(address author, address target, uint256 version) internal view returns (bytes memory) { address ptr = sPtrs[author][target][version]; if (ptr == address(0)) return new bytes(0); - return SSTORE2.read(ptr); + bytes memory data = SSTORE2.read(ptr); + + if (bytes4(bytes32(data)) == bytes4(0xffffffff)) return LibZip.flzDecompress(LibBytes.slice(data, 4)); + + return data; } function setHtml(address target, bytes calldata htmlData) external onlyAuthorized(target) { diff --git a/test/HTMLRegistry.t.sol b/test/HTMLRegistry.t.sol index 40a9a93..231a395 100644 --- a/test/HTMLRegistry.t.sol +++ b/test/HTMLRegistry.t.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.13; import {Test} from "forge-std/Test.sol"; +import {console2} from "forge-std/console2.sol"; import {HTMLRegistry} from "../src/HTMLRegistry.sol"; +import {LibZip} from "solady/utils/LibZip.sol"; contract HTMLRegistryTest is Test { HTMLRegistry public registry; @@ -101,6 +103,54 @@ contract HTMLRegistryTest is Test { assertEq(registry.html(alice, alice, 2), new bytes(0)); } + function test_html_decompressesWhenPrefixed() public { + bytes memory compressed = bytes.concat(bytes4(0xffffffff), LibZip.flzCompress(_HTML)); + vm.prank(alice); + registry.setHtml(alice, compressed); + + assertEq(registry.html(alice, alice), _HTML); + } + + function test_html_returnsRawWhenPrefixDoesNotMatch() public { + bytes memory payload = bytes.concat(bytes4(0xdeadbeef), bytes("raw bytes")); + vm.prank(alice); + registry.setHtml(alice, payload); + + assertEq(registry.html(alice, alice), payload); + } + + function test_html_returnsRawWhenDataTooShortForPrefix() public { + bytes memory payload = hex"010203"; + vm.prank(alice); + registry.setHtml(alice, payload); + + assertEq(registry.html(alice, alice), payload); + } + + function test_storageMetrics_plainVsCompressed() public { + bytes memory compressedBody = LibZip.flzCompress(_HTML); + bytes memory compressedPayload = bytes.concat(bytes4(0xffffffff), compressedBody); + + vm.startPrank(alice); + uint256 gasStartPlain = gasleft(); + registry.setHtml(alice, _HTML); + uint256 gasUsedPlain = gasStartPlain - gasleft(); + + uint256 gasStartCompressed = gasleft(); + registry.setHtml(alice, compressedPayload); + uint256 gasUsedCompressed = gasStartCompressed - gasleft(); + vm.stopPrank(); + + assertEq(registry.html(alice, alice, 1), _HTML); + assertEq(registry.html(alice, alice, 2), _HTML); + + console2.log("plain length", _HTML.length); + console2.log("compressed length", compressedBody.length); + console2.log("compressed+selector length", compressedPayload.length); + console2.log("plain write gas", gasUsedPlain); + console2.log("compressed write gas", gasUsedCompressed); + } + function test_emitsHtmlSet() public { vm.startPrank(alice); vm.expectEmit(true, true, false, true);