Skip to content

Commit

Permalink
fix(store): add missing FieldLayout and Schema validations [L-07] (#2046
Browse files Browse the repository at this point in the history
)

Co-authored-by: Kevin Ingersoll <[email protected]>
  • Loading branch information
dk1a and holic authored Jan 18, 2024
1 parent a735e14 commit ad4ac44
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-pianos-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/store": patch
---

Added more validation checks for `FieldLayout` and `Schema`.
16 changes: 14 additions & 2 deletions docs/pages/store/reference/store.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ error Store_TableNotFound(ResourceId tableId, string tableIdString);
error Store_InvalidResourceType(bytes2 expected, ResourceId resourceId, string resourceIdString);
```

### Store_InvalidDynamicDataLength
### Store_InvalidStaticDataLength

```solidity
error Store_InvalidDynamicDataLength(uint256 expected, uint256 received);
error Store_InvalidStaticDataLength(uint256 expected, uint256 received);
```

### Store_IndexOutOfBounds
Expand All @@ -148,6 +148,18 @@ error Store_InvalidFieldNamesLength(uint256 expected, uint256 received);
error Store_InvalidValueSchemaLength(uint256 expected, uint256 received);
```

### Store_InvalidValueSchemaStaticLength

```solidity
error Store_InvalidValueSchemaStaticLength(uint256 expected, uint256 received);
```

### Store_InvalidValueSchemaDynamicLength

```solidity
error Store_InvalidValueSchemaDynamicLength(uint256 expected, uint256 received);
```

### Store_InvalidSplice

