diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 7e1e3b9316..e6d78e4c3a 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -719,6 +719,12 @@ "name": "delete record (complex data, 3 slots)", "gasUsed": 8015 }, + { + "file": "test/StoreCoreGas.t.sol", + "test": "testDeleteDataOffchainTable", + "name": "StoreCore: delete record in offchain table", + "gasUsed": 4687 + }, { "file": "test/StoreCoreGas.t.sol", "test": "testGetStaticDataLocation", @@ -753,19 +759,19 @@ "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set record on table with subscriber", - "gasUsed": 72296 + "gasUsed": 72361 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set static field on table with subscriber", - "gasUsed": 19791 + "gasUsed": 19813 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "delete record on table with subscriber", - "gasUsed": 18617 + "gasUsed": 18595 }, { "file": "test/StoreCoreGas.t.sol", @@ -777,19 +783,19 @@ "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) record on table with subscriber", - "gasUsed": 165451 + "gasUsed": 165516 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) field on table with subscriber", - "gasUsed": 24469 + "gasUsed": 24424 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "delete (dynamic) record on table with subscriber", - "gasUsed": 20283 + "gasUsed": 20261 }, { "file": "test/StoreCoreGas.t.sol", @@ -941,6 +947,12 @@ "name": "get static record (2 slots)", "gasUsed": 1740 }, + { + "file": "test/StoreCoreGas.t.sol", + "test": "testSetDataOffchainTable", + "name": "StoreCore: set record in offchain table", + "gasUsed": 8082 + }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInDynamicField", diff --git a/packages/store/test/StoreCore.t.sol b/packages/store/test/StoreCore.t.sol index 7638fac612..15a3983363 100644 --- a/packages/store/test/StoreCore.t.sol +++ b/packages/store/test/StoreCore.t.sol @@ -17,7 +17,7 @@ import { StoreSwitch } from "../src/StoreSwitch.sol"; import { IStoreHook } from "../src/IStoreHook.sol"; import { Tables, ResourceIds, TablesTableId } from "../src/codegen/index.sol"; import { ResourceId, ResourceIdLib, ResourceIdInstance } from "../src/ResourceId.sol"; -import { RESOURCE_TABLE } from "../src/storeResourceTypes.sol"; +import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "../src/storeResourceTypes.sol"; import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol"; import { BEFORE_SET_RECORD, AFTER_SET_RECORD, BEFORE_SPLICE_STATIC_DATA, AFTER_SPLICE_STATIC_DATA, BEFORE_SPLICE_DYNAMIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD, AFTER_DELETE_RECORD, ALL, BEFORE_ALL, AFTER_ALL } from "../src/storeHookTypes.sol"; import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol"; @@ -44,6 +44,7 @@ contract StoreCoreTest is Test, StoreMock { string[] defaultKeyNames = new string[](1); ResourceId _tableId = ResourceIdLib.encode({ typeId: RESOURCE_TABLE, name: "some table" }); ResourceId _tableId2 = ResourceIdLib.encode({ typeId: RESOURCE_TABLE, name: "some other table" }); + ResourceId _tableId3 = ResourceIdLib.encode({ typeId: RESOURCE_OFFCHAIN_TABLE, name: "some offchain table" }); function testGetStaticDataLocation() public { ResourceId tableId = _tableId; @@ -1318,4 +1319,88 @@ contract StoreCoreTest is Test, StoreMock { assertEq(loadedData.encodedLengths.unwrap(), bytes32(0)); assertEq(loadedData.dynamicData, ""); } + + function testSetDataOffchainTable() public { + ResourceId tableId = _tableId3; + + // Register offchain table + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); + Schema valueSchema = SchemaEncodeHelper.encode( + SchemaType.UINT8, + SchemaType.UINT16, + SchemaType.UINT8, + SchemaType.UINT16 + ); + + IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); + + // Set data + bytes memory staticData = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04), bytes2(0x0506)); + + bytes32[] memory keyTuple = new bytes32[](1); + keyTuple[0] = "some key"; + + // Expect a Store_SetRecord event to be emitted + vm.expectEmit(true, true, true, true); + emit Store_SetRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0)); + + IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0)); + } + + function testDeleteDataOffchainTable() public { + ResourceId tableId = _tableId3; + + // Register table + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 2); + { + Schema valueSchema = SchemaEncodeHelper.encode( + SchemaType.UINT128, + SchemaType.UINT32_ARRAY, + SchemaType.UINT32_ARRAY + ); + IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](3)); + } + + bytes16 firstDataBytes = bytes16(0x0102030405060708090a0b0c0d0e0f10); + + bytes memory secondDataBytes; + { + uint32[] memory secondData = new uint32[](2); + secondData[0] = 0x11121314; + secondData[1] = 0x15161718; + secondDataBytes = EncodeArray.encode(secondData); + } + + bytes memory thirdDataBytes; + { + uint32[] memory thirdData = new uint32[](3); + thirdData[0] = 0x191a1b1c; + thirdData[1] = 0x1d1e1f20; + thirdData[2] = 0x21222324; + thirdDataBytes = EncodeArray.encode(thirdData); + } + + PackedCounter encodedDynamicLength; + { + encodedDynamicLength = PackedCounterLib.pack(uint40(secondDataBytes.length), uint40(thirdDataBytes.length)); + } + + // Concat data + bytes memory staticData = abi.encodePacked(firstDataBytes); + bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); + + // Create keyTuple + bytes32[] memory keyTuple = new bytes32[](1); + keyTuple[0] = bytes32("some key"); + + // Set data + IStore(this).setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData); + + // Expect a Store_DeleteRecord event to be emitted + vm.expectEmit(true, true, true, true); + emit Store_DeleteRecord(tableId, keyTuple); + + // Delete data + IStore(this).deleteRecord(tableId, keyTuple); + } } diff --git a/packages/store/test/StoreCoreGas.t.sol b/packages/store/test/StoreCoreGas.t.sol index 3b65a03fc2..67962c21b1 100644 --- a/packages/store/test/StoreCoreGas.t.sol +++ b/packages/store/test/StoreCoreGas.t.sol @@ -16,7 +16,7 @@ import { IStoreErrors } from "../src/IStoreErrors.sol"; import { IStore } from "../src/IStore.sol"; import { ResourceId, ResourceIdLib } from "../src/ResourceId.sol"; import { ResourceIds } from "../src/codegen/tables/ResourceIds.sol"; -import { RESOURCE_TABLE } from "../src/storeResourceTypes.sol"; +import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "../src/storeResourceTypes.sol"; import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol"; import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol"; import { StoreMock } from "./StoreMock.sol"; @@ -37,6 +37,7 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { Schema defaultKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32); ResourceId _tableId = ResourceIdLib.encode({ typeId: RESOURCE_TABLE, name: "some table" }); ResourceId _tableId2 = ResourceIdLib.encode({ typeId: RESOURCE_TABLE, name: "some other table" }); + ResourceId _tableId3 = ResourceIdLib.encode({ typeId: RESOURCE_OFFCHAIN_TABLE, name: "some offchain table" }); function testGetStaticDataLocation() public { ResourceId tableId = _tableId; @@ -724,4 +725,92 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { StoreCore.deleteRecord(tableId, keyTuple); endGasReport(); } + + function testSetDataOffchainTable() public { + ResourceId tableId = _tableId3; + + // Register offchain table + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); + Schema valueSchema = SchemaEncodeHelper.encode( + SchemaType.UINT8, + SchemaType.UINT16, + SchemaType.UINT8, + SchemaType.UINT16 + ); + + IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); + + // Set data + bytes memory staticData = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04), bytes2(0x0506)); + + bytes32[] memory keyTuple = new bytes32[](1); + keyTuple[0] = "some key"; + + // Expect a Store_SetRecord event to be emitted + vm.expectEmit(true, true, true, true); + emit Store_SetRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0)); + + startGasReport("StoreCore: set record in offchain table"); + IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0)); + endGasReport(); + } + + function testDeleteDataOffchainTable() public { + ResourceId tableId = _tableId3; + + // Register table + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 2); + { + Schema valueSchema = SchemaEncodeHelper.encode( + SchemaType.UINT128, + SchemaType.UINT32_ARRAY, + SchemaType.UINT32_ARRAY + ); + IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](3)); + } + + bytes16 firstDataBytes = bytes16(0x0102030405060708090a0b0c0d0e0f10); + + bytes memory secondDataBytes; + { + uint32[] memory secondData = new uint32[](2); + secondData[0] = 0x11121314; + secondData[1] = 0x15161718; + secondDataBytes = EncodeArray.encode(secondData); + } + + bytes memory thirdDataBytes; + { + uint32[] memory thirdData = new uint32[](3); + thirdData[0] = 0x191a1b1c; + thirdData[1] = 0x1d1e1f20; + thirdData[2] = 0x21222324; + thirdDataBytes = EncodeArray.encode(thirdData); + } + + PackedCounter encodedDynamicLength; + { + encodedDynamicLength = PackedCounterLib.pack(uint40(secondDataBytes.length), uint40(thirdDataBytes.length)); + } + + // Concat data + bytes memory staticData = abi.encodePacked(firstDataBytes); + bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); + + // Create keyTuple + bytes32[] memory keyTuple = new bytes32[](1); + keyTuple[0] = bytes32("some key"); + + // Set data + IStore(this).setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData); + + // Expect a Store_DeleteRecord event to be emitted + vm.expectEmit(true, true, true, true); + emit Store_DeleteRecord(tableId, keyTuple); + + // Delete data + startGasReport("StoreCore: delete record in offchain table"); + IStore(this).deleteRecord(tableId, keyTuple); + endGasReport(); + } }