Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(store): add gas comparisons for Storage.sol #1192

Merged
merged 3 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 113 additions & 5 deletions packages/store/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@
"file": "test/Gas.t.sol",
"test": "testCompareAbiEncodeVsCustom",
"name": "abi encode",
"gasUsed": 957
"gasUsed": 949
},
{
"file": "test/Gas.t.sol",
"test": "testCompareAbiEncodeVsCustom",
"name": "abi decode",
"gasUsed": 1746
"gasUsed": 1738
},
{
"file": "test/Gas.t.sol",
"test": "testCompareAbiEncodeVsCustom",
"name": "custom encode",
"gasUsed": 1812
"gasUsed": 1796
},
{
"file": "test/Gas.t.sol",
Expand All @@ -87,13 +87,121 @@
"file": "test/Gas.t.sol",
"test": "testCompareAbiEncodeVsCustom",
"name": "pass abi encoded bytes to external contract",
"gasUsed": 6551
"gasUsed": 6563
},
{
"file": "test/Gas.t.sol",
"test": "testCompareAbiEncodeVsCustom",
"name": "pass custom encoded bytes to external contract",
"gasUsed": 1376
"gasUsed": 1380
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage write (cold, 1 word)",
"gasUsed": 22469
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage write (cold, 1 word, partial)",
"gasUsed": 22432
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage write (cold, 10 words)",
"gasUsed": 200429
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage write (warm, 1 word)",
"gasUsed": 469
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage write (warm, 1 word, partial)",
"gasUsed": 532
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage write (warm, 10 words)",
"gasUsed": 2429
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage load (warm, 1 word)",
"gasUsed": 630
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage load (warm, 1 word, partial)",
"gasUsed": 577
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageMUD",
"name": "MUD storage load (warm, 10 words)",
"gasUsed": 2655
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage write (cold, 1 word)",
"gasUsed": 22107
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage write (cold, 1 word, partial)",
"gasUsed": 22136
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage write (cold, 10 words)",
"gasUsed": 199902
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage write (warm, 1 word)",
"gasUsed": 107
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage write (warm, 1 word, partial)",
"gasUsed": 236
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage write (warm, 10 words)",
"gasUsed": 1988
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage load (warm, 1 word)",
"gasUsed": 109
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage load (warm, 1 word, partial)",
"gasUsed": 126
},
{
"file": "test/Gas.t.sol",
"test": "testCompareStorageSolidity",
"name": "solidity storage load (warm, 10 words)",
"gasUsed": 1916
},
{
"file": "test/KeyEncoding.t.sol",
Expand Down
158 changes: 158 additions & 0 deletions packages/store/test/Gas.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Test, console } from "forge-std/Test.sol";
import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
import { Bytes } from "../src/Bytes.sol";
import { SliceLib } from "../src/Slice.sol";
import { Storage } from "../src/Storage.sol";
import { EncodeArray } from "../src/tightcoder/EncodeArray.sol";