```solidity
Expand Down
10 changes: 5 additions & 5 deletions packages/store/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"file": "test/FieldLayout.t.sol",
"test": "testValidate",
"name": "validate field layout",
"gasUsed": 3954
"gasUsed": 6925
},
{
"file": "test/Gas.t.sol",
Expand Down Expand Up @@ -321,7 +321,7 @@
"file": "test/KeyEncoding.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "register KeyEncoding table",
"gasUsed": 719023
"gasUsed": 727536
},
{
"file": "test/Mixed.t.sol",
Expand Down Expand Up @@ -477,7 +477,7 @@
"file": "test/Schema.t.sol",
"test": "testValidate",
"name": "validate schema",
"gasUsed": 11497
"gasUsed": 13777
},
{
"file": "test/Slice.t.sol",
Expand Down Expand Up @@ -765,7 +765,7 @@
"file": "test/StoreCoreGas.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "StoreCore: register table",
"gasUsed": 640905
"gasUsed": 651200
},
{
"file": "test/StoreCoreGas.t.sol",
Expand Down Expand Up @@ -1107,7 +1107,7 @@
"file": "test/Vector2.t.sol",
"test": "testRegisterAndGetFieldLayout",
"name": "register Vector2 field layout",
"gasUsed": 442408
"gasUsed": 451196
},
{
"file": "test/Vector2.t.sol",
Expand Down
27 changes: 21 additions & 6 deletions packages/store/src/FieldLayout.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ library FieldLayoutLib {
error FieldLayoutLib_TooManyFields(uint256 numFields, uint256 maxFields);
error FieldLayoutLib_TooManyDynamicFields(uint256 numFields, uint256 maxFields);
error FieldLayoutLib_Empty();
error FieldLayoutLib_StaticLengthIsZero();
error FieldLayoutLib_StaticLengthDoesNotFitInAWord();
error FieldLayoutLib_InvalidStaticDataLength(uint256 staticDataLength, uint256 computedStaticDataLength);
error FieldLayoutLib_StaticLengthIsZero(uint256 index);
error FieldLayoutLib_StaticLengthIsNotZero(uint256 index);
error FieldLayoutLib_StaticLengthDoesNotFitInAWord(uint256 index);

/**
* @notice Encodes the given field layout into a single bytes32.
Expand All @@ -51,9 +53,9 @@ library FieldLayoutLib {
for (uint256 i = 0; i < _staticFieldLengths.length; ) {
uint256 staticByteLength = _staticFieldLengths[i];
if (staticByteLength == 0) {
revert FieldLayoutLib_StaticLengthIsZero();
revert FieldLayoutLib_StaticLengthIsZero(i);
} else if (staticByteLength > WORD_SIZE) {
revert FieldLayoutLib_StaticLengthDoesNotFitInAWord();
revert FieldLayoutLib_StaticLengthDoesNotFitInAWord(i);
}

unchecked {
Expand Down Expand Up @@ -166,17 +168,30 @@ library FieldLayoutInstance {
}

// Static lengths must be valid
uint256 _staticDataLength;
for (uint256 i; i < _numStaticFields; ) {
uint256 staticByteLength = fieldLayout.atIndex(i);
if (staticByteLength == 0) {
revert FieldLayoutLib.FieldLayoutLib_StaticLengthIsZero();
revert FieldLayoutLib.FieldLayoutLib_StaticLengthIsZero(i);
} else if (staticByteLength > WORD_SIZE) {
revert FieldLayoutLib.FieldLayoutLib_StaticLengthDoesNotFitInAWord();
revert FieldLayoutLib.FieldLayoutLib_StaticLengthDoesNotFitInAWord(i);
}
_staticDataLength += staticByteLength;
unchecked {
i++;
}
}
// Static length sums must match
if (_staticDataLength != fieldLayout.staticDataLength()) {
revert FieldLayoutLib.FieldLayoutLib_InvalidStaticDataLength(fieldLayout.staticDataLength(), _staticDataLength);
}
// Unused fields must be zero
for (uint256 i = _numStaticFields; i < MAX_TOTAL_FIELDS; i++) {
uint256 staticByteLength = fieldLayout.atIndex(i);
if (staticByteLength != 0) {
revert FieldLayoutLib.FieldLayoutLib_StaticLengthIsNotZero(i);
}
}
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/store/src/IStoreErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ interface IStoreErrors {
error Store_TableNotFound(ResourceId tableId, string tableIdString);
error Store_InvalidResourceType(bytes2 expected, ResourceId resourceId, string resourceIdString);

error Store_InvalidDynamicDataLength(uint256 expected, uint256 received);
error Store_InvalidStaticDataLength(uint256 expected, uint256 received);
error Store_IndexOutOfBounds(uint256 length, uint256 accessedIndex);
error Store_InvalidKeyNamesLength(uint256 expected, uint256 received);
error Store_InvalidFieldNamesLength(uint256 expected, uint256 received);
error Store_InvalidValueSchemaLength(uint256 expected, uint256 received);
error Store_InvalidValueSchemaStaticLength(uint256 expected, uint256 received);
error Store_InvalidValueSchemaDynamicLength(uint256 expected, uint256 received);
error Store_InvalidSplice(uint40 startWithinField, uint40 deleteCount, uint40 fieldLength);
}
9 changes: 8 additions & 1 deletion packages/store/src/Schema.sol
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,11 @@ library SchemaInstance {
// No static field can be after a dynamic field
uint256 countStaticFields;
uint256 countDynamicFields;
uint256 _staticDataLength;
for (uint256 i; i < _numTotalFields; ) {
if (schema.atIndex(i).getStaticByteLength() > 0) {
uint256 staticByteLength = schema.atIndex(i).getStaticByteLength();
if (staticByteLength > 0) {
_staticDataLength += staticByteLength;
// Static field in dynamic part
if (i >= _numStaticFields) revert SchemaLib.SchemaLib_StaticTypeAfterDynamicType();
unchecked {
Expand All @@ -189,6 +192,10 @@ library SchemaInstance {
i++;
}
}
// Static length sums must match
if (_staticDataLength != schema.staticDataLength()) {
revert SchemaLib.SchemaLib_InvalidLength(schema.staticDataLength());
}

// Number of static fields must match
if (countStaticFields != _numStaticFields) revert SchemaLib.SchemaLib_InvalidLength(countStaticFields);
Expand Down
22 changes: 22 additions & 0 deletions packages/store/src/StoreCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,28 @@ library StoreCore {
if (valueSchema.numFields() != fieldLayout.numFields()) {
revert IStoreErrors.Store_InvalidValueSchemaLength(fieldLayout.numFields(), valueSchema.numFields());
}
if (valueSchema.numStaticFields() != fieldLayout.numStaticFields()) {
revert IStoreErrors.Store_InvalidValueSchemaStaticLength(
fieldLayout.numStaticFields(),
valueSchema.numStaticFields()
);
}
if (valueSchema.numDynamicFields() != fieldLayout.numDynamicFields()) {
revert IStoreErrors.Store_InvalidValueSchemaDynamicLength(
fieldLayout.numDynamicFields(),
valueSchema.numDynamicFields()
);
}

// Verify that static field lengths are consistent between Schema and FieldLayout
for (uint256 i; i < fieldLayout.numStaticFields(); i++) {
if (fieldLayout.atIndex(i) != valueSchema.atIndex(i).getStaticByteLength()) {
revert IStoreErrors.Store_InvalidStaticDataLength(
fieldLayout.atIndex(i),
valueSchema.atIndex(i).getStaticByteLength()
);
}
}

// Verify there is no resource with this ID yet
if (ResourceIds._getExists(tableId)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/store/test/FieldLayout.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ contract FieldLayoutTest is Test, GasReporter {
}

function testInvalidFieldLayoutStaticTypeIsZero() public {
vm.expectRevert(FieldLayoutLib.FieldLayoutLib_StaticLengthIsZero.selector);
vm.expectRevert(abi.encodeWithSelector(FieldLayoutLib.FieldLayoutLib_StaticLengthIsZero.selector, 1));
FieldLayoutEncodeHelper.encode(1, 0, 1);
}

function testInvalidFieldLayoutStaticTypeDoesNotFitInAWord() public {
vm.expectRevert(FieldLayoutLib.FieldLayoutLib_StaticLengthDoesNotFitInAWord.selector);
vm.expectRevert(abi.encodeWithSelector(FieldLayoutLib.FieldLayoutLib_StaticLengthDoesNotFitInAWord.selector, 1));
FieldLayoutEncodeHelper.encode(1, 33, 1);
}

Expand Down
22 changes: 11 additions & 11 deletions packages/world-modules/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallComposite",
"name": "install keys in table module",
"gasUsed": 1419104
"gasUsed": 1435380
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallGas",
"name": "install keys in table module",
"gasUsed": 1419104
"gasUsed": 1435380
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -93,13 +93,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallSingleton",
"name": "install keys in table module",
"gasUsed": 1419104
"gasUsed": 1435380
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookCompositeGas",
"name": "install keys in table module",
"gasUsed": 1419104
"gasUsed": 1435380
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -117,7 +117,7 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookGas",
"name": "install keys in table module",
"gasUsed": 1419104
"gasUsed": 1435380
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -135,7 +135,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testGetKeysWithValueGas",
"name": "install keys with value module",
"gasUsed": 674321
"gasUsed": 681695
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -153,7 +153,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testInstall",
"name": "install keys with value module",
"gasUsed": 674321
"gasUsed": 681695
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -165,7 +165,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetAndDeleteRecordHook",
"name": "install keys with value module",
"gasUsed": 674321
"gasUsed": 681695
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -183,7 +183,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetField",
"name": "install keys with value module",
"gasUsed": 674321
"gasUsed": 681695
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand Down Expand Up @@ -303,7 +303,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstall",
"name": "install unique entity module",
"gasUsed": 694733
"gasUsed": 702723
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -315,7 +315,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstallRoot",
"name": "installRoot unique entity module",
"gasUsed": 663752
"gasUsed": 671743
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand Down
4 changes: 2 additions & 2 deletions packages/world/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"file": "test/Factories.t.sol",
"test": "testWorldFactory",
"name": "deploy world via WorldFactory",
"gasUsed": 12386832
"gasUsed": 12501056
},
{
"file": "test/World.t.sol",
Expand Down Expand Up @@ -129,7 +129,7 @@
"file": "test/World.t.sol",
"test": "testRegisterTable",
"name": "Register a new table in the namespace",
"gasUsed": 528266
"gasUsed": 537060
},
{
"file": "test/World.t.sol",
Expand Down
2 changes: 1 addition & 1 deletion packages/world/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default mudConfig({
},
excludeSystems: [
// Worldgen currently does not support systems inheriting logic
// from other contracts, so all parts of CoreSystem are named
// from other contracts, so all parts of CoreRegistrationSystem are named
// System too to be included in the IBaseWorld interface.
// However, IStoreRegistrationSystem overlaps with IStore if
// included in IBaseWorld, so it needs to be excluded from worldgen.
Expand Down

0 comments on commit ad4ac44

Please sign in to comment.