Skip to content

Commit

Permalink
test(store): add gas comparisons for Storage.sol (#1192)
Browse files Browse the repository at this point in the history
  • Loading branch information
dk1a authored Jul 27, 2023
1 parent 942985c commit fbcd6e5
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 5 deletions.
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": 3370
"gasUsed": 3354
},
{
"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": 1453
"gasUsed": 1457
},
{
"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 { Mixed, MixedData } from "../src/codegen/Tables.sol";

contract SomeContract {
Expand Down Expand Up @@ -48,4 +49,161 @@ contract GasTest is Test, GasReporter {

assertEq(abi.encode(abiDecoded), 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));
}
}

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())));
}
}
}

0 comments on commit fbcd6e5

Please sign in to comment.