struct Mixed {
Expand Down Expand Up @@ -55,6 +56,116 @@ contract GasTest is Test, GasReporter {

assertEq(keccak256(abi.encode(abiDecoded)), keccak256(abi.encode(customDecoded)));
}

function testCompareStorageSolidity() public {
(bytes32 valueSimple, bytes16 valuePartial, bytes memory value9Words) = SolidityStorage.generateValues();
(
SolidityStorage.LayoutSimple storage layoutSimple,
SolidityStorage.LayoutPartial storage layoutPartial,
SolidityStorage.LayoutBytes storage layoutBytes
) = SolidityStorage.layouts();

startGasReport("solidity storage write (cold, 1 word)");
layoutSimple.value = valueSimple;
endGasReport();

startGasReport("solidity storage write (cold, 1 word, partial)");
layoutPartial.value = valuePartial;
endGasReport();

// 10 becase length is also stored
startGasReport("solidity storage write (cold, 10 words)");
layoutBytes.value = value9Words;
endGasReport();

// get new values
(valueSimple, valuePartial, value9Words) = SolidityStorage.generateValues();

startGasReport("solidity storage write (warm, 1 word)");
layoutSimple.value = valueSimple;
endGasReport();

startGasReport("solidity storage write (warm, 1 word, partial)");
layoutPartial.value = valuePartial;
endGasReport();

startGasReport("solidity storage write (warm, 10 words)");
layoutBytes.value = value9Words;
endGasReport();

// load

startGasReport("solidity storage load (warm, 1 word)");
valueSimple = layoutSimple.value;
endGasReport();

startGasReport("solidity storage load (warm, 1 word, partial)");
valuePartial = layoutPartial.value;
endGasReport();

startGasReport("solidity storage load (warm, 10 words)");
value9Words = layoutBytes.value;
endGasReport();

// Do something in case the optimizer removes unused assignments
someContract.doSomethingWithBytes(abi.encode(valueSimple, valuePartial, value9Words));
}

// Note that this compares storage writes in isolation, which can be misleading,
// since MUD encoding is dynamic and separate from storage,
// but solidity encoding is hardcoded at compile-time and is part of writing to storage.
// (look for comparison of native storage vs MUD tables for a more comprehensive overview)
function testCompareStorageMUD() public {
(bytes32 valueSimple, bytes16 valuePartial, bytes memory value9Words) = SolidityStorage.generateValues();
bytes memory encodedSimple = abi.encodePacked(valueSimple);
bytes memory encodedPartial = abi.encodePacked(valuePartial);
bytes memory encoded9Words = abi.encodePacked(value9Words.length, value9Words);

startGasReport("MUD storage write (cold, 1 word)");
Storage.store(SolidityStorage.STORAGE_SLOT_SIMPLE, 0, encodedSimple);
endGasReport();

startGasReport("MUD storage write (cold, 1 word, partial)");
Storage.store(SolidityStorage.STORAGE_SLOT_PARTIAL, 16, encodedPartial);
endGasReport();

// 10 becase length is also stored
startGasReport("MUD storage write (cold, 10 words)");
Storage.store(SolidityStorage.STORAGE_SLOT_BYTES, 0, encoded9Words);
endGasReport();

// get new values
(valueSimple, valuePartial, value9Words) = SolidityStorage.generateValues();

startGasReport("MUD storage write (warm, 1 word)");
Storage.store(SolidityStorage.STORAGE_SLOT_SIMPLE, 0, encodedSimple);
endGasReport();

startGasReport("MUD storage write (warm, 1 word, partial)");
Storage.store(SolidityStorage.STORAGE_SLOT_PARTIAL, 16, encodedPartial);
endGasReport();

startGasReport("MUD storage write (warm, 10 words)");
Storage.store(SolidityStorage.STORAGE_SLOT_BYTES, 0, encoded9Words);
endGasReport();

// load

startGasReport("MUD storage load (warm, 1 word)");
encodedSimple = Storage.load(SolidityStorage.STORAGE_SLOT_SIMPLE, encodedSimple.length, 0);
endGasReport();

startGasReport("MUD storage load (warm, 1 word, partial)");
encodedPartial = Storage.load(SolidityStorage.STORAGE_SLOT_PARTIAL, encodedPartial.length, 16);
endGasReport();

startGasReport("MUD storage load (warm, 10 words)");
encoded9Words = Storage.load(SolidityStorage.STORAGE_SLOT_BYTES, encoded9Words.length, 0);
endGasReport();

// Do something in case the optimizer removes unused assignments
someContract.doSomethingWithBytes(abi.encode(encodedSimple, encodedPartial, encoded9Words));
}
}

function customEncode(Mixed memory mixed) pure returns (bytes memory) {
Expand All @@ -70,3 +181,50 @@ function customDecode(bytes memory input) view returns (Mixed memory) {
s: string(SliceLib.getSubslice(input, 20 + 3 * 4, input.length).toBytes())
});
}

library SolidityStorage {
uint256 internal constant STORAGE_SLOT_SIMPLE = uint256(keccak256("mud.store.storage.GasTest.simple"));
uint256 internal constant STORAGE_SLOT_PARTIAL = uint256(keccak256("mud.store.storage.GasTest.partial"));
uint256 internal constant STORAGE_SLOT_BYTES = uint256(keccak256("mud.store.storage.GasTest.bytes"));

struct LayoutSimple {
bytes32 value;
}

struct LayoutPartial {
bytes16 _offset16;
bytes16 value;
}

struct LayoutBytes {
bytes value;
}

function layouts()
internal
pure
returns (LayoutSimple storage layoutSimple, LayoutPartial storage layoutPartial, LayoutBytes storage layoutBytes)
{
uint256 slotSimple = STORAGE_SLOT_SIMPLE;
uint256 slotPartial = STORAGE_SLOT_PARTIAL;
uint256 slotBytes = STORAGE_SLOT_BYTES;
assembly {
layoutSimple.slot := slotSimple
layoutPartial.slot := slotPartial
layoutBytes.slot := slotBytes
}
}

function generateValues()
internal
view
returns (bytes32 valueSimple, bytes16 valuePartial, bytes memory value9Words)
{
valueSimple = keccak256(abi.encode(gasleft()));
valuePartial = bytes16(keccak256(abi.encode(gasleft())));
value9Words = new bytes(256);
for (uint256 i; i < 256; i++) {
value9Words[i] = bytes1(keccak256(abi.encode(gasleft())));
}
}
}