From 0d12db8c2170905f5116111e6bc417b6dca8eb61 Mon Sep 17 00:00:00 2001 From: dk1a Date: Fri, 18 Aug 2023 16:36:36 +0300 Subject: [PATCH] refactor(store): optimize Schema (#1252) --- .changeset/strong-geckos-shake.md | 7 ++ packages/store/gas-report.json | 158 ++++++++++++++++-------------- packages/store/src/Schema.sol | 101 ++++++++++--------- packages/store/src/StoreCore.sol | 14 +-- packages/store/test/Schema.t.sol | 36 +++++-- packages/world/gas-report.json | 98 +++++++++--------- 6 files changed, 230 insertions(+), 184 deletions(-) create mode 100644 .changeset/strong-geckos-shake.md diff --git a/.changeset/strong-geckos-shake.md b/.changeset/strong-geckos-shake.md new file mode 100644 index 0000000000..88111ad99b --- /dev/null +++ b/.changeset/strong-geckos-shake.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/store": patch +"@latticexyz/world": patch +--- + +Optimize Schema methods. +Return `uint256` instead of `uint8` in SchemaInstance numFields methods diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 1e94000ce1..3bfaafc4e6 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -243,7 +243,7 @@ "file": "test/KeyEncoding.t.sol", "test": "testRegisterAndGetSchema", "name": "register KeyEncoding schema", - "gasUsed": 679626 + "gasUsed": 671539 }, { "file": "test/Mixed.t.sol", @@ -255,19 +255,19 @@ "file": "test/Mixed.t.sol", "test": "testRegisterAndGetSchema", "name": "register Mixed schema", - "gasUsed": 540065 + "gasUsed": 533230 }, { "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "set record in Mixed", - "gasUsed": 111151 + "gasUsed": 109280 }, { "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "get record from Mixed", - "gasUsed": 12294 + "gasUsed": 10944 }, { "file": "test/PackedCounter.t.sol", @@ -293,29 +293,41 @@ "name": "get total of PackedCounter", "gasUsed": 27 }, + { + "file": "test/Schema.t.sol", + "test": "testEncodeDecodeSchema", + "name": "initialize schema array with 6 entries", + "gasUsed": 856 + }, { "file": "test/Schema.t.sol", "test": "testEncodeDecodeSchema", "name": "encode schema with 6 entries", - "gasUsed": 5639 + "gasUsed": 2814 }, { "file": "test/Schema.t.sol", "test": "testEncodeDecodeSchema", "name": "get schema type at index", - "gasUsed": 185 + "gasUsed": 127 }, { "file": "test/Schema.t.sol", "test": "testGetNumDynamicFields", "name": "get number of dynamic fields from schema", - "gasUsed": 74 + "gasUsed": 68 + }, + { + "file": "test/Schema.t.sol", + "test": "testGetNumFields", + "name": "get number of all fields from schema", + "gasUsed": 34 }, { "file": "test/Schema.t.sol", "test": "testGetNumStaticFields", "name": "get number of static fields from schema", - "gasUsed": 85 + "gasUsed": 79 }, { "file": "test/Schema.t.sol", @@ -339,7 +351,7 @@ "file": "test/Schema.t.sol", "test": "testValidate", "name": "validate schema", - "gasUsed": 16381 + "gasUsed": 11163 }, { "file": "test/Slice.t.sol", @@ -435,25 +447,25 @@ "file": "test/StoreCoreDynamic.t.sol", "test": "testGetFieldSlice", "name": "get field slice (cold, 1 slot)", - "gasUsed": 7996 + "gasUsed": 7990 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testGetFieldSlice", "name": "get field slice (warm, 1 slot)", - "gasUsed": 2065 + "gasUsed": 2059 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testGetFieldSlice", "name": "get field slice (semi-cold, 1 slot)", - "gasUsed": 4070 + "gasUsed": 4064 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testGetFieldSlice", "name": "get field slice (warm, 2 slots)", - "gasUsed": 4296 + "gasUsed": 4290 }, { "file": "test/StoreCoreDynamic.t.sol", @@ -483,43 +495,43 @@ "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromSecondField", "name": "pop from field (cold, 1 slot, 1 uint32 item)", - "gasUsed": 23215 + "gasUsed": 22845 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromSecondField", "name": "pop from field (warm, 1 slot, 1 uint32 item)", - "gasUsed": 17245 + "gasUsed": 16875 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromThirdField", "name": "pop from field (cold, 2 slots, 10 uint32 items)", - "gasUsed": 24964 + "gasUsed": 24594 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromThirdField", "name": "pop from field (warm, 2 slots, 10 uint32 items)", - "gasUsed": 16995 + "gasUsed": 16625 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access non-existing record", - "gasUsed": 6090 + "gasUsed": 6084 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access static field of non-existing record", - "gasUsed": 1571 + "gasUsed": 1507 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access dynamic field of non-existing record", - "gasUsed": 2233 + "gasUsed": 2227 }, { "file": "test/StoreCoreGas.t.sol", @@ -531,115 +543,115 @@ "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access slice of dynamic field of non-existing record", - "gasUsed": 1203 + "gasUsed": 1197 }, { "file": "test/StoreCoreGas.t.sol", "test": "testDeleteData", "name": "delete record (complex data, 3 slots)", - "gasUsed": 8983 + "gasUsed": 8613 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHasSchema", "name": "Check for existence of table (existent)", - "gasUsed": 6819 + "gasUsed": 5525 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHasSchema", "name": "check for existence of table (non-existent)", - "gasUsed": 8821 + "gasUsed": 7527 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "register subscriber", - "gasUsed": 62181 + "gasUsed": 61453 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set record on table with subscriber", - "gasUsed": 71606 + "gasUsed": 70854 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set static field on table with subscriber", - "gasUsed": 27542 + "gasUsed": 26686 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "delete record on table with subscriber", - "gasUsed": 19988 + "gasUsed": 19248 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "register subscriber", - "gasUsed": 62181 + "gasUsed": 61453 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) record on table with subscriber", - "gasUsed": 164934 + "gasUsed": 164158 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) field on table with subscriber", - "gasUsed": 30424 + "gasUsed": 29684 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "delete (dynamic) record on table with subscriber", - "gasUsed": 21545 + "gasUsed": 20855 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (1 slot, 1 uint32 item)", - "gasUsed": 14881 + "gasUsed": 14511 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (2 slots, 10 uint32 items)", - "gasUsed": 37528 + "gasUsed": 37158 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetSchema", "name": "StoreCore: register schema", - "gasUsed": 601182 + "gasUsed": 596588 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetSchema", "name": "StoreCore: get schema (warm)", - "gasUsed": 6833 + "gasUsed": 5539 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetSchema", "name": "StoreCore: get key schema (warm)", - "gasUsed": 13048 + "gasUsed": 10518 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "set complex record with dynamic data (4 slots)", - "gasUsed": 103624 + "gasUsed": 103230 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "get complex record with dynamic data (4 slots)", - "gasUsed": 5101 + "gasUsed": 5095 }, { "file": "test/StoreCoreGas.t.sol", @@ -675,85 +687,85 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (1 slot)", - "gasUsed": 33919 + "gasUsed": 33491 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get static field (1 slot)", - "gasUsed": 1572 + "gasUsed": 1508 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (overlap 2 slot)", - "gasUsed": 32854 + "gasUsed": 32368 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get static field (overlap 2 slot)", - "gasUsed": 2386 + "gasUsed": 2264 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, first dynamic field)", - "gasUsed": 55312 + "gasUsed": 54942 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get dynamic field (1 slot, first dynamic field)", - "gasUsed": 2410 + "gasUsed": 2404 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, second dynamic field)", - "gasUsed": 33430 + "gasUsed": 33060 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get dynamic field (1 slot, second dynamic field)", - "gasUsed": 2418 + "gasUsed": 2412 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "set static record (1 slot)", - "gasUsed": 33393 + "gasUsed": 33017 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "get static record (1 slot)", - "gasUsed": 1269 + "gasUsed": 1263 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "set static record (2 slots)", - "gasUsed": 55897 + "gasUsed": 55521 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "get static record (2 slots)", - "gasUsed": 1457 + "gasUsed": 1451 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "update in field (1 slot, 1 uint32 item)", - "gasUsed": 14416 + "gasUsed": 14046 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "push to field (2 slots, 6 uint64 items)", - "gasUsed": 15445 + "gasUsed": 15075 }, { "file": "test/StoreSwitch.t.sol", @@ -771,103 +783,103 @@ "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "Callbacks: set field", - "gasUsed": 59939 + "gasUsed": 58949 }, { "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "Callbacks: get field (warm)", - "gasUsed": 5287 + "gasUsed": 4790 }, { "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "Callbacks: push 1 element", - "gasUsed": 39813 + "gasUsed": 38823 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: set field (cold)", - "gasUsed": 61928 + "gasUsed": 60938 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: get field (warm)", - "gasUsed": 5281 + "gasUsed": 4784 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: push 1 element (cold)", - "gasUsed": 39799 + "gasUsed": 38809 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: pop 1 element (warm)", - "gasUsed": 16265 + "gasUsed": 15275 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: push 1 element (warm)", - "gasUsed": 17957 + "gasUsed": 16967 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: update 1 element (warm)", - "gasUsed": 17664 + "gasUsed": 16670 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: delete record (warm)", - "gasUsed": 11002 + "gasUsed": 10008 }, { "file": "test/tables/Hooks.t.sol", "test": "testTable", "name": "Hooks: set field (warm)", - "gasUsed": 34154 + "gasUsed": 33164 }, { "file": "test/tables/HooksColdLoad.t.sol", "test": "testDelete", "name": "Hooks: delete record (cold)", - "gasUsed": 19797 + "gasUsed": 18803 }, { "file": "test/tables/HooksColdLoad.t.sol", "test": "testGet", "name": "Hooks: get field (cold)", - "gasUsed": 11270 + "gasUsed": 10773 }, { "file": "test/tables/HooksColdLoad.t.sol", "test": "testGetItem", "name": "Hooks: get 1 element (cold)", - "gasUsed": 7560 + "gasUsed": 7063 }, { "file": "test/tables/HooksColdLoad.t.sol", "test": "testLength", "name": "Hooks: get length (cold)", - "gasUsed": 7482 + "gasUsed": 6991 }, { "file": "test/tables/HooksColdLoad.t.sol", "test": "testPop", "name": "Hooks: pop 1 element (cold)", - "gasUsed": 26386 + "gasUsed": 25396 }, { "file": "test/tables/HooksColdLoad.t.sol", "test": "testUpdate", "name": "Hooks: update 1 element (cold)", - "gasUsed": 27237 + "gasUsed": 26243 }, { "file": "test/tightcoder/DecodeSlice.t.sol", @@ -921,18 +933,18 @@ "file": "test/Vector2.t.sol", "test": "testRegisterAndGetSchema", "name": "register Vector2 schema", - "gasUsed": 400685 + "gasUsed": 394654 }, { "file": "test/Vector2.t.sol", "test": "testSetAndGet", "name": "set Vector2 record", - "gasUsed": 36708 + "gasUsed": 35269 }, { "file": "test/Vector2.t.sol", "test": "testSetAndGet", "name": "get Vector2 record", - "gasUsed": 4556 + "gasUsed": 3620 } ] diff --git a/packages/store/src/Schema.sol b/packages/store/src/Schema.sol index f58f344773..d27084af51 100644 --- a/packages/store/src/Schema.sol +++ b/packages/store/src/Schema.sol @@ -22,54 +22,59 @@ library SchemaLib { // Based on PackedCounter's capacity uint256 internal constant MAX_DYNAMIC_FIELDS = 5; - /************************************************************************ - * - * STATIC FUNCTIONS - * - ************************************************************************/ - /** * Encode the given schema into a single bytes32 */ function encode(SchemaType[] memory _schema) internal pure returns (Schema) { if (_schema.length > 28) revert SchemaLib_InvalidLength(_schema.length); - bytes32 schema; - uint16 length; - uint8 staticFields; + uint256 schema; + uint256 totalLength; + uint256 dynamicFields; // Compute the length of the schema and the number of static fields // and store the schema types in the encoded schema - bool hasDynamicFields; for (uint256 i = 0; i < _schema.length; ) { - uint16 staticByteLength = uint16(_schema[i].getStaticByteLength()); - - // Increase the static field count if the field is static - if (staticByteLength > 0) { + uint256 staticByteLength = _schema[i].getStaticByteLength(); + + if (staticByteLength == 0) { + // Increase the dynamic field count if the field is dynamic + // (safe because of the initial _schema.length check) + unchecked { + dynamicFields++; + } + } else if (dynamicFields > 0) { // Revert if we have seen a dynamic field before, but now we see a static field - if (hasDynamicFields) revert SchemaLib_StaticTypeAfterDynamicType(); - staticFields++; - } else { - // Flag that we have seen a dynamic field - hasDynamicFields = true; + revert SchemaLib_StaticTypeAfterDynamicType(); } - length += staticByteLength; - schema = Bytes.setBytes1(schema, i + 4, bytes1(uint8(_schema[i]))); unchecked { + // (safe because 28 (max _schema.length) * 32 (max static length) < 2**16) + totalLength += staticByteLength; + // Sequentially store schema types after the first 4 bytes (which are reserved for length and field numbers) + // (safe because of the initial _schema.length check) + schema |= uint256(_schema[i]) << ((31 - 4 - i) * 8); i++; } } // Require MAX_DYNAMIC_FIELDS - uint8 dynamicFields = uint8(_schema.length) - staticFields; if (dynamicFields > MAX_DYNAMIC_FIELDS) revert SchemaLib_InvalidLength(dynamicFields); - // Store total static length, and number of static and dynamic fields - schema = Bytes.setBytes2(schema, 0, (bytes2(length))); // 2 length bytes - schema = Bytes.setBytes1(schema, 2, bytes1(staticFields)); // number of static fields - schema = Bytes.setBytes1(schema, 3, bytes1(dynamicFields)); // number of dynamic fields + // Get the static field count + uint256 staticFields; + unchecked { + staticFields = _schema.length - dynamicFields; + } + + // Store total static length in the first 2 bytes, + // number of static fields in the 3rd byte, + // number of dynamic fields in the 4th byte + // (optimizer can handle this, no need for unchecked or single-line assignment) + schema |= totalLength << ((32 - 2) * 8); + schema |= staticFields << ((32 - 2 - 1) * 8); + schema |= dynamicFields << ((32 - 2 - 1 - 1) * 8); - return Schema.wrap(schema); + return Schema.wrap(bytes32(schema)); } } @@ -77,45 +82,43 @@ library SchemaLib { * Instance functions for Schema */ library SchemaInstance { - /************************************************************************ - * - * INSTANCE FUNCTIONS - * - ************************************************************************/ - /** * Get the length of the static data for the given schema */ function staticDataLength(Schema schema) internal pure returns (uint256) { - return uint256(uint16(bytes2(Schema.unwrap(schema)))); + return uint256(Schema.unwrap(schema)) >> ((32 - 2) * 8); } /** * Get the type of the data for the given schema at the given index */ function atIndex(Schema schema, uint256 index) internal pure returns (SchemaType) { - return SchemaType(uint8(Bytes.slice1(Schema.unwrap(schema), index + 4))); + unchecked { + return SchemaType(uint8(uint256(schema.unwrap()) >> ((31 - 4 - index) * 8))); + } } /** * Get the number of dynamic length fields for the given schema */ - function numDynamicFields(Schema schema) internal pure returns (uint8) { - return uint8(Bytes.slice1(Schema.unwrap(schema), 3)); + function numDynamicFields(Schema schema) internal pure returns (uint256) { + return uint8(uint256(schema.unwrap()) >> ((31 - 3) * 8)); } /** - * Get the number of static fields for the given schema + * Get the number of static fields for the given schema */ - function numStaticFields(Schema schema) internal pure returns (uint8) { - return uint8(Bytes.slice1(Schema.unwrap(schema), 2)); + function numStaticFields(Schema schema) internal pure returns (uint256) { + return uint8(uint256(schema.unwrap()) >> ((31 - 2) * 8)); } /** * Get the total number of fields for the given schema */ - function numFields(Schema schema) internal pure returns (uint8) { - return numStaticFields(schema) + numDynamicFields(schema); + function numFields(Schema schema) internal pure returns (uint256) { + unchecked { + return uint8(uint256(schema.unwrap()) >> ((31 - 3) * 8)) + uint8(uint256(schema.unwrap()) >> ((31 - 2) * 8)); + } } /** @@ -135,21 +138,25 @@ library SchemaInstance { uint256 _numStaticFields = schema.numStaticFields(); // Schema must not have more than 28 fields in total - if (_numStaticFields + _numDynamicFields > 28) - revert SchemaLib.SchemaLib_InvalidLength(_numStaticFields + _numDynamicFields); + uint256 _numTotalFields = _numStaticFields + _numDynamicFields; + if (_numTotalFields > 28) revert SchemaLib.SchemaLib_InvalidLength(_numTotalFields); // No static field can be after a dynamic field uint256 countStaticFields; uint256 countDynamicFields; - for (uint256 i; i < _numStaticFields + _numDynamicFields; ) { + for (uint256 i; i < _numTotalFields; ) { if (schema.atIndex(i).getStaticByteLength() > 0) { // Static field in dynamic part if (i >= _numStaticFields) revert SchemaLib.SchemaLib_StaticTypeAfterDynamicType(); - countStaticFields++; + unchecked { + countStaticFields++; + } } else { // Dynamic field in static part if (i < _numStaticFields) revert SchemaLib.SchemaLib_StaticTypeAfterDynamicType(); - countDynamicFields++; + unchecked { + countDynamicFields++; + } } unchecked { i++; diff --git a/packages/store/src/StoreCore.sol b/packages/store/src/StoreCore.sol index 82e1b5005b..f3b5776476 100644 --- a/packages/store/src/StoreCore.sol +++ b/packages/store/src/StoreCore.sol @@ -481,7 +481,7 @@ library StoreCore { uint8 schemaIndex, Schema valueSchema ) internal view returns (uint256) { - uint8 numStaticFields = valueSchema.numStaticFields(); + uint8 numStaticFields = uint8(valueSchema.numStaticFields()); if (schemaIndex < numStaticFields) { SchemaType schemaType = valueSchema.atIndex(schemaIndex); return schemaType.getStaticByteLength(); @@ -504,7 +504,7 @@ library StoreCore { uint256 start, uint256 end ) internal view returns (bytes memory) { - uint8 numStaticFields = valueSchema.numStaticFields(); + uint8 numStaticFields = uint8(valueSchema.numStaticFields()); if (schemaIndex < valueSchema.numStaticFields()) { revert IStoreErrors.StoreCore_NotDynamicField(); } @@ -552,7 +552,7 @@ library StoreCoreInternal { uint8 schemaIndex, bytes memory data ) internal { - uint8 dynamicSchemaIndex = schemaIndex - valueSchema.numStaticFields(); + uint8 dynamicSchemaIndex = schemaIndex - uint8(valueSchema.numStaticFields()); // Update the dynamic data length _setDynamicDataLengthAtIndex(tableId, key, dynamicSchemaIndex, data.length); @@ -569,7 +569,7 @@ library StoreCoreInternal { uint8 schemaIndex, bytes memory dataToPush ) internal { - uint8 dynamicSchemaIndex = schemaIndex - valueSchema.numStaticFields(); + uint8 dynamicSchemaIndex = schemaIndex - uint8(valueSchema.numStaticFields()); // Load dynamic data length from storage uint256 dynamicSchemaLengthSlot = _getDynamicDataLengthLocation(tableId, key); @@ -593,7 +593,7 @@ library StoreCoreInternal { uint8 schemaIndex, uint256 byteLengthToPop ) internal { - uint8 dynamicSchemaIndex = schemaIndex - valueSchema.numStaticFields(); + uint8 dynamicSchemaIndex = schemaIndex - uint8(valueSchema.numStaticFields()); // Load dynamic data length from storage uint256 dynamicSchemaLengthSlot = _getDynamicDataLengthLocation(tableId, key); @@ -618,7 +618,7 @@ library StoreCoreInternal { uint256 startByteIndex, bytes memory dataToSet ) internal { - uint8 dynamicSchemaIndex = schemaIndex - valueSchema.numStaticFields(); + uint8 dynamicSchemaIndex = schemaIndex - uint8(valueSchema.numStaticFields()); // Set `dataToSet` at the given index _setPartialDynamicData(tableId, key, dynamicSchemaIndex, startByteIndex, dataToSet); @@ -671,7 +671,7 @@ library StoreCoreInternal { Schema valueSchema ) internal view returns (bytes memory) { // Get the length and storage location of the dynamic field - uint8 dynamicSchemaIndex = schemaIndex - valueSchema.numStaticFields(); + uint8 dynamicSchemaIndex = schemaIndex - uint8(valueSchema.numStaticFields()); uint256 location = _getDynamicDataLocation(tableId, key, dynamicSchemaIndex); uint256 dataLength = _loadEncodedDynamicDataLength(tableId, key).atIndex(dynamicSchemaIndex); diff --git a/packages/store/test/Schema.t.sol b/packages/store/test/Schema.t.sol index 9a0aba97d3..cf967093ed 100644 --- a/packages/store/test/Schema.t.sol +++ b/packages/store/test/Schema.t.sol @@ -10,15 +10,18 @@ import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol"; // TODO add tests for all schema types contract SchemaTest is Test, GasReporter { function testEncodeDecodeSchema() public { + startGasReport("initialize schema array with 6 entries"); + SchemaType[] memory _schema = new SchemaType[](6); + _schema[0] = SchemaType.UINT8; // 1 byte + _schema[1] = SchemaType.UINT16; // 2 bytes + _schema[2] = SchemaType.UINT32; // 4 bytes + _schema[3] = SchemaType.UINT128; // 16 bytes + _schema[4] = SchemaType.UINT256; // 32 bytes + _schema[5] = SchemaType.UINT32_ARRAY; // 0 bytes (because it's dynamic) + endGasReport(); + startGasReport("encode schema with 6 entries"); - Schema schema = SchemaEncodeHelper.encode( - SchemaType.UINT8, // 1 byte - SchemaType.UINT16, // 2 bytes - SchemaType.UINT32, // 4 bytes - SchemaType.UINT128, // 16 bytes - SchemaType.UINT256, // 32 bytes - SchemaType.UINT32_ARRAY // 0 bytes (because it's dynamic) - ); + Schema schema = SchemaLib.encode(_schema); endGasReport(); startGasReport("get schema type at index"); @@ -180,6 +183,23 @@ contract SchemaTest is Test, GasReporter { assertEq(num, 1); } + function testGetNumFields() public { + Schema schema = SchemaEncodeHelper.encode( + SchemaType.UINT8, // 1 byte + SchemaType.UINT16, // 2 bytes + SchemaType.UINT32, // 4 bytes + SchemaType.UINT128, // 16 bytes + SchemaType.UINT256, // 32 bytes + SchemaType.UINT32_ARRAY // 0 bytes (because it's dynamic) + ); + + startGasReport("get number of all fields from schema"); + uint256 num = schema.numFields(); + endGasReport(); + + assertEq(num, 6); + } + function testValidate() public { SchemaType[] memory schema = new SchemaType[](28); schema[0] = SchemaType.UINT256; diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 71728ce602..a86fe2413a 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -3,73 +3,73 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1448122 + "gasUsed": 1418450 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1448122 + "gasUsed": 1418450 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", - "gasUsed": 191498 + "gasUsed": 183830 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1448122 + "gasUsed": 1418450 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1448122 + "gasUsed": 1418450 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "change a composite record on a table with keysInTableModule installed", - "gasUsed": 27618 + "gasUsed": 25867 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 282449 + "gasUsed": 256284 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1448122 + "gasUsed": 1418450 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "change a record on a table with keysInTableModule installed", - "gasUsed": 26338 + "gasUsed": 24587 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 144972 + "gasUsed": 131341 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 672214 + "gasUsed": 654924 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "Get list of keys with a given value", - "gasUsed": 7463 + "gasUsed": 7120 }, { "file": "test/KeysWithValueModule.t.sol", @@ -81,222 +81,222 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 672214 + "gasUsed": 654924 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", - "gasUsed": 158204 + "gasUsed": 153345 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 672214 + "gasUsed": 654924 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", - "gasUsed": 125193 + "gasUsed": 120334 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", - "gasUsed": 47302 + "gasUsed": 44242 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 672214 + "gasUsed": 654924 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", - "gasUsed": 165221 + "gasUsed": 160304 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", - "gasUsed": 127479 + "gasUsed": 122562 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueNotQuery", "name": "CombinedHasHasValueNotQuery", - "gasUsed": 189738 + "gasUsed": 166965 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueQuery", "name": "CombinedHasHasValueQuery", - "gasUsed": 85117 + "gasUsed": 76840 }, { "file": "test/query.t.sol", "test": "testCombinedHasNotQuery", "name": "CombinedHasNotQuery", - "gasUsed": 268269 + "gasUsed": 230910 }, { "file": "test/query.t.sol", "test": "testCombinedHasQuery", "name": "CombinedHasQuery", - "gasUsed": 178067 + "gasUsed": 152432 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueNotQuery", "name": "CombinedHasValueNotQuery", - "gasUsed": 166401 + "gasUsed": 144314 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueQuery", "name": "CombinedHasValueQuery", - "gasUsed": 19057 + "gasUsed": 18371 }, { "file": "test/query.t.sol", "test": "testHasQuery", "name": "HasQuery", - "gasUsed": 41223 + "gasUsed": 35094 }, { "file": "test/query.t.sol", "test": "testHasQuery1000Keys", "name": "HasQuery with 1000 keys", - "gasUsed": 10395747 + "gasUsed": 9272856 }, { "file": "test/query.t.sol", "test": "testHasQuery100Keys", "name": "HasQuery with 100 keys", - "gasUsed": 977380 + "gasUsed": 861589 }, { "file": "test/query.t.sol", "test": "testHasValueQuery", "name": "HasValueQuery", - "gasUsed": 9205 + "gasUsed": 8862 }, { "file": "test/query.t.sol", "test": "testNotValueQuery", "name": "NotValueQuery", - "gasUsed": 78711 + "gasUsed": 70434 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 753458 + "gasUsed": 726356 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", - "gasUsed": 69375 + "gasUsed": 65475 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 729850 + "gasUsed": 705186 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", - "gasUsed": 69375 + "gasUsed": 65475 }, { "file": "test/World.t.sol", "test": "testDeleteRecord", "name": "Delete record", - "gasUsed": 13320 + "gasUsed": 12412 }, { "file": "test/World.t.sol", "test": "testPushToField", "name": "Push data to the table", - "gasUsed": 93469 + "gasUsed": 92561 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a fallback system", - "gasUsed": 74905 + "gasUsed": 70459 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a root fallback system", - "gasUsed": 68398 + "gasUsed": 63952 }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 95499 + "gasUsed": 91053 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 145824 + "gasUsed": 140713 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 84309 + "gasUsed": 79863 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 665054 + "gasUsed": 653043 }, { "file": "test/World.t.sol", "test": "testSetField", "name": "Write data to a table field", - "gasUsed": 41910 + "gasUsed": 40944 }, { "file": "test/World.t.sol", "test": "testSetRecord", "name": "Write data to the table", - "gasUsed": 41238 + "gasUsed": 39807 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (cold)", - "gasUsed": 33166 + "gasUsed": 32258 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (warm)", - "gasUsed": 19956 + "gasUsed": 19048 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (cold)", - "gasUsed": 34850 + "gasUsed": 33942 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (warm)", - "gasUsed": 22054 + "gasUsed": 21146 } ]