diff --git a/.changeset/cyan-hats-try.md b/.changeset/cyan-hats-try.md new file mode 100644 index 0000000000..877a795bba --- /dev/null +++ b/.changeset/cyan-hats-try.md @@ -0,0 +1,30 @@ +--- +"@latticexyz/store": major +"@latticexyz/world": major +--- + +We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies. + +If you've written your own sync logic or are interacting with Store calls directly, this is a breaking change. We have a few more breaking protocol changes upcoming, so you may hold off on upgrading until those land. + +If you are using MUD's built-in tooling (table codegen, indexer, store sync, etc.), you don't have to make any changes except upgrading to the latest versions and deploying a fresh World. + +- The `data` field in each `StoreSetRecord` and `StoreEphemeralRecord` has been replaced with three new fields: `staticData`, `encodedLengths`, and `dynamicData`. This better reflects the on-chain state and makes it easier to perform modifications to the raw bytes. We recommend storing each of these fields individually in your off-chain storage of choice (indexer, client, etc.). + + ```diff + - event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data); + + event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData); + + - event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data); + + event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData); + ``` + +- The `StoreSetField` event is now replaced by two new events: `StoreSpliceStaticData` and `StoreSpliceDynamicData`. Splicing allows us to perform efficient operations like push and pop, in addition to replacing a field value. We use two events because updating a dynamic-length field also requires updating the record's `encodedLengths` (aka PackedCounter). + + ```diff + - event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data); + + event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data); + + event StoreSpliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data, bytes32 encodedLengths); + ``` + +Similarly, Store setter methods (e.g. `setRecord`) have been updated to reflect the `data` to `staticData`, `encodedLengths`, and `dynamicData` changes. We'll be following up shortly with Store getter method changes for more gas efficient storage reads. diff --git a/.changeset/fast-zebras-promise.md b/.changeset/fast-zebras-promise.md new file mode 100644 index 0000000000..f7d1cef47e --- /dev/null +++ b/.changeset/fast-zebras-promise.md @@ -0,0 +1,6 @@ +--- +"@latticexyz/common": minor +"@latticexyz/protocol-parser": major +--- + +`readHex` was moved from `@latticexyz/protocol-parser` to `@latticexyz/common` diff --git a/.changeset/rotten-cats-lay.md b/.changeset/rotten-cats-lay.md new file mode 100644 index 0000000000..021154c423 --- /dev/null +++ b/.changeset/rotten-cats-lay.md @@ -0,0 +1,9 @@ +--- +"@latticexyz/common": minor +--- + +`spliceHex` was added, which has a similar API as JavaScript's [`Array.prototype.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice), but for `Hex` strings. + +```ts +spliceHex("0x123456", 1, 1, "0x0000"); // "0x12000056" +``` diff --git a/.changeset/shy-sheep-wait.md b/.changeset/shy-sheep-wait.md new file mode 100644 index 0000000000..baabf2b946 --- /dev/null +++ b/.changeset/shy-sheep-wait.md @@ -0,0 +1,9 @@ +--- +"@latticexyz/dev-tools": major +"@latticexyz/store-sync": major +"create-mud": minor +--- + +We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies. + +As such, we've replaced `blockStorageOperations$` with `storedBlockLogs$`, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables. diff --git a/e2e/packages/client-vanilla/src/mud/setupNetwork.ts b/e2e/packages/client-vanilla/src/mud/setupNetwork.ts index ae79c26916..34cb6e7329 100644 --- a/e2e/packages/client-vanilla/src/mud/setupNetwork.ts +++ b/e2e/packages/client-vanilla/src/mud/setupNetwork.ts @@ -33,7 +33,7 @@ export async function setupNetwork() { walletClient: burnerWalletClient, }); - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -74,7 +74,7 @@ export async function setupNetwork() { walletClient: burnerWalletClient, worldContract, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, }; } diff --git a/e2e/packages/contracts/src/codegen/tables/Multi.sol b/e2e/packages/contracts/src/codegen/tables/Multi.sol index a634403932..a66e9b0d72 100644 --- a/e2e/packages/contracts/src/codegen/tables/Multi.sol +++ b/e2e/packages/contracts/src/codegen/tables/Multi.sol @@ -207,7 +207,10 @@ library Multi { /** Set the full data using individual values */ function set(uint32 a, bool b, uint256 c, int120 d, int256 num, bool value) internal { - bytes memory _data = encode(num, value); + bytes memory _staticData = encodeStatic(num, value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](4); _keyTuple[0] = bytes32(uint256(a)); @@ -215,12 +218,15 @@ library Multi { _keyTuple[2] = bytes32(uint256(c)); _keyTuple[3] = bytes32(uint256(int256(d))); - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, uint32 a, bool b, uint256 c, int120 d, int256 num, bool value) internal { - bytes memory _data = encode(num, value); + bytes memory _staticData = encodeStatic(num, value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](4); _keyTuple[0] = bytes32(uint256(a)); @@ -228,7 +234,7 @@ library Multi { _keyTuple[2] = bytes32(uint256(c)); _keyTuple[3] = bytes32(uint256(int256(d))); - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -248,9 +254,19 @@ library Multi { _table.value = (_toBool(uint8(Bytes.slice1(_blob, 32)))); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(int256 num, bool value) internal pure returns (bytes memory) { + return abi.encodePacked(num, value); + } + /** Tightly pack full data using this table's field layout */ function encode(int256 num, bool value) internal pure returns (bytes memory) { - return abi.encodePacked(num, value); + bytes memory _staticData = encodeStatic(num, value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/src/codegen/tables/Number.sol b/e2e/packages/contracts/src/codegen/tables/Number.sol index 01127f809c..68d463bacd 100644 --- a/e2e/packages/contracts/src/codegen/tables/Number.sol +++ b/e2e/packages/contracts/src/codegen/tables/Number.sol @@ -109,9 +109,19 @@ library Number { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/src/codegen/tables/NumberList.sol b/e2e/packages/contracts/src/codegen/tables/NumberList.sol index 60d34cc507..76ab24b8e5 100644 --- a/e2e/packages/contracts/src/codegen/tables/NumberList.sol +++ b/e2e/packages/contracts/src/codegen/tables/NumberList.sol @@ -207,15 +207,26 @@ library NumberList { } } - /** Tightly pack full data using this table's field layout */ - function encode(uint32[] memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(uint32[] memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(value.length * 4); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(uint32[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(uint32[] memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/src/codegen/tables/Vector.sol b/e2e/packages/contracts/src/codegen/tables/Vector.sol index e258fac50c..c64c1d9086 100644 --- a/e2e/packages/contracts/src/codegen/tables/Vector.sol +++ b/e2e/packages/contracts/src/codegen/tables/Vector.sol @@ -171,22 +171,28 @@ library Vector { /** Set the full data using individual values */ function set(uint32 key, int32 x, int32 y) internal { - bytes memory _data = encode(x, y); + bytes memory _staticData = encodeStatic(x, y); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(uint256(key)); - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, uint32 key, int32 x, int32 y) internal { - bytes memory _data = encode(x, y); + bytes memory _staticData = encodeStatic(x, y); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(uint256(key)); - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -206,9 +212,19 @@ library Vector { _table.y = (int32(uint32(Bytes.slice4(_blob, 4)))); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(int32 x, int32 y) internal pure returns (bytes memory) { + return abi.encodePacked(x, y); + } + /** Tightly pack full data using this table's field layout */ function encode(int32 x, int32 y) internal pure returns (bytes memory) { - return abi.encodePacked(x, y); + bytes memory _staticData = encodeStatic(x, y); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/worlds.json b/e2e/packages/contracts/worlds.json index bd48105fd7..a9623cfa62 100644 --- a/e2e/packages/contracts/worlds.json +++ b/e2e/packages/contracts/worlds.json @@ -1,5 +1,5 @@ { "31337": { - "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3" + "address": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" } } \ No newline at end of file diff --git a/e2e/packages/sync-test/data/encodeTestData.test.ts b/e2e/packages/sync-test/data/encodeTestData.test.ts index df67bb56e2..11c919ac0e 100644 --- a/e2e/packages/sync-test/data/encodeTestData.test.ts +++ b/e2e/packages/sync-test/data/encodeTestData.test.ts @@ -3,24 +3,36 @@ import { encodeTestData } from "./encodeTestData"; describe("encodeTestData", () => { it("should encode numbers", () => { - expect(encodeTestData({ Number: [{ key: { key: 42 }, value: { value: 1337 } }] })).toStrictEqual({ - Number: [ - { - key: ["0x000000000000000000000000000000000000000000000000000000000000002a"], - value: "0x00000539", - valueSchema: "0x0004010003000000000000000000000000000000000000000000000000000000", - }, - ], - }); + expect(encodeTestData({ Number: [{ key: { key: 42 }, value: { value: 1337 } }] })).toMatchInlineSnapshot(` + { + "Number": [ + { + "dynamicData": "0x", + "encodedLengths": "0x0000000000000000000000000000000000000000000000000000000000000000", + "fieldLayout": "0x0004010004000000000000000000000000000000000000000000000000000000", + "key": [ + "0x000000000000000000000000000000000000000000000000000000000000002a", + ], + "staticData": "0x00000539", + }, + ], + } + `); - expect(encodeTestData({ Vector: [{ key: { key: 1337 }, value: { x: 42, y: -69 } }] })).toStrictEqual({ - Vector: [ - { - key: ["0x0000000000000000000000000000000000000000000000000000000000000539"], - value: "0x0000002affffffbb", - valueSchema: "0x0008020023230000000000000000000000000000000000000000000000000000", - }, - ], - }); + expect(encodeTestData({ Vector: [{ key: { key: 1337 }, value: { x: 42, y: -69 } }] })).toMatchInlineSnapshot(` + { + "Vector": [ + { + "dynamicData": "0x", + "encodedLengths": "0x0000000000000000000000000000000000000000000000000000000000000000", + "fieldLayout": "0x0008020004040000000000000000000000000000000000000000000000000000", + "key": [ + "0x0000000000000000000000000000000000000000000000000000000000000539", + ], + "staticData": "0x0000002affffffbb", + }, + ], + } + `); }); }); diff --git a/e2e/packages/sync-test/data/encodeTestData.ts b/e2e/packages/sync-test/data/encodeTestData.ts index a433626d61..891d770dc4 100644 --- a/e2e/packages/sync-test/data/encodeTestData.ts +++ b/e2e/packages/sync-test/data/encodeTestData.ts @@ -1,9 +1,7 @@ import { mapObject } from "@latticexyz/utils"; +import { encodeKey, encodeValueArgs, valueSchemaToFieldLayoutHex } from "@latticexyz/protocol-parser"; import { Data, EncodedData } from "./types"; -import { encodeAbiParameters, encodePacked } from "viem"; import config from "../../contracts/mud.config"; -import { abiTypesToSchema, schemaToHex } from "@latticexyz/protocol-parser"; -import { StaticAbiType } from "@latticexyz/schema-type"; /** * Turns the typed data into encoded data in the format expected by `world.setRecord` @@ -11,21 +9,16 @@ import { StaticAbiType } from "@latticexyz/schema-type"; export function encodeTestData(testData: Data) { return mapObject(testData, (records, table) => { if (!records) return undefined; - const keyConfig = config.tables[table].keySchema; + const tableConfig = config.tables[table]; return records.map((record) => { - const encodedKey = Object.entries(record.key).map(([keyName, keyValue]) => { - const keyType = keyConfig[keyName as keyof typeof keyConfig] as StaticAbiType; - return encodeAbiParameters([{ type: keyType }], [keyValue]); - }); - - const encodedValue = encodePacked(Object.values(config.tables[table].valueSchema), Object.values(record.value)); - - const encodedValueSchema = schemaToHex(abiTypesToSchema(Object.values(config.tables[table].valueSchema))); + const key = encodeKey(tableConfig.keySchema, record.key); + const valueArgs = encodeValueArgs(tableConfig.valueSchema, record.value); + const fieldLayout = valueSchemaToFieldLayoutHex(tableConfig.valueSchema); return { - key: encodedKey, - value: encodedValue, - valueSchema: encodedValueSchema, + key, + ...valueArgs, + fieldLayout, }; }); }) as EncodedData; diff --git a/e2e/packages/sync-test/data/expectClientData.ts b/e2e/packages/sync-test/data/expectClientData.ts index 6e52c0fc3d..92e81c323f 100644 --- a/e2e/packages/sync-test/data/expectClientData.ts +++ b/e2e/packages/sync-test/data/expectClientData.ts @@ -11,7 +11,7 @@ export async function expectClientData(page: Page, data: Data) { for (const [table, records] of Object.entries(data)) { for (const record of records) { const value = await readComponentValue(page, table, encodeEntity(config.tables[table].keySchema, record.key)); - expect(value).toEqual(record.value); + expect(value).toMatchObject(record.value); } } } diff --git a/e2e/packages/sync-test/data/setContractData.ts b/e2e/packages/sync-test/data/setContractData.ts index 5ca090cae8..323189d95c 100644 --- a/e2e/packages/sync-test/data/setContractData.ts +++ b/e2e/packages/sync-test/data/setContractData.ts @@ -16,8 +16,10 @@ export async function setContractData(page: Page, data: Data) { // TODO: add support for multiple namespaces after https://github.com/latticexyz/mud/issues/994 is resolved tableIdToHex("", table), record.key, - record.value, - record.valueSchema, + record.staticData, + record.encodedLengths, + record.dynamicData, + record.fieldLayout, ]); // Wait for transactions to be confirmed diff --git a/e2e/packages/sync-test/data/types.ts b/e2e/packages/sync-test/data/types.ts index 85abd2c90d..b79ac3f3b6 100644 --- a/e2e/packages/sync-test/data/types.ts +++ b/e2e/packages/sync-test/data/types.ts @@ -20,5 +20,5 @@ export type Datum> }; export type EncodedData = { - [Table in keyof T]: Array<{ key: Hex[]; value: Hex; valueSchema: Hex }>; + [Table in keyof T]: Array<{ key: Hex[]; staticData: Hex; encodedLengths: Hex; dynamicData: Hex; fieldLayout: Hex }>; }; diff --git a/e2e/packages/sync-test/rpcSync.test.ts b/e2e/packages/sync-test/rpcSync.test.ts index 7726f86995..39e786a319 100644 --- a/e2e/packages/sync-test/rpcSync.test.ts +++ b/e2e/packages/sync-test/rpcSync.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, test } from "vitest"; import type { ViteDevServer } from "vite"; import { Browser, Page } from "@playwright/test"; -import { ExecaChildProcess } from "execa"; import { createAsyncErrorHandler } from "./asyncErrors"; import { deployContracts, startViteServer, startBrowserAndPage, openClientWithRootAccount } from "./setup"; import { diff --git a/e2e/packages/sync-test/setup/startBrowserAndPage.ts b/e2e/packages/sync-test/setup/startBrowserAndPage.ts index f5ab642f9b..0f18151879 100644 --- a/e2e/packages/sync-test/setup/startBrowserAndPage.ts +++ b/e2e/packages/sync-test/setup/startBrowserAndPage.ts @@ -14,7 +14,7 @@ export async function startBrowserAndPage( // log uncaught errors in the browser page (browser and test consoles are separate) page.on("pageerror", (err) => { - console.log(chalk.yellow("[browser page error]:"), err.message); + console.log(chalk.yellow("[browser page error]:"), err.message, err); }); // log browser's console logs diff --git a/e2e/packages/sync-test/vite.config.ts b/e2e/packages/sync-test/vite.config.ts index aee36eb400..c6202f1992 100644 --- a/e2e/packages/sync-test/vite.config.ts +++ b/e2e/packages/sync-test/vite.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from "vitest/config"; -// TODO should this along with `.test.ts` be in `client-vanilla`? export default defineConfig({ test: { environment: "jsdom", diff --git a/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts b/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts index 85bac97cd8..144ecd4a33 100644 --- a/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts +++ b/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts @@ -36,7 +36,7 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -75,7 +75,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/examples/minimal/packages/client-phaser/src/ui/App.tsx b/examples/minimal/packages/client-phaser/src/ui/App.tsx index 314e5572d4..69529815b5 100644 --- a/examples/minimal/packages/client-phaser/src/ui/App.tsx +++ b/examples/minimal/packages/client-phaser/src/ui/App.tsx @@ -21,7 +21,7 @@ export const App = () => { publicClient: networkLayer.network.publicClient, walletClient: networkLayer.network.walletClient, latestBlock$: networkLayer.network.latestBlock$, - blockStorageOperations$: networkLayer.network.blockStorageOperations$, + storedBlockLogs$: networkLayer.network.storedBlockLogs$, worldAddress: networkLayer.network.worldContract.address, worldAbi: networkLayer.network.worldContract.abi, write$: networkLayer.network.write$, diff --git a/examples/minimal/packages/client-react/src/index.tsx b/examples/minimal/packages/client-react/src/index.tsx index 9b3f9121e1..da8d70f020 100644 --- a/examples/minimal/packages/client-react/src/index.tsx +++ b/examples/minimal/packages/client-react/src/index.tsx @@ -24,7 +24,7 @@ setup().then(async (result) => { publicClient: result.network.publicClient, walletClient: result.network.walletClient, latestBlock$: result.network.latestBlock$, - blockStorageOperations$: result.network.blockStorageOperations$, + storedBlockLogs$: result.network.storedBlockLogs$, worldAddress: result.network.worldContract.address, worldAbi: result.network.worldContract.abi, write$: result.network.write$, diff --git a/examples/minimal/packages/client-react/src/mud/setupNetwork.ts b/examples/minimal/packages/client-react/src/mud/setupNetwork.ts index 9c42a54e14..d11cd48e24 100644 --- a/examples/minimal/packages/client-react/src/mud/setupNetwork.ts +++ b/examples/minimal/packages/client-react/src/mud/setupNetwork.ts @@ -36,7 +36,7 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -75,7 +75,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/examples/minimal/packages/client-vanilla/src/index.ts b/examples/minimal/packages/client-vanilla/src/index.ts index fe63aa1950..8666b71a68 100644 --- a/examples/minimal/packages/client-vanilla/src/index.ts +++ b/examples/minimal/packages/client-vanilla/src/index.ts @@ -62,7 +62,7 @@ if (import.meta.env.DEV) { publicClient: network.publicClient, walletClient: network.walletClient, latestBlock$: network.latestBlock$, - blockStorageOperations$: network.blockStorageOperations$, + storedBlockLogs$: network.storedBlockLogs$, worldAddress: network.worldContract.address, worldAbi: network.worldContract.abi, write$: network.write$, diff --git a/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts b/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts index 85bac97cd8..144ecd4a33 100644 --- a/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts +++ b/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts @@ -36,7 +36,7 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -75,7 +75,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol b/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol index ed4f112789..0de74a8c72 100644 --- a/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol +++ b/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol @@ -103,9 +103,19 @@ library CounterTable { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol b/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol index 96df3c93dd..15c35b4678 100644 --- a/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol +++ b/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol @@ -121,9 +121,19 @@ library Inventory { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((amount)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 amount) internal pure returns (bytes memory) { + return abi.encodePacked(amount); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 amount) internal pure returns (bytes memory) { - return abi.encodePacked(amount); + bytes memory _staticData = encodeStatic(amount); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol b/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol index 5fe5d13250..142691da0a 100644 --- a/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol +++ b/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol @@ -74,31 +74,46 @@ library MessageTable { /** Emit the ephemeral event using individual values */ function emitEphemeral(string memory value) internal { - bytes memory _data = encode(value); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); bytes32[] memory _keyTuple = new bytes32[](0); - StoreSwitch.emitEphemeralRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.emitEphemeralRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Emit the ephemeral event using individual values (using the specified store) */ function emitEphemeral(IStore _store, string memory value) internal { - bytes memory _data = encode(value); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); bytes32[] memory _keyTuple = new bytes32[](0); - _store.emitEphemeralRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.emitEphemeralRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } - /** Tightly pack full data using this table's field layout */ - function encode(string memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(string memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(bytes(value).length); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(string memory value) internal pure returns (bytes memory) { + return abi.encodePacked(bytes((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(string memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), bytes((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol b/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol index ac62fff28f..0bd179745f 100644 --- a/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol +++ b/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol @@ -13,8 +13,15 @@ import { IChatNamespacedSystem } from "../src/interfaces/IChatNamespacedSystem.s contract ChatNamespacedTest is MudTest { function testEmitEphemeral() public { bytes32[] memory keyTuple; + string memory value = "test"; vm.expectEmit(true, true, true, true); - emit StoreCore.StoreEphemeralRecord(MessageTableTableId, keyTuple, MessageTable.encode("test")); - IChatNamespacedSystem(worldAddress).namespace_ChatNamespaced_sendMessage("test"); + emit StoreCore.StoreEphemeralRecord( + MessageTableTableId, + keyTuple, + new bytes(0), + MessageTable.encodeLengths(value).unwrap(), + MessageTable.encodeDynamic(value) + ); + IChatNamespacedSystem(worldAddress).namespace_ChatNamespaced_sendMessage(value); } } diff --git a/packages/block-logs-stream/src/groupLogsByBlockNumber.ts b/packages/block-logs-stream/src/groupLogsByBlockNumber.ts index e0d2f1144e..b0656acfbc 100644 --- a/packages/block-logs-stream/src/groupLogsByBlockNumber.ts +++ b/packages/block-logs-stream/src/groupLogsByBlockNumber.ts @@ -4,7 +4,7 @@ import { bigIntSort, isDefined } from "@latticexyz/common/utils"; type PartialLog = { blockNumber: bigint; logIndex: number }; export type GroupLogsByBlockNumberResult = { - blockNumber: BlockNumber; + blockNumber: TLog["blockNumber"]; logs: TLog[]; }[]; diff --git a/packages/cli/contracts/src/codegen/tables/Dynamics1.sol b/packages/cli/contracts/src/codegen/tables/Dynamics1.sol index 368c3b73f4..f32451cf30 100644 --- a/packages/cli/contracts/src/codegen/tables/Dynamics1.sol +++ b/packages/cli/contracts/src/codegen/tables/Dynamics1.sol @@ -925,12 +925,14 @@ library Dynamics1 { address[4] memory staticAddrs, bool[5] memory staticBools ) internal { - bytes memory _data = encode(staticB32, staticI32, staticU128, staticAddrs, staticBools); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(staticB32, staticI32, staticU128, staticAddrs, staticBools); + bytes memory _dynamicData = encodeDynamic(staticB32, staticI32, staticU128, staticAddrs, staticBools); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ @@ -943,12 +945,14 @@ library Dynamics1 { address[4] memory staticAddrs, bool[5] memory staticBools ) internal { - bytes memory _data = encode(staticB32, staticI32, staticU128, staticAddrs, staticBools); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(staticB32, staticI32, staticU128, staticAddrs, staticBools); + bytes memory _dynamicData = encodeDynamic(staticB32, staticI32, staticU128, staticAddrs, staticBools); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -1005,15 +1009,14 @@ library Dynamics1 { } } - /** Tightly pack full data using this table's field layout */ - function encode( + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths( bytes32[1] memory staticB32, int32[2] memory staticI32, uint128[3] memory staticU128, address[4] memory staticAddrs, bool[5] memory staticBools - ) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + ) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack( @@ -1024,10 +1027,18 @@ library Dynamics1 { staticBools.length * 1 ); } + } + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic( + bytes32[1] memory staticB32, + int32[2] memory staticI32, + uint128[3] memory staticU128, + address[4] memory staticAddrs, + bool[5] memory staticBools + ) internal pure returns (bytes memory) { return abi.encodePacked( - _encodedLengths.unwrap(), EncodeArray.encode(fromStaticArray_bytes32_1(staticB32)), EncodeArray.encode(fromStaticArray_int32_2(staticI32)), EncodeArray.encode(fromStaticArray_uint128_3(staticU128)), @@ -1036,6 +1047,21 @@ library Dynamics1 { ); } + /** Tightly pack full data using this table's field layout */ + function encode( + bytes32[1] memory staticB32, + int32[2] memory staticI32, + uint128[3] memory staticU128, + address[4] memory staticAddrs, + bool[5] memory staticBools + ) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(staticB32, staticI32, staticU128, staticAddrs, staticBools); + bytes memory _dynamicData = encodeDynamic(staticB32, staticI32, staticU128, staticAddrs, staticBools); + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + } + /** Encode keys as a bytes32 array using this table's field layout */ function encodeKeyTuple(bytes32 key) internal pure returns (bytes32[] memory) { bytes32[] memory _keyTuple = new bytes32[](1); diff --git a/packages/cli/contracts/src/codegen/tables/Dynamics2.sol b/packages/cli/contracts/src/codegen/tables/Dynamics2.sol index 3bcbe30cd3..9ced950df1 100644 --- a/packages/cli/contracts/src/codegen/tables/Dynamics2.sol +++ b/packages/cli/contracts/src/codegen/tables/Dynamics2.sol @@ -551,22 +551,26 @@ library Dynamics2 { /** Set the full data using individual values */ function set(bytes32 key, uint64[] memory u64, string memory str, bytes memory b) internal { - bytes memory _data = encode(u64, str, b); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(u64, str, b); + bytes memory _dynamicData = encodeDynamic(u64, str, b); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, bytes32 key, uint64[] memory u64, string memory str, bytes memory b) internal { - bytes memory _data = encode(u64, str, b); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(u64, str, b); + bytes memory _dynamicData = encodeDynamic(u64, str, b); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -611,15 +615,30 @@ library Dynamics2 { } } - /** Tightly pack full data using this table's field layout */ - function encode(uint64[] memory u64, string memory str, bytes memory b) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths( + uint64[] memory u64, + string memory str, + bytes memory b + ) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(u64.length * 8, bytes(str).length, bytes(b).length); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(uint64[] memory u64, string memory str, bytes memory b) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((u64)), bytes((str)), bytes((b))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(uint64[] memory u64, string memory str, bytes memory b) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(u64, str, b); + bytes memory _dynamicData = encodeDynamic(u64, str, b); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((u64)), bytes((str)), bytes((b))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Ephemeral.sol b/packages/cli/contracts/src/codegen/tables/Ephemeral.sol index 0f9cec20cc..0742a42eb8 100644 --- a/packages/cli/contracts/src/codegen/tables/Ephemeral.sol +++ b/packages/cli/contracts/src/codegen/tables/Ephemeral.sol @@ -77,27 +77,43 @@ library Ephemeral { /** Emit the ephemeral event using individual values */ function emitEphemeral(bytes32 key, uint256 value) internal { - bytes memory _data = encode(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - StoreSwitch.emitEphemeralRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.emitEphemeralRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Emit the ephemeral event using individual values (using the specified store) */ function emitEphemeral(IStore _store, bytes32 key, uint256 value) internal { - bytes memory _data = encode(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - _store.emitEphemeralRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.emitEphemeralRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); + } + + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint256 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); } /** Tightly pack full data using this table's field layout */ function encode(uint256 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Singleton.sol b/packages/cli/contracts/src/codegen/tables/Singleton.sol index c309eedd60..29ef27a1d8 100644 --- a/packages/cli/contracts/src/codegen/tables/Singleton.sol +++ b/packages/cli/contracts/src/codegen/tables/Singleton.sol @@ -534,20 +534,26 @@ library Singleton { /** Set the full data using individual values */ function set(int256 v1, uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) internal { - bytes memory _data = encode(v1, v2, v3, v4); + bytes memory _staticData = encodeStatic(v1); + + PackedCounter _encodedLengths = encodeLengths(v2, v3, v4); + bytes memory _dynamicData = encodeDynamic(v2, v3, v4); bytes32[] memory _keyTuple = new bytes32[](0); - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, int256 v1, uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) internal { - bytes memory _data = encode(v1, v2, v3, v4); + bytes memory _staticData = encodeStatic(v1); + + PackedCounter _encodedLengths = encodeLengths(v2, v3, v4); + bytes memory _dynamicData = encodeDynamic(v2, v3, v4); bytes32[] memory _keyTuple = new bytes32[](0); - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** @@ -586,29 +592,52 @@ library Singleton { } } - /** Tightly pack full data using this table's field layout */ - function encode( - int256 v1, + /** Tightly pack static data using this table's schema */ + function encodeStatic(int256 v1) internal pure returns (bytes memory) { + return abi.encodePacked(v1); + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths( uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4 - ) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + ) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(v2.length * 4, v3.length * 4, v4.length * 4); } + } + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic( + uint32[2] memory v2, + uint32[2] memory v3, + uint32[1] memory v4 + ) internal pure returns (bytes memory) { return abi.encodePacked( - v1, - _encodedLengths.unwrap(), EncodeArray.encode(fromStaticArray_uint32_2(v2)), EncodeArray.encode(fromStaticArray_uint32_2(v3)), EncodeArray.encode(fromStaticArray_uint32_1(v4)) ); } + /** Tightly pack full data using this table's field layout */ + function encode( + int256 v1, + uint32[2] memory v2, + uint32[2] memory v3, + uint32[1] memory v4 + ) internal pure returns (bytes memory) { + bytes memory _staticData = encodeStatic(v1); + + PackedCounter _encodedLengths = encodeLengths(v2, v3, v4); + bytes memory _dynamicData = encodeDynamic(v2, v3, v4); + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + } + /** Encode keys as a bytes32 array using this table's field layout */ function encodeKeyTuple() internal pure returns (bytes32[] memory) { bytes32[] memory _keyTuple = new bytes32[](0); diff --git a/packages/cli/contracts/src/codegen/tables/Statics.sol b/packages/cli/contracts/src/codegen/tables/Statics.sol index 089552188c..9d6fcab330 100644 --- a/packages/cli/contracts/src/codegen/tables/Statics.sol +++ b/packages/cli/contracts/src/codegen/tables/Statics.sol @@ -19,7 +19,7 @@ import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; import { PackedCounter, PackedCounterLib } from "@latticexyz/store/src/PackedCounter.sol"; // Import user types -import { Enum1, Enum2 } from "./../Types.sol"; +import { Enum2, Enum1 } from "./../Types.sol"; bytes32 constant _tableId = bytes32(abi.encodePacked(bytes16(""), bytes16("Statics"))); bytes32 constant StaticsTableId = _tableId; @@ -31,74 +31,68 @@ struct StaticsData { address v4; bool v5; Enum1 v6; - Enum2 v7; } library Statics { /** Get the table values' field layout */ function getFieldLayout() internal pure returns (FieldLayout) { - uint256[] memory _fieldLayout = new uint256[](7); + uint256[] memory _fieldLayout = new uint256[](6); _fieldLayout[0] = 32; _fieldLayout[1] = 4; _fieldLayout[2] = 16; _fieldLayout[3] = 20; _fieldLayout[4] = 1; _fieldLayout[5] = 1; - _fieldLayout[6] = 1; return FieldLayoutLib.encode(_fieldLayout, 0); } /** Get the table's key schema */ function getKeySchema() internal pure returns (Schema) { - SchemaType[] memory _keySchema = new SchemaType[](7); + SchemaType[] memory _keySchema = new SchemaType[](6); _keySchema[0] = SchemaType.UINT256; _keySchema[1] = SchemaType.INT32; _keySchema[2] = SchemaType.BYTES16; _keySchema[3] = SchemaType.ADDRESS; _keySchema[4] = SchemaType.BOOL; _keySchema[5] = SchemaType.UINT8; - _keySchema[6] = SchemaType.UINT8; return SchemaLib.encode(_keySchema); } /** Get the table's value schema */ function getValueSchema() internal pure returns (Schema) { - SchemaType[] memory _valueSchema = new SchemaType[](7); + SchemaType[] memory _valueSchema = new SchemaType[](6); _valueSchema[0] = SchemaType.UINT256; _valueSchema[1] = SchemaType.INT32; _valueSchema[2] = SchemaType.BYTES16; _valueSchema[3] = SchemaType.ADDRESS; _valueSchema[4] = SchemaType.BOOL; _valueSchema[5] = SchemaType.UINT8; - _valueSchema[6] = SchemaType.UINT8; return SchemaLib.encode(_valueSchema); } /** Get the table's key names */ function getKeyNames() internal pure returns (string[] memory keyNames) { - keyNames = new string[](7); + keyNames = new string[](6); keyNames[0] = "k1"; keyNames[1] = "k2"; keyNames[2] = "k3"; keyNames[3] = "k4"; keyNames[4] = "k5"; keyNames[5] = "k6"; - keyNames[6] = "k7"; } /** Get the table's field names */ function getFieldNames() internal pure returns (string[] memory fieldNames) { - fieldNames = new string[](7); + fieldNames = new string[](6); fieldNames[0] = "v1"; fieldNames[1] = "v2"; fieldNames[2] = "v3"; fieldNames[3] = "v4"; fieldNames[4] = "v5"; fieldNames[5] = "v6"; - fieldNames[6] = "v7"; } /** Register the table with its config */ @@ -119,23 +113,14 @@ library Statics { } /** Get v1 */ - function getV1( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (uint256 v1) { - bytes32[] memory _keyTuple = new bytes32[](7); + function getV1(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal view returns (uint256 v1) { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 0, getFieldLayout()); return (uint256(Bytes.slice32(_blob, 0))); @@ -149,78 +134,55 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (uint256 v1) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 0, getFieldLayout()); return (uint256(Bytes.slice32(_blob, 0))); } /** Set v1 */ - function setV1(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, uint256 v1) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV1(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, uint256 v1) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked((v1)), getFieldLayout()); } /** Set v1 (using the specified store) */ - function setV1( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - uint256 v1 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV1(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, uint256 v1) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((v1)), getFieldLayout()); } /** Get v2 */ - function getV2( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (int32 v2) { - bytes32[] memory _keyTuple = new bytes32[](7); + function getV2(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal view returns (int32 v2) { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 1, getFieldLayout()); return (int32(uint32(Bytes.slice4(_blob, 0)))); @@ -234,78 +196,55 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (int32 v2) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 1, getFieldLayout()); return (int32(uint32(Bytes.slice4(_blob, 0)))); } /** Set v2 */ - function setV2(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, int32 v2) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV2(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, int32 v2) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.setField(_tableId, _keyTuple, 1, abi.encodePacked((v2)), getFieldLayout()); } /** Set v2 (using the specified store) */ - function setV2( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - int32 v2 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV2(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, int32 v2) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.setField(_tableId, _keyTuple, 1, abi.encodePacked((v2)), getFieldLayout()); } /** Get v3 */ - function getV3( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (bytes16 v3) { - bytes32[] memory _keyTuple = new bytes32[](7); + function getV3(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal view returns (bytes16 v3) { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 2, getFieldLayout()); return (Bytes.slice16(_blob, 0)); @@ -319,78 +258,55 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (bytes16 v3) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 2, getFieldLayout()); return (Bytes.slice16(_blob, 0)); } /** Set v3 */ - function setV3(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bytes16 v3) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV3(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, bytes16 v3) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.setField(_tableId, _keyTuple, 2, abi.encodePacked((v3)), getFieldLayout()); } /** Set v3 (using the specified store) */ - function setV3( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - bytes16 v3 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV3(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, bytes16 v3) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.setField(_tableId, _keyTuple, 2, abi.encodePacked((v3)), getFieldLayout()); } /** Get v4 */ - function getV4( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (address v4) { - bytes32[] memory _keyTuple = new bytes32[](7); + function getV4(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal view returns (address v4) { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 3, getFieldLayout()); return (address(Bytes.slice20(_blob, 0))); @@ -404,78 +320,55 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (address v4) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 3, getFieldLayout()); return (address(Bytes.slice20(_blob, 0))); } /** Set v4 */ - function setV4(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, address v4) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV4(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, address v4) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.setField(_tableId, _keyTuple, 3, abi.encodePacked((v4)), getFieldLayout()); } /** Set v4 (using the specified store) */ - function setV4( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - address v4 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV4(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, address v4) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.setField(_tableId, _keyTuple, 3, abi.encodePacked((v4)), getFieldLayout()); } /** Get v5 */ - function getV5( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (bool v5) { - bytes32[] memory _keyTuple = new bytes32[](7); + function getV5(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal view returns (bool v5) { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 4, getFieldLayout()); return (_toBool(uint8(Bytes.slice1(_blob, 0)))); @@ -489,78 +382,55 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (bool v5) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 4, getFieldLayout()); return (_toBool(uint8(Bytes.slice1(_blob, 0)))); } /** Set v5 */ - function setV5(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bool v5) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV5(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, bool v5) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.setField(_tableId, _keyTuple, 4, abi.encodePacked((v5)), getFieldLayout()); } /** Set v5 (using the specified store) */ - function setV5( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - bool v5 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV5(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, bool v5) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.setField(_tableId, _keyTuple, 4, abi.encodePacked((v5)), getFieldLayout()); } /** Get v6 */ - function getV6( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (Enum1 v6) { - bytes32[] memory _keyTuple = new bytes32[](7); + function getV6(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal view returns (Enum1 v6) { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 5, getFieldLayout()); return Enum1(uint8(Bytes.slice1(_blob, 0))); @@ -574,145 +444,46 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (Enum1 v6) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 5, getFieldLayout()); return Enum1(uint8(Bytes.slice1(_blob, 0))); } /** Set v6 */ - function setV6(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum1 v6) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV6(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, Enum1 v6) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.setField(_tableId, _keyTuple, 5, abi.encodePacked(uint8(v6)), getFieldLayout()); } /** Set v6 (using the specified store) */ - function setV6( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - Enum1 v6 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function setV6(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, Enum1 v6) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.setField(_tableId, _keyTuple, 5, abi.encodePacked(uint8(v6)), getFieldLayout()); } - /** Get v7 */ - function getV7( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (Enum2 v7) { - bytes32[] memory _keyTuple = new bytes32[](7); - _keyTuple[0] = bytes32(uint256(k1)); - _keyTuple[1] = bytes32(uint256(int256(k2))); - _keyTuple[2] = bytes32(k3); - _keyTuple[3] = bytes32(uint256(uint160(k4))); - _keyTuple[4] = _boolToBytes32(k5); - _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); - - bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 6, getFieldLayout()); - return Enum2(uint8(Bytes.slice1(_blob, 0))); - } - - /** Get v7 (using the specified store) */ - function getV7( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal view returns (Enum2 v7) { - bytes32[] memory _keyTuple = new bytes32[](7); - _keyTuple[0] = bytes32(uint256(k1)); - _keyTuple[1] = bytes32(uint256(int256(k2))); - _keyTuple[2] = bytes32(k3); - _keyTuple[3] = bytes32(uint256(uint160(k4))); - _keyTuple[4] = _boolToBytes32(k5); - _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); - - bytes memory _blob = _store.getField(_tableId, _keyTuple, 6, getFieldLayout()); - return Enum2(uint8(Bytes.slice1(_blob, 0))); - } - - /** Set v7 */ - function setV7(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum2 v7) internal { - bytes32[] memory _keyTuple = new bytes32[](7); - _keyTuple[0] = bytes32(uint256(k1)); - _keyTuple[1] = bytes32(uint256(int256(k2))); - _keyTuple[2] = bytes32(k3); - _keyTuple[3] = bytes32(uint256(uint160(k4))); - _keyTuple[4] = _boolToBytes32(k5); - _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); - - StoreSwitch.setField(_tableId, _keyTuple, 6, abi.encodePacked(uint8(v7)), getFieldLayout()); - } - - /** Set v7 (using the specified store) */ - function setV7( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - Enum2 v7 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); - _keyTuple[0] = bytes32(uint256(k1)); - _keyTuple[1] = bytes32(uint256(int256(k2))); - _keyTuple[2] = bytes32(k3); - _keyTuple[3] = bytes32(uint256(uint160(k4))); - _keyTuple[4] = _boolToBytes32(k5); - _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); - - _store.setField(_tableId, _keyTuple, 6, abi.encodePacked(uint8(v7)), getFieldLayout()); - } - /** Get the full data */ function get( uint256 k1, @@ -720,17 +491,15 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (StaticsData memory _table) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, getFieldLayout()); return decode(_blob); @@ -744,17 +513,15 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal view returns (StaticsData memory _table) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); bytes memory _blob = _store.getRecord(_tableId, _keyTuple, getFieldLayout()); return decode(_blob); @@ -767,28 +534,28 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7, + Enum2 k6, uint256 v1, int32 v2, bytes16 v3, address v4, bool v5, - Enum1 v6, - Enum2 v7 + Enum1 v6 ) internal { - bytes memory _data = encode(v1, v2, v3, v4, v5, v6, v7); + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5, v6); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ @@ -799,42 +566,33 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7, + Enum2 k6, uint256 v1, int32 v2, bytes16 v3, address v4, bool v5, - Enum1 v6, - Enum2 v7 + Enum1 v6 ) internal { - bytes memory _data = encode(v1, v2, v3, v4, v5, v6, v7); + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5, v6); - bytes32[] memory _keyTuple = new bytes32[](7); + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ - function set( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - StaticsData memory _table - ) internal { - set(k1, k2, k3, k4, k5, k6, k7, _table.v1, _table.v2, _table.v3, _table.v4, _table.v5, _table.v6, _table.v7); + function set(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6, StaticsData memory _table) internal { + set(k1, k2, k3, k4, k5, k6, _table.v1, _table.v2, _table.v3, _table.v4, _table.v5, _table.v6); } /** Set the full data using the data struct (using the specified store) */ @@ -845,27 +603,10 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7, + Enum2 k6, StaticsData memory _table ) internal { - set( - _store, - k1, - k2, - k3, - k4, - k5, - k6, - k7, - _table.v1, - _table.v2, - _table.v3, - _table.v4, - _table.v5, - _table.v6, - _table.v7 - ); + set(_store, k1, k2, k3, k4, k5, k6, _table.v1, _table.v2, _table.v3, _table.v4, _table.v5, _table.v6); } /** Decode the tightly packed blob using this table's field layout */ @@ -881,8 +622,18 @@ library Statics { _table.v5 = (_toBool(uint8(Bytes.slice1(_blob, 72)))); _table.v6 = Enum1(uint8(Bytes.slice1(_blob, 73))); + } - _table.v7 = Enum2(uint8(Bytes.slice1(_blob, 74))); + /** Tightly pack static data using this table's schema */ + function encodeStatic( + uint256 v1, + int32 v2, + bytes16 v3, + address v4, + bool v5, + Enum1 v6 + ) internal pure returns (bytes memory) { + return abi.encodePacked(v1, v2, v3, v4, v5, v6); } /** Tightly pack full data using this table's field layout */ @@ -892,10 +643,14 @@ library Statics { bytes16 v3, address v4, bool v5, - Enum1 v6, - Enum2 v7 + Enum1 v6 ) internal pure returns (bytes memory) { - return abi.encodePacked(v1, v2, v3, v4, v5, v6, v7); + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5, v6); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ @@ -905,54 +660,41 @@ library Statics { bytes16 k3, address k4, bool k5, - Enum1 k6, - Enum2 k7 + Enum2 k6 ) internal pure returns (bytes32[] memory) { - bytes32[] memory _keyTuple = new bytes32[](7); + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); return _keyTuple; } /* Delete all data for given keys */ - function deleteRecord(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function deleteRecord(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); StoreSwitch.deleteRecord(_tableId, _keyTuple, getFieldLayout()); } /* Delete all data for given keys (using the specified store) */ - function deleteRecord( - IStore _store, - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal { - bytes32[] memory _keyTuple = new bytes32[](7); + function deleteRecord(IStore _store, uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum2 k6) internal { + bytes32[] memory _keyTuple = new bytes32[](6); _keyTuple[0] = bytes32(uint256(k1)); _keyTuple[1] = bytes32(uint256(int256(k2))); _keyTuple[2] = bytes32(k3); _keyTuple[3] = bytes32(uint256(uint160(k4))); _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - _keyTuple[6] = bytes32(uint256(uint8(k7))); _store.deleteRecord(_tableId, _keyTuple, getFieldLayout()); } diff --git a/packages/cli/contracts/test/Tablegen.t.sol b/packages/cli/contracts/test/Tablegen.t.sol index 29f6ec8b29..af41a9f635 100644 --- a/packages/cli/contracts/test/Tablegen.t.sol +++ b/packages/cli/contracts/test/Tablegen.t.sol @@ -17,15 +17,14 @@ contract TablegenTest is Test, StoreMock { bytes16 k3 = hex"02"; address k4 = address(123); bool k5 = true; - Enum1 k6 = Enum1.E3; - Enum2 k7 = Enum2.E1; + Enum2 k6 = Enum2.E1; - Statics.setV1(k1, k2, k3, k4, k5, k6, k7, 4); - assertEq(Statics.getV1(k1, k2, k3, k4, k5, k6, k7), 4); + Statics.setV1(k1, k2, k3, k4, k5, k6, 4); + assertEq(Statics.getV1(k1, k2, k3, k4, k5, k6), 4); - StaticsData memory data = StaticsData(4, -5, hex"06", address(456), false, Enum1.E2, Enum2.E1); - Statics.set(k1, k2, k3, k4, k5, k6, k7, data); - assertEq(abi.encode(Statics.get(k1, k2, k3, k4, k5, k6, k7)), abi.encode(data)); + StaticsData memory data = StaticsData(4, -5, hex"06", address(456), false, Enum1.E2); + Statics.set(k1, k2, k3, k4, k5, k6, data); + assertEq(abi.encode(Statics.get(k1, k2, k3, k4, k5, k6)), abi.encode(data)); } function testDynamicsSetAndGet() public { diff --git a/packages/cli/scripts/generate-test-tables.ts b/packages/cli/scripts/generate-test-tables.ts index d7ca0db655..74ecec0817 100644 --- a/packages/cli/scripts/generate-test-tables.ts +++ b/packages/cli/scripts/generate-test-tables.ts @@ -18,8 +18,7 @@ try { k3: "bytes16", k4: "address", k5: "bool", - k6: "Enum1", - k7: "Enum2", + k6: "Enum2", }, valueSchema: { v1: "uint256", @@ -28,7 +27,6 @@ try { v4: "address", v5: "bool", v6: "Enum1", - v7: "Enum2", }, }, Dynamics1: { diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 95489023b1..d6a59390fa 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -3,5 +3,7 @@ export * from "./createContract"; export * from "./createNonceManager"; export * from "./getBurnerPrivateKey"; export * from "./hexToTableId"; +export * from "./readHex"; +export * from "./spliceHex"; export * from "./tableIdToHex"; export * from "./transportObserver"; diff --git a/packages/protocol-parser/src/readHex.test.ts b/packages/common/src/readHex.test.ts similarity index 100% rename from packages/protocol-parser/src/readHex.test.ts rename to packages/common/src/readHex.test.ts diff --git a/packages/protocol-parser/src/readHex.ts b/packages/common/src/readHex.ts similarity index 100% rename from packages/protocol-parser/src/readHex.ts rename to packages/common/src/readHex.ts diff --git a/packages/common/src/spliceHex.ts b/packages/common/src/spliceHex.ts new file mode 100644 index 0000000000..5ce8c1f6d0 --- /dev/null +++ b/packages/common/src/spliceHex.ts @@ -0,0 +1,6 @@ +import { Hex, concatHex } from "viem"; +import { readHex } from "./readHex"; + +export function spliceHex(data: Hex, start: number, deleteCount = 0, newData: Hex = "0x"): Hex { + return concatHex([readHex(data, 0, start), newData, readHex(data, start + deleteCount)]); +} diff --git a/packages/common/src/type-utils/common.ts b/packages/common/src/type-utils/common.ts index 2e7d66225c..e75f5054dc 100644 --- a/packages/common/src/type-utils/common.ts +++ b/packages/common/src/type-utils/common.ts @@ -19,3 +19,5 @@ export type OrDefaults = { }; export type UnionOmit = T extends any ? Omit : never; +export type UnionKeys = T extends any ? keyof T : never; +export type UnionPick> = T extends any ? Pick> : never; diff --git a/packages/dev-tools/README.md b/packages/dev-tools/README.md index cbab052e80..2b3da880e7 100644 --- a/packages/dev-tools/README.md +++ b/packages/dev-tools/README.md @@ -21,7 +21,7 @@ if (import.meta.env.DEV) { publicClient, walletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, worldAddress, worldAbi, write$, diff --git a/packages/dev-tools/src/DevToolsContext.tsx b/packages/dev-tools/src/DevToolsContext.tsx index 8a9d359e1f..2bc811dec5 100644 --- a/packages/dev-tools/src/DevToolsContext.tsx +++ b/packages/dev-tools/src/DevToolsContext.tsx @@ -1,12 +1,11 @@ import { createContext, ReactNode, useContext, useEffect, useState } from "react"; import { DevToolsOptions } from "./common"; import { ContractWrite } from "@latticexyz/common"; -import { StorageOperation } from "@latticexyz/store-sync"; -import { StoreConfig } from "@latticexyz/store"; +import { StorageAdapterLog } from "@latticexyz/store-sync"; type DevToolsContextValue = DevToolsOptions & { writes: ContractWrite[]; - storageOperations: StorageOperation[]; + storedLogs: StorageAdapterLog[]; }; const DevToolsContext = createContext(null); @@ -28,20 +27,20 @@ export const DevToolsProvider = ({ children, value }: Props) => { return () => sub.unsubscribe(); }, [value.write$]); - const [storageOperations, setStorageOperations] = useState[]>([]); + const [storedLogs, setStoredLogs] = useState([]); useEffect(() => { - const sub = value.blockStorageOperations$.subscribe(({ operations }) => { - setStorageOperations((val) => [...val, ...operations]); + const sub = value.storedBlockLogs$.subscribe(({ logs }) => { + setStoredLogs((val) => [...val, ...logs]); }); return () => sub.unsubscribe(); - }, [value.blockStorageOperations$]); + }, [value.storedBlockLogs$]); return ( {children} diff --git a/packages/dev-tools/src/actions/WriteSummary.tsx b/packages/dev-tools/src/actions/WriteSummary.tsx index fb281fa85d..f7f4dca1a5 100644 --- a/packages/dev-tools/src/actions/WriteSummary.tsx +++ b/packages/dev-tools/src/actions/WriteSummary.tsx @@ -132,6 +132,7 @@ export function WriteSummary({ write }: Props) { {events.map(({ eventName, args }, i) => { const table = hexToTableId((args as any).tableId); + // TODO: dedupe this with logs table so we can get both rendering the same return (
diff --git a/packages/dev-tools/src/common.ts b/packages/dev-tools/src/common.ts index 8ecbcac88c..42475fbab3 100644 --- a/packages/dev-tools/src/common.ts +++ b/packages/dev-tools/src/common.ts @@ -1,7 +1,7 @@ import { Observable } from "rxjs"; import { Abi, Block, Chain, PublicClient, Transport, WalletClient } from "viem"; import { StoreConfig } from "@latticexyz/store"; -import { BlockStorageOperations } from "@latticexyz/store-sync"; +import { StorageAdapterBlock } from "@latticexyz/store-sync"; import { ContractWrite } from "@latticexyz/common"; import { World as RecsWorld } from "@latticexyz/recs"; @@ -10,7 +10,7 @@ export type DevToolsOptions = { publicClient: PublicClient; walletClient: WalletClient; latestBlock$: Observable; - blockStorageOperations$: Observable>; + storedBlockLogs$: Observable; worldAddress: string | null; worldAbi: Abi; write$: Observable; diff --git a/packages/dev-tools/src/events/EventIcon.tsx b/packages/dev-tools/src/events/EventIcon.tsx index 8b6aa682e3..8b40c01690 100644 --- a/packages/dev-tools/src/events/EventIcon.tsx +++ b/packages/dev-tools/src/events/EventIcon.tsx @@ -1,22 +1,22 @@ import { assertExhaustive } from "@latticexyz/common/utils"; -import { StoreConfig } from "@latticexyz/store"; -import { StorageOperation } from "@latticexyz/store-sync"; +import { StorageAdapterLog } from "@latticexyz/store-sync"; type Props = { - type: StorageOperation["type"]; + type: StorageAdapterLog["eventName"]; }; export function EventIcon({ type }: Props) { switch (type) { - case "SetRecord": + case "StoreSetRecord": return =; - case "SetField": + case "StoreSpliceStaticData": + case "StoreSpliceDynamicData": return +; - case "DeleteRecord": + case "StoreDeleteRecord": return -; - // case "EphemeralRecord": - // return ~; + case "StoreEphemeralRecord": + return ~; default: - return assertExhaustive(type, `Unexpected storage operation type: ${type}`); + return assertExhaustive(type, `Unexpected event type: ${type}`); } } diff --git a/packages/dev-tools/src/events/EventsPage.tsx b/packages/dev-tools/src/events/EventsPage.tsx index 59e3f21888..e07a463810 100644 --- a/packages/dev-tools/src/events/EventsPage.tsx +++ b/packages/dev-tools/src/events/EventsPage.tsx @@ -1,9 +1,9 @@ import { useRef, useEffect } from "react"; import { useDevToolsContext } from "../DevToolsContext"; -import { StorageOperationsTable } from "./StorageOperationsTable"; +import { LogsTable } from "./LogsTable"; export function EventsPage() { - const { storageOperations } = useDevToolsContext(); + const { storedLogs } = useDevToolsContext(); const containerRef = useRef(null); const hoveredRef = useRef(false); const scrollBehaviorRef = useRef("auto"); @@ -13,7 +13,7 @@ export function EventsPage() { containerRef.current?.scrollIntoView({ behavior: scrollBehaviorRef.current, block: "end" }); } scrollBehaviorRef.current = "smooth"; - }, [storageOperations]); + }, [storedLogs]); return (
- +
); } diff --git a/packages/dev-tools/src/events/LogsTable.tsx b/packages/dev-tools/src/events/LogsTable.tsx new file mode 100644 index 0000000000..ac4779fb2b --- /dev/null +++ b/packages/dev-tools/src/events/LogsTable.tsx @@ -0,0 +1,72 @@ +import { StorageAdapterLog } from "@latticexyz/store-sync"; +import { EventIcon } from "./EventIcon"; +import { hexToTableId } from "@latticexyz/common"; + +// TODO: use react-table or similar for better perf with lots of logs + +type Props = { + logs: StorageAdapterLog[]; +}; + +export function LogsTable({ logs }: Props) { + return ( + + + + + + + + + + + + {logs.map((log) => { + const { namespace, name } = hexToTableId(log.args.tableId); + return ( + + + + + + + + ); + })} + +
blocktablekeyvalue
+ {log.blockNumber?.toString()} + + {namespace}:{name} + {log.args.keyTuple.join(",")} + + + {/* TODO: decode these values if we can */} + {log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord" + ? JSON.stringify({ + staticData: log.args.staticData, + encodedLengths: log.args.encodedLengths, + dynamicData: log.args.dynamicData, + }) + : null} + {log.eventName === "StoreSpliceStaticData" + ? JSON.stringify({ start: log.args.start, deleteCount: log.args.deleteCount, data: log.args.data }) + : null} + {log.eventName === "StoreSpliceDynamicData" + ? JSON.stringify({ + start: log.args.start, + deleteCount: log.args.deleteCount, + data: log.args.data, + encodedLengths: log.args.encodedLengths, + }) + : null} +
+ ); +} diff --git a/packages/dev-tools/src/events/StorageOperationsTable.tsx b/packages/dev-tools/src/events/StorageOperationsTable.tsx deleted file mode 100644 index bcf63d8876..0000000000 --- a/packages/dev-tools/src/events/StorageOperationsTable.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { StorageOperation } from "@latticexyz/store-sync"; -import { serialize } from "../serialize"; -import { EventIcon } from "./EventIcon"; -import { StoreConfig } from "@latticexyz/store"; - -// TODO: use react-table or similar for better perf with lots of logs - -type Props = { - operations: StorageOperation[]; -}; - -export function StorageOperationsTable({ operations }: Props) { - return ( - - - - - - - - - - - - {operations.map((operation) => ( - - - - - - - - ))} - -
blocktablekeyvalue
- {operation.log?.blockNumber.toString()} - - {operation.namespace}:{operation.name} - {serialize(operation.key)} - - - {operation.type === "SetRecord" ? serialize(operation.value) : null} - {operation.type === "SetField" ? serialize({ [operation.fieldName]: operation.fieldValue }) : null} -
- ); -} diff --git a/packages/dev-tools/src/summary/EventsSummary.tsx b/packages/dev-tools/src/summary/EventsSummary.tsx index 34e7bcebfb..fbc2ad87cb 100644 --- a/packages/dev-tools/src/summary/EventsSummary.tsx +++ b/packages/dev-tools/src/summary/EventsSummary.tsx @@ -1,12 +1,12 @@ import { NavButton } from "../NavButton"; import { useDevToolsContext } from "../DevToolsContext"; -import { StorageOperationsTable } from "../events/StorageOperationsTable"; +import { LogsTable } from "../events/LogsTable"; export function EventsSummary() { - const { storageOperations } = useDevToolsContext(); + const { storedLogs } = useDevToolsContext(); return ( <> - + See more diff --git a/packages/protocol-parser/src/common.ts b/packages/protocol-parser/src/common.ts index cf205abafd..377e6e9267 100644 --- a/packages/protocol-parser/src/common.ts +++ b/packages/protocol-parser/src/common.ts @@ -1,4 +1,5 @@ import { DynamicAbiType, SchemaAbiType, SchemaAbiTypeToPrimitiveType, StaticAbiType } from "@latticexyz/schema-type"; +import { Hex } from "viem"; /** @deprecated use `KeySchema` or `ValueSchema` instead */ export type Schema = { @@ -29,3 +30,9 @@ export type TableRecord; value: SchemaToPrimitives; }; + +export type ValueArgs = { + staticData: Hex; + encodedLengths: Hex; + dynamicData: Hex; +}; diff --git a/packages/protocol-parser/src/decodeRecord.ts b/packages/protocol-parser/src/decodeRecord.ts index c48dc0bd24..411d93327c 100644 --- a/packages/protocol-parser/src/decodeRecord.ts +++ b/packages/protocol-parser/src/decodeRecord.ts @@ -10,7 +10,7 @@ import { decodeDynamicField } from "./decodeDynamicField"; import { decodeStaticField } from "./decodeStaticField"; import { hexToPackedCounter } from "./hexToPackedCounter"; import { staticDataLength } from "./staticDataLength"; -import { readHex } from "./readHex"; +import { readHex } from "@latticexyz/common"; /** @deprecated use `decodeValue` instead */ export function decodeRecord(valueSchema: Schema, data: Hex): readonly (StaticPrimitiveType | DynamicPrimitiveType)[] { diff --git a/packages/protocol-parser/src/decodeValueArgs.ts b/packages/protocol-parser/src/decodeValueArgs.ts new file mode 100644 index 0000000000..d9f5e5161f --- /dev/null +++ b/packages/protocol-parser/src/decodeValueArgs.ts @@ -0,0 +1,20 @@ +import { concatHex } from "viem"; +import { isStaticAbiType } from "@latticexyz/schema-type"; +import { SchemaToPrimitives, ValueArgs, ValueSchema } from "./common"; +import { decodeValue } from "./decodeValue"; +import { staticDataLength } from "./staticDataLength"; +import { readHex } from "@latticexyz/common"; + +export function decodeValueArgs( + valueSchema: TSchema, + { staticData, encodedLengths, dynamicData }: ValueArgs +): SchemaToPrimitives { + return decodeValue( + valueSchema, + concatHex([ + readHex(staticData, 0, staticDataLength(Object.values(valueSchema).filter(isStaticAbiType))), + encodedLengths, + dynamicData, + ]) + ); +} diff --git a/packages/protocol-parser/src/encodeLengths.test.ts b/packages/protocol-parser/src/encodeLengths.test.ts new file mode 100644 index 0000000000..c55204ddc6 --- /dev/null +++ b/packages/protocol-parser/src/encodeLengths.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest"; +import { encodeLengths } from "./encodeLengths"; + +describe("encodeLengths", () => { + it("can encode bool key tuple", () => { + expect(encodeLengths(["0x1234", "0x12345678"])).toMatchInlineSnapshot( + '"0x0000000000000000000000000000000000000004000000000200000000000006"' + ); + }); +}); diff --git a/packages/protocol-parser/src/encodeLengths.ts b/packages/protocol-parser/src/encodeLengths.ts new file mode 100644 index 0000000000..5a3bbf0827 --- /dev/null +++ b/packages/protocol-parser/src/encodeLengths.ts @@ -0,0 +1,12 @@ +import { Hex, concatHex, padHex, size } from "viem"; +import { encodeField } from "./encodeField"; + +export function encodeLengths(values: Hex[]): Hex { + const byteLengths = values.map(size).reverse(); + const totalByteLength = byteLengths.reduce((total, length) => total + BigInt(length), 0n); + + return padHex( + concatHex([...byteLengths.map((length) => encodeField("uint40", length)), encodeField("uint56", totalByteLength)]), + { size: 32, dir: "left" } + ); +} diff --git a/packages/protocol-parser/src/encodeValue.ts b/packages/protocol-parser/src/encodeValue.ts index f953ccee21..df85964b65 100644 --- a/packages/protocol-parser/src/encodeValue.ts +++ b/packages/protocol-parser/src/encodeValue.ts @@ -1,18 +1,11 @@ -import { isStaticAbiType, isDynamicAbiType } from "@latticexyz/schema-type"; -import { Hex } from "viem"; +import { Hex, concatHex } from "viem"; import { SchemaToPrimitives, ValueSchema } from "./common"; -import { encodeRecord } from "./encodeRecord"; +import { encodeValueArgs } from "./encodeValueArgs"; export function encodeValue( valueSchema: TSchema, value: SchemaToPrimitives ): Hex { - const staticFields = Object.values(valueSchema).filter(isStaticAbiType); - const dynamicFields = Object.values(valueSchema).filter(isDynamicAbiType); - - // TODO: refactor and move all encodeRecord logic into this method so we can delete encodeRecord - - // This currently assumes fields/values are ordered by static, dynamic - // TODO: make sure we preserve ordering based on value schema definition - return encodeRecord({ staticFields, dynamicFields }, Object.values(value)); + const { staticData, encodedLengths, dynamicData } = encodeValueArgs(valueSchema, value); + return concatHex([staticData, encodedLengths, dynamicData]); } diff --git a/packages/protocol-parser/src/encodeValueArgs.ts b/packages/protocol-parser/src/encodeValueArgs.ts new file mode 100644 index 0000000000..0f58a01b8a --- /dev/null +++ b/packages/protocol-parser/src/encodeValueArgs.ts @@ -0,0 +1,27 @@ +import { StaticPrimitiveType, DynamicPrimitiveType, isStaticAbiType, isDynamicAbiType } from "@latticexyz/schema-type"; +import { concatHex } from "viem"; +import { encodeField } from "./encodeField"; +import { SchemaToPrimitives, ValueArgs, ValueSchema } from "./common"; +import { encodeLengths } from "./encodeLengths"; + +export function encodeValueArgs( + valueSchema: TSchema, + value: SchemaToPrimitives +): ValueArgs { + const staticFields = Object.values(valueSchema).filter(isStaticAbiType); + const dynamicFields = Object.values(valueSchema).filter(isDynamicAbiType); + + const values = Object.values(value); + const staticValues = values.slice(0, staticFields.length) as readonly StaticPrimitiveType[]; + const dynamicValues = values.slice(staticFields.length) as readonly DynamicPrimitiveType[]; + + const encodedStaticValues = staticValues.map((value, i) => encodeField(staticFields[i], value)); + const encodedDynamicValues = dynamicValues.map((value, i) => encodeField(dynamicFields[i], value)); + const encodedLengths = encodeLengths(encodedDynamicValues); + + return { + staticData: concatHex(encodedStaticValues), + encodedLengths, + dynamicData: concatHex(encodedDynamicValues), + }; +} diff --git a/packages/protocol-parser/src/hexToPackedCounter.ts b/packages/protocol-parser/src/hexToPackedCounter.ts index c8816d15f7..d29121b7ff 100644 --- a/packages/protocol-parser/src/hexToPackedCounter.ts +++ b/packages/protocol-parser/src/hexToPackedCounter.ts @@ -2,7 +2,7 @@ import { Hex } from "viem"; import { decodeStaticField } from "./decodeStaticField"; import { decodeDynamicField } from "./decodeDynamicField"; import { InvalidHexLengthForPackedCounterError, PackedCounterLengthMismatchError } from "./errors"; -import { readHex } from "./readHex"; +import { readHex } from "@latticexyz/common"; // Keep this logic in sync with PackedCounter.sol diff --git a/packages/protocol-parser/src/index.ts b/packages/protocol-parser/src/index.ts index 805719cc39..9874481e71 100644 --- a/packages/protocol-parser/src/index.ts +++ b/packages/protocol-parser/src/index.ts @@ -7,18 +7,19 @@ export * from "./decodeKeyTuple"; export * from "./decodeRecord"; export * from "./decodeStaticField"; export * from "./decodeValue"; +export * from "./decodeValueArgs"; export * from "./encodeField"; export * from "./encodeKey"; export * from "./encodeKeyTuple"; export * from "./encodeRecord"; export * from "./encodeValue"; +export * from "./encodeValueArgs"; export * from "./errors"; export * from "./fieldLayoutToHex"; export * from "./hexToPackedCounter"; export * from "./hexToSchema"; export * from "./hexToTableSchema"; export * from "./keySchemaToHex"; -export * from "./readHex"; export * from "./schemaIndexToAbiType"; export * from "./schemaToHex"; export * from "./staticDataLength"; diff --git a/packages/store-indexer/bin/postgres-indexer.ts b/packages/store-indexer/bin/postgres-indexer.ts index 9a7ea96d1e..8370b10338 100644 --- a/packages/store-indexer/bin/postgres-indexer.ts +++ b/packages/store-indexer/bin/postgres-indexer.ts @@ -67,14 +67,14 @@ const database = drizzle(postgres(env.DATABASE_URL), { let startBlock = env.START_BLOCK; -const storageAdapter = await postgresStorage({ database, publicClient }); +const { storageAdapter, internalTables } = await postgresStorage({ database, publicClient }); // Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. try { const currentChainStates = await database .select() - .from(storageAdapter.internalTables.chain) - .where(eq(storageAdapter.internalTables.chain.chainId, chainId)) + .from(internalTables.chain) + .where(eq(internalTables.chain.chainId, chainId)) .execute(); // TODO: replace this type workaround with `noUncheckedIndexedAccess: true` when we can fix all the issues related (https://github.com/latticexyz/mud/issues/1212) const currentChainState: (typeof currentChainStates)[number] | undefined = currentChainStates[0]; @@ -98,14 +98,14 @@ try { // ignore errors, this is optional } -const { latestBlockNumber$, blockStorageOperations$ } = await createStoreSync({ +const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({ storageAdapter, publicClient, startBlock, maxBlockRange: env.MAX_BLOCK_RANGE, }); -combineLatest([latestBlockNumber$, blockStorageOperations$]) +combineLatest([latestBlockNumber$, storedBlockLogs$]) .pipe( filter( ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => latestBlockNumber === lastBlockNumberProcessed diff --git a/packages/store-indexer/bin/sqlite-indexer.ts b/packages/store-indexer/bin/sqlite-indexer.ts index 1433768aef..7cdaa50c87 100644 --- a/packages/store-indexer/bin/sqlite-indexer.ts +++ b/packages/store-indexer/bin/sqlite-indexer.ts @@ -90,14 +90,14 @@ try { // ignore errors, this is optional } -const { latestBlockNumber$, blockStorageOperations$ } = await syncToSqlite({ +const { latestBlockNumber$, storedBlockLogs$ } = await syncToSqlite({ database, publicClient, startBlock, maxBlockRange: env.MAX_BLOCK_RANGE, }); -combineLatest([latestBlockNumber$, blockStorageOperations$]) +combineLatest([latestBlockNumber$, storedBlockLogs$]) .pipe( filter( ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => latestBlockNumber === lastBlockNumberProcessed diff --git a/packages/store-indexer/package.json b/packages/store-indexer/package.json index c17410cf6b..175b8b7090 100644 --- a/packages/store-indexer/package.json +++ b/packages/store-indexer/package.json @@ -40,9 +40,9 @@ "@trpc/client": "10.34.0", "@trpc/server": "10.34.0", "@wagmi/chains": "^0.2.22", - "better-sqlite3": "^8.4.0", + "better-sqlite3": "^8.6.0", "debug": "^4.3.4", - "drizzle-orm": "^0.27.0", + "drizzle-orm": "^0.28.5", "fastify": "^4.21.0", "postgres": "^3.3.5", "rxjs": "7.5.5", diff --git a/packages/store-indexer/src/sqlite/createQueryAdapter.ts b/packages/store-indexer/src/sqlite/createQueryAdapter.ts index eae41e90c4..07d4e86949 100644 --- a/packages/store-indexer/src/sqlite/createQueryAdapter.ts +++ b/packages/store-indexer/src/sqlite/createQueryAdapter.ts @@ -1,6 +1,6 @@ import { eq } from "drizzle-orm"; import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"; -import { createSqliteTable, chainState, getTables } from "@latticexyz/store-sync/sqlite"; +import { buildTable, chainState, getTables } from "@latticexyz/store-sync/sqlite"; import { QueryAdapter } from "@latticexyz/store-sync/trpc-indexer"; import { debug } from "../debug"; @@ -16,7 +16,7 @@ export async function createQueryAdapter(database: BaseSQLiteDatabase<"sync", an const tables = getTables(database).filter((table) => table.address === address); const tablesWithRecords = tables.map((table) => { - const sqliteTable = createSqliteTable(table); + const sqliteTable = buildTable(table); const records = database.select().from(sqliteTable).where(eq(sqliteTable.__isDeleted, false)).all(); return { ...table, diff --git a/packages/store-sync/package.json b/packages/store-sync/package.json index 74ac0cd678..ca4ce4abbc 100644 --- a/packages/store-sync/package.json +++ b/packages/store-sync/package.json @@ -56,8 +56,8 @@ "@trpc/client": "10.34.0", "@trpc/server": "10.34.0", "debug": "^4.3.4", - "drizzle-orm": "^0.27.0", - "kysely": "^0.26.1", + "drizzle-orm": "^0.28.5", + "kysely": "^0.26.3", "postgres": "^3.3.5", "rxjs": "7.5.5", "sql.js": "^1.8.0", diff --git a/packages/store-sync/src/blockLogsToStorage.test.ts b/packages/store-sync/src/blockLogsToStorage.test.ts deleted file mode 100644 index e3f134db48..0000000000 --- a/packages/store-sync/src/blockLogsToStorage.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { blockLogsToStorage } from "./blockLogsToStorage"; -import storeConfig from "@latticexyz/store/mud.config"; -import { isDefined } from "@latticexyz/common/utils"; -import { tableIdToHex } from "@latticexyz/common"; -import { StorageAdapter } from "./common"; - -const mockedCallbacks = { - registerTables: vi.fn, ReturnType>(), - getTables: vi.fn, ReturnType>(), - storeOperations: vi.fn< - Parameters, - ReturnType - >(), -}; - -const mockedDecode = blockLogsToStorage( - mockedCallbacks as any as StorageAdapter -); - -describe("blockLogsToStorage", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it("call setField with data properly decoded", async () => { - mockedCallbacks.getTables.mockImplementation(async ({ tables }) => { - return tables - .map((table) => { - if (table.namespace === "" && table.name === "Inventory") { - return { - ...table, - tableId: tableIdToHex("", "Inventory"), - keySchema: { - owner: "address", - item: "uint32", - itemVariant: "uint32", - } as const, - valueSchema: { - amount: "uint32", - } as const, - }; - } - }) - .filter(isDefined); - }); - - const operations = await mockedDecode({ - blockNumber: 5448n, - logs: [ - { - address: "0x5fbdb2315678afecb367f032d93f642f64180aa3", - topics: ["0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32"], - data: "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e76656e746f72790000000000000000000000000000000000000000000000000000000000000000000000000002800004010004000000000000000000000000000000000000000000000000000000001c030061030300000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000001600000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000056f776e657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046974656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6974656d56617269616e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006616d6f756e740000000000000000000000000000000000000000000000000000", - blockHash: "0x50b3e15ef79abc7c72126ca5ecbd0cf0773c48ade0528e1e3debaf9bbe3643da", - blockNumber: 5448n, - transactionHash: "0xd66a9f982ddb8da9ca15a72c5713c3390fb3a6c2f7fe81a8c9026d0ba8130189", - transactionIndex: 16, - logIndex: 53, - removed: false, - args: { - tableId: "0x6d756473746f726500000000000000005461626c657300000000000000000000", - keyTuple: ["0x00000000000000000000000000000000496e76656e746f727900000000000000"], - data: "0x0004010004000000000000000000000000000000000000000000000000000000001c030061030300000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000001600000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000056f776e657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046974656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6974656d56617269616e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006616d6f756e740000000000000000000000000000000000000000000000000000", - }, - eventName: "StoreSetRecord", - }, - { - address: "0x5fbdb2315678afecb367f032d93f642f64180aa3", - topics: ["0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46"], - data: "0x00000000000000000000000000000000496e76656e746f7279000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000796eb990a3f9c431c69149c7a168b91596d87f600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000040000000800000000000000000000000000000000000000000000000000000000", - blockHash: "0x03e962e7402b2ab295b92feac342a132111dd14b0d1fd4d4a0456fdc77981577", - blockNumber: 5448n, - transactionHash: "0xa6986924609542dc4c2d81c53799d8eab47109ef34ee1e422de595e19ee9bfa4", - transactionIndex: 88, - logIndex: 88, - removed: false, - args: { - tableId: "0x00000000000000000000000000000000496e76656e746f727900000000000000", - keyTuple: [ - "0x000000000000000000000000796eb990a3f9c431c69149c7a168b91596d87f60", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000001", - ], - schemaIndex: 0, - data: "0x00000008", - }, - eventName: "StoreSetField", - }, - ], - }); - - expect(mockedCallbacks.storeOperations).toMatchInlineSnapshot(` - [MockFunction spy] { - "calls": [ - [ - { - "blockNumber": 5448n, - "operations": [ - { - "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "fieldName": "amount", - "fieldValue": 8, - "key": { - "item": 1, - "itemVariant": 1, - "owner": "0x796eb990A3F9C431C69149c7a168b91596D87F60", - }, - "log": { - "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "args": { - "data": "0x00000008", - "keyTuple": [ - "0x000000000000000000000000796eb990a3f9c431c69149c7a168b91596d87f60", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000001", - ], - "schemaIndex": 0, - "tableId": "0x00000000000000000000000000000000496e76656e746f727900000000000000", - }, - "blockHash": "0x03e962e7402b2ab295b92feac342a132111dd14b0d1fd4d4a0456fdc77981577", - "blockNumber": 5448n, - "data": "0x00000000000000000000000000000000496e76656e746f7279000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000796eb990a3f9c431c69149c7a168b91596d87f600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000040000000800000000000000000000000000000000000000000000000000000000", - "eventName": "StoreSetField", - "logIndex": 88, - "removed": false, - "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46", - ], - "transactionHash": "0xa6986924609542dc4c2d81c53799d8eab47109ef34ee1e422de595e19ee9bfa4", - "transactionIndex": 88, - }, - "name": "Inventory", - "namespace": "", - "type": "SetField", - }, - ], - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], - } - `); - - expect(operations).toMatchInlineSnapshot(` - { - "blockNumber": 5448n, - "operations": [ - { - "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "fieldName": "amount", - "fieldValue": 8, - "key": { - "item": 1, - "itemVariant": 1, - "owner": "0x796eb990A3F9C431C69149c7a168b91596D87F60", - }, - "log": { - "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "args": { - "data": "0x00000008", - "keyTuple": [ - "0x000000000000000000000000796eb990a3f9c431c69149c7a168b91596d87f60", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000001", - ], - "schemaIndex": 0, - "tableId": "0x00000000000000000000000000000000496e76656e746f727900000000000000", - }, - "blockHash": "0x03e962e7402b2ab295b92feac342a132111dd14b0d1fd4d4a0456fdc77981577", - "blockNumber": 5448n, - "data": "0x00000000000000000000000000000000496e76656e746f7279000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000796eb990a3f9c431c69149c7a168b91596d87f600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000040000000800000000000000000000000000000000000000000000000000000000", - "eventName": "StoreSetField", - "logIndex": 88, - "removed": false, - "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46", - ], - "transactionHash": "0xa6986924609542dc4c2d81c53799d8eab47109ef34ee1e422de595e19ee9bfa4", - "transactionIndex": 88, - }, - "name": "Inventory", - "namespace": "", - "type": "SetField", - }, - ], - } - `); - }); -}); diff --git a/packages/store-sync/src/blockLogsToStorage.ts b/packages/store-sync/src/blockLogsToStorage.ts deleted file mode 100644 index e2d025f3e3..0000000000 --- a/packages/store-sync/src/blockLogsToStorage.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { decodeField, decodeKeyTuple, decodeRecord, abiTypesToSchema, hexToSchema } from "@latticexyz/protocol-parser"; -import { - StoreConfig, - ConfigToKeyPrimitives as Key, - ConfigToValuePrimitives as Value, - ConfigToValuePrimitives, -} from "@latticexyz/store"; -import { decodeAbiParameters, getAddress, parseAbiParameters } from "viem"; -import { debug } from "./debug"; -import { isDefined } from "@latticexyz/common/utils"; -import { BlockLogs, StorageAdapter, StorageOperation, Table } from "./common"; -import { hexToTableId, tableIdToHex } from "@latticexyz/common"; -import storeConfig from "@latticexyz/store/mud.config"; - -// TODO: adjust when we get namespace support (https://github.com/latticexyz/mud/issues/994) and when table has namespace key (https://github.com/latticexyz/mud/issues/1201) -const schemasTable = storeConfig.tables.Tables; -const schemasTableId = tableIdToHex(storeConfig.namespace, schemasTable.name); - -export type BlockStorageOperations = { - blockNumber: BlockLogs["blockNumber"]; - operations: StorageOperation[]; -}; - -export type BlockLogsToStorageResult = ( - block: BlockLogs -) => Promise>; - -export function blockLogsToStorage({ - registerTables, - getTables, - storeOperations, -}: StorageAdapter): BlockLogsToStorageResult { - return async (block) => { - // Find table schema registration events - const newTables = block.logs - .map((log) => { - try { - if (log.eventName !== "StoreSetRecord") return; - if (log.args.tableId !== schemasTableId) return; - - // TODO: refactor encode/decode to use Record schemas - // TODO: refactor to decode key with protocol-parser utils - - const [tableId, ...otherKeys] = log.args.keyTuple; - if (otherKeys.length) { - console.warn("registerSchema event is expected to have only one key in key tuple, but got multiple", log); - } - - const table = hexToTableId(tableId); - - const valueTuple = decodeRecord(abiTypesToSchema(Object.values(schemasTable.valueSchema)), log.args.data); - const value = Object.fromEntries( - Object.keys(schemasTable.valueSchema).map((name, i) => [name, valueTuple[i]]) - ) as ConfigToValuePrimitives; - - const keySchema = hexToSchema(value.keySchema); - const valueSchema = hexToSchema(value.valueSchema); - const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0]; - const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0]; - - const valueAbiTypes = [...valueSchema.staticFields, ...valueSchema.dynamicFields]; - - return { - address: log.address, - tableId, - namespace: table.namespace, - name: table.name, - keySchema: Object.fromEntries(keySchema.staticFields.map((abiType, i) => [keyNames[i], abiType])), - valueSchema: Object.fromEntries(valueAbiTypes.map((abiType, i) => [fieldNames[i], abiType])), - }; - } catch (error: unknown) { - console.error("Failed to get table from log", log, error); - } - }) - .filter(isDefined); - - // Then register tables before we start storing data in them - if (newTables.length > 0) { - await registerTables({ - blockNumber: block.blockNumber, - tables: newTables, - }); - } - - const tablesToFetch = Array.from( - new Set( - block.logs.map((log) => - JSON.stringify({ - address: getAddress(log.address), - tableId: log.args.tableId, - ...hexToTableId(log.args.tableId), - }) - ) - ) - ).map((json) => JSON.parse(json)); - - const tables = Object.fromEntries( - ( - await getTables({ - blockNumber: block.blockNumber, - tables: tablesToFetch, - }) - ).map((table) => [`${getAddress(table.address)}:${table.tableId}`, table]) - ) as Record; - - const operations = block.logs - .map((log): StorageOperation | undefined => { - try { - const table = tables[`${getAddress(log.address)}:${log.args.tableId}`]; - if (!table) { - debug("no table found for event, skipping", hexToTableId(log.args.tableId), log); - return; - } - - const keyNames = Object.keys(table.keySchema); - const keyValues = decodeKeyTuple( - { staticFields: Object.values(table.keySchema), dynamicFields: [] }, - log.args.keyTuple - ); - const key = Object.fromEntries(keyValues.map((value, i) => [keyNames[i], value])) as Key< - TConfig, - keyof TConfig["tables"] - >; - - const valueAbiTypes = Object.values(table.valueSchema); - const valueSchema = abiTypesToSchema(valueAbiTypes); - const fieldNames = Object.keys(table.valueSchema); - - // TODO: decide if we should split these up into distinct operations so the storage adapter can decide whether to combine or not - if (log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord") { - const valueTuple = decodeRecord(valueSchema, log.args.data); - const value = Object.fromEntries(fieldNames.map((name, i) => [name, valueTuple[i]])) as Value< - TConfig, - keyof TConfig["tables"] - >; - return { - log, - address: getAddress(log.address), - namespace: table.namespace, - name: table.name, - type: "SetRecord", - key, - value, - }; - } - - if (log.eventName === "StoreSetField") { - const fieldName = fieldNames[log.args.schemaIndex] as string & - keyof Value; - const fieldValue = decodeField(valueAbiTypes[log.args.schemaIndex], log.args.data) as Value< - TConfig, - keyof TConfig["tables"] - >[typeof fieldName]; - return { - log, - address: getAddress(log.address), - namespace: table.namespace, - name: table.name, - type: "SetField", - key, - fieldName, - fieldValue, - }; - } - - if (log.eventName === "StoreDeleteRecord") { - return { - log, - address: getAddress(log.address), - namespace: table.namespace, - name: table.name, - type: "DeleteRecord", - key, - }; - } - - debug("unknown store event or log, skipping", log); - return; - } catch (error: unknown) { - console.error("Failed to translate log to storage operation", log, error); - } - }) - .filter(isDefined); - - await storeOperations({ blockNumber: block.blockNumber, operations }); - - return { - blockNumber: block.blockNumber, - operations, - }; - }; -} diff --git a/packages/store-sync/src/common.ts b/packages/store-sync/src/common.ts index d903b05ba1..4927923521 100644 --- a/packages/store-sync/src/common.ts +++ b/packages/store-sync/src/common.ts @@ -1,15 +1,10 @@ import { Address, Block, Hex, Log, PublicClient } from "viem"; -import { GroupLogsByBlockNumberResult } from "@latticexyz/block-logs-stream"; -import { - StoreConfig, - ConfigToKeyPrimitives as Key, - ConfigToValuePrimitives as Value, - StoreEventsAbiItem, - StoreEventsAbi, -} from "@latticexyz/store"; +import { StoreConfig, StoreEventsAbiItem, StoreEventsAbi } from "@latticexyz/store"; +import storeConfig from "@latticexyz/store/mud.config"; import { Observable } from "rxjs"; -import { BlockStorageOperations } from "./blockLogsToStorage"; -import { KeySchema, ValueSchema, TableRecord } from "@latticexyz/protocol-parser"; +import { tableIdToHex } from "@latticexyz/common"; +import { UnionPick } from "@latticexyz/common/type-utils"; +import { KeySchema, TableRecord, ValueSchema } from "@latticexyz/protocol-parser"; export type ChainId = number; export type WorldId = `${ChainId}:${Address}`; @@ -29,52 +24,7 @@ export type Table = { export type TableWithRecords = Table & { records: TableRecord[] }; export type StoreEventsLog = Log; -export type BlockLogs = GroupLogsByBlockNumberResult[number]; - -export type BaseStorageOperation = { - log?: StoreEventsLog; - address: Hex; - namespace: TableNamespace; - name: TableName; -}; - -export type SetRecordOperation = BaseStorageOperation & { - type: "SetRecord"; -} & { - [TTable in keyof TConfig["tables"]]: { - name: TTable & string; - key: Key; - value: Value; - }; - }[keyof TConfig["tables"]]; - -export type SetFieldOperation = BaseStorageOperation & { - type: "SetField"; -} & { - [TTable in keyof TConfig["tables"]]: { - name: TTable & string; - key: Key; - } & { - [TValue in keyof Value]: { - fieldName: TValue & string; - fieldValue: Value[TValue]; - }; - }[keyof Value]; - }[keyof TConfig["tables"]]; - -export type DeleteRecordOperation = BaseStorageOperation & { - type: "DeleteRecord"; -} & { - [TTable in keyof TConfig["tables"]]: { - name: TTable & string; - key: Key; - }; - }[keyof TConfig["tables"]]; - -export type StorageOperation = - | SetFieldOperation - | SetRecordOperation - | DeleteRecordOperation; +export type BlockLogs = { blockNumber: StoreEventsLog["blockNumber"]; logs: StoreEventsLog[] }; export type SyncOptions = { /** @@ -112,22 +62,19 @@ export type SyncOptions = { }; }; -export type SyncResult = { +export type SyncResult = { latestBlock$: Observable; latestBlockNumber$: Observable; blockLogs$: Observable; - blockStorageOperations$: Observable>; + storedBlockLogs$: Observable; waitForTransaction: (tx: Hex) => Promise; }; -export type StorageAdapter = { - registerTables: (opts: { blockNumber: BlockLogs["blockNumber"]; tables: Table[] }) => Promise; - getTables: (opts: { - blockNumber: BlockLogs["blockNumber"]; - tables: Pick[]; - }) => Promise; - storeOperations: (opts: { - blockNumber: BlockLogs["blockNumber"]; - operations: StorageOperation[]; - }) => Promise; -}; +// TODO: add optional, original log to this? +export type StorageAdapterLog = Partial & UnionPick; +export type StorageAdapterBlock = { blockNumber: BlockLogs["blockNumber"]; logs: StorageAdapterLog[] }; +export type StorageAdapter = (block: StorageAdapterBlock) => Promise; + +// TODO: adjust when we get namespace support (https://github.com/latticexyz/mud/issues/994) and when table has namespace key (https://github.com/latticexyz/mud/issues/1201) +export const schemasTable = storeConfig.tables.Tables; +export const schemasTableId = tableIdToHex(storeConfig.namespace, schemasTable.name); diff --git a/packages/store-sync/src/createStoreSync.ts b/packages/store-sync/src/createStoreSync.ts index 2e0cfc0857..dcd8852df4 100644 --- a/packages/store-sync/src/createStoreSync.ts +++ b/packages/store-sync/src/createStoreSync.ts @@ -1,6 +1,13 @@ -import { ConfigToKeyPrimitives, ConfigToValuePrimitives, StoreConfig, storeEventsAbi } from "@latticexyz/store"; +import { StoreConfig, storeEventsAbi } from "@latticexyz/store"; import { Hex, TransactionReceiptNotFoundError } from "viem"; -import { SetRecordOperation, StorageAdapter, SyncOptions, SyncResult, TableWithRecords } from "./common"; +import { + StorageAdapter, + StorageAdapterBlock, + StorageAdapterLog, + SyncOptions, + SyncResult, + TableWithRecords, +} from "./common"; import { createBlockStream, blockRangeToLogs, groupLogsByBlockNumber } from "@latticexyz/block-logs-stream"; import { filter, @@ -20,16 +27,16 @@ import { scan, identity, } from "rxjs"; -import { BlockStorageOperations, blockLogsToStorage } from "./blockLogsToStorage"; import { debug as parentDebug } from "./debug"; import { createIndexerClient } from "./trpc-indexer"; import { SyncStep } from "./SyncStep"; import { chunk, isDefined } from "@latticexyz/common/utils"; +import { encodeKey, encodeValueArgs } from "@latticexyz/protocol-parser"; const debug = parentDebug.extend("createStoreSync"); type CreateStoreSyncOptions = SyncOptions & { - storageAdapter: StorageAdapter; + storageAdapter: StorageAdapter; onProgress?: (opts: { step: SyncStep; percentage: number; @@ -39,8 +46,6 @@ type CreateStoreSyncOptions = SyncOpt }) => void; }; -type CreateStoreSyncResult = SyncResult; - export async function createStoreSync({ storageAdapter, onProgress, @@ -50,7 +55,7 @@ export async function createStoreSync maxBlockRange, initialState, indexerUrl, -}: CreateStoreSyncOptions): Promise> { +}: CreateStoreSyncOptions): Promise { const initialState$ = defer( async (): Promise< | { @@ -109,7 +114,7 @@ export async function createStoreSync tap((startBlock) => debug("starting sync from block", startBlock)) ); - const initialStorageOperations$ = initialState$.pipe( + const initialLogs$ = initialState$.pipe( filter( (initialState): initialState is { blockNumber: bigint; tables: TableWithRecords[] } => initialState != null && initialState.blockNumber != null && initialState.tables.length > 0 @@ -125,27 +130,28 @@ export async function createStoreSync message: "Hydrating from snapshot", }); - await storageAdapter.registerTables({ blockNumber, tables }); - - const operations: SetRecordOperation[] = tables.flatMap((table) => - table.records.map((record) => ({ - type: "SetRecord", - address: table.address, - namespace: table.namespace, - name: table.name, - key: record.key as ConfigToKeyPrimitives, - value: record.value as ConfigToValuePrimitives, - })) + const logs: StorageAdapterLog[] = tables.flatMap((table) => + table.records.map( + (record): StorageAdapterLog => ({ + eventName: "StoreSetRecord", + address: table.address, + args: { + tableId: table.tableId, + keyTuple: encodeKey(table.keySchema, record.key), + ...encodeValueArgs(table.valueSchema, record.value), + }, + }) + ) ); // Split snapshot operations into chunks so we can update the progress callback (and ultimately render visual progress for the user). // This isn't ideal if we want to e.g. batch load these into a DB in a single DB tx, but we'll take it. // // Split into 50 equal chunks (for better `onProgress` updates) but only if we have 100+ items per chunk - const chunkSize = Math.max(100, Math.floor(operations.length / 50)); - const chunks = Array.from(chunk(operations, chunkSize)); + const chunkSize = Math.max(100, Math.floor(logs.length / 50)); + const chunks = Array.from(chunk(logs, chunkSize)); for (const [i, chunk] of chunks.entries()) { - await storageAdapter.storeOperations({ blockNumber, operations: chunk }); + await storageAdapter({ blockNumber, logs: chunk }); onProgress?.({ step: SyncStep.SNAPSHOT, percentage: (i + chunk.length) / chunks.length, @@ -163,7 +169,7 @@ export async function createStoreSync message: "Hydrated from snapshot", }); - return { blockNumber, operations }; + return { blockNumber, logs }; }), shareReplay(1) ); @@ -196,12 +202,15 @@ export async function createStoreSync ); let lastBlockNumberProcessed: bigint | null = null; - const blockStorageOperations$ = concat( - initialStorageOperations$, + const storedBlockLogs$ = concat( + initialLogs$, blockLogs$.pipe( - concatMap(blockLogsToStorage(storageAdapter)), - tap(({ blockNumber, operations }) => { - debug("stored", operations.length, "operations for block", blockNumber); + concatMap(async (block) => { + await storageAdapter(block); + return block; + }), + tap(({ blockNumber, logs }) => { + debug("stored", logs.length, "logs for block", blockNumber); lastBlockNumberProcessed = blockNumber; if (startBlock != null && endBlock != null) { @@ -232,8 +241,8 @@ export async function createStoreSync // keep 10 blocks worth processed transactions in memory const recentBlocksWindow = 10; // most recent block first, for ease of pulling the first one off the array - const recentBlocks$ = blockStorageOperations$.pipe( - scan( + const recentBlocks$ = storedBlockLogs$.pipe( + scan( (recentBlocks, block) => [block, ...recentBlocks].slice(0, recentBlocksWindow), [] ), @@ -249,7 +258,7 @@ export async function createStoreSync // We could potentially speed this up a tiny bit by racing to see if 1) tx exists in processed block or 2) fetch tx receipt for latest block processed const hasTransaction$ = recentBlocks$.pipe( concatMap(async (blocks) => { - const txs = blocks.flatMap((block) => block.operations.map((op) => op.log?.transactionHash).filter(isDefined)); + const txs = blocks.flatMap((block) => block.logs.map((op) => op.transactionHash).filter(isDefined)); if (txs.includes(tx)) return true; try { @@ -274,7 +283,7 @@ export async function createStoreSync latestBlock$, latestBlockNumber$, blockLogs$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, }; } diff --git a/packages/store-sync/src/index.ts b/packages/store-sync/src/index.ts index 01c883d7e2..2e0771488d 100644 --- a/packages/store-sync/src/index.ts +++ b/packages/store-sync/src/index.ts @@ -1,4 +1,3 @@ -export * from "./blockLogsToStorage"; export * from "./common"; export * from "./createStoreSync"; export * from "./SyncStep"; diff --git a/packages/store-sync/src/isTableRegistrationLog.ts b/packages/store-sync/src/isTableRegistrationLog.ts new file mode 100644 index 0000000000..ca937f8030 --- /dev/null +++ b/packages/store-sync/src/isTableRegistrationLog.ts @@ -0,0 +1,7 @@ +import { StorageAdapterLog, schemasTableId } from "./common"; + +export function isTableRegistrationLog( + log: StorageAdapterLog +): log is StorageAdapterLog & { eventName: "StoreSetRecord" } { + return log.eventName === "StoreSetRecord" && log.args.tableId === schemasTableId; +} diff --git a/packages/store-sync/src/logToTable.test.ts b/packages/store-sync/src/logToTable.test.ts new file mode 100644 index 0000000000..53ec2a6a5e --- /dev/null +++ b/packages/store-sync/src/logToTable.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from "vitest"; +import { logToTable } from "./logToTable"; + +describe("logToTable", () => { + it("should convert a table registration log to table object", async () => { + expect( + logToTable({ + address: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", + eventName: "StoreSetRecord", + args: { + tableId: "0x6d756473746f726500000000000000005461626c657300000000000000000000", + keyTuple: ["0x6d756473746f726500000000000000005461626c657300000000000000000000"], + staticData: + "0x0060030220202000000000000000000000000000000000000000000000000000002001005f000000000000000000000000000000000000000000000000000000006003025f5f5fc4c40000000000000000000000000000000000000000000000", + encodedLengths: "0x000000000000000000000000000000000000022000000000a0000000000002c0", // "0x00000000000000000000000000000000000000a00000000220000000000002c0", + dynamicData: + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000077461626c654964000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000b6669656c644c61796f757400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096b6579536368656d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b76616c7565536368656d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012616269456e636f6465644b65794e616d657300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014616269456e636f6465644669656c644e616d6573000000000000000000000000", + }, + }) + ).toMatchInlineSnapshot(` + { + "address": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", + "keySchema": { + "tableId": "bytes32", + }, + "name": "Tables", + "namespace": "mudstore", + "tableId": "0x6d756473746f726500000000000000005461626c657300000000000000000000", + "valueSchema": { + "abiEncodedFieldNames": "bytes", + "abiEncodedKeyNames": "bytes", + "fieldLayout": "bytes32", + "keySchema": "bytes32", + "valueSchema": "bytes32", + }, + } + `); + }); +}); diff --git a/packages/store-sync/src/logToTable.ts b/packages/store-sync/src/logToTable.ts new file mode 100644 index 0000000000..72f7f01d47 --- /dev/null +++ b/packages/store-sync/src/logToTable.ts @@ -0,0 +1,37 @@ +import { hexToSchema, decodeValue } from "@latticexyz/protocol-parser"; +import { concatHex, decodeAbiParameters, parseAbiParameters } from "viem"; +import { StorageAdapterLog, Table, schemasTable } from "./common"; +import { hexToTableId } from "@latticexyz/common"; + +// TODO: add tableToLog + +export function logToTable(log: StorageAdapterLog & { eventName: "StoreSetRecord" }): Table { + const [tableId, ...otherKeys] = log.args.keyTuple; + if (otherKeys.length) { + console.warn("registerSchema event is expected to have only one key in key tuple, but got multiple", log); + } + + const table = hexToTableId(tableId); + + const value = decodeValue( + schemasTable.valueSchema, + concatHex([log.args.staticData, log.args.encodedLengths, log.args.dynamicData]) + ); + + const keySchema = hexToSchema(value.keySchema); + const valueSchema = hexToSchema(value.valueSchema); + + const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0]; + const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0]; + + const valueAbiTypes = [...valueSchema.staticFields, ...valueSchema.dynamicFields]; + + return { + address: log.address, + tableId, + namespace: table.namespace, + name: table.name, + keySchema: Object.fromEntries(keySchema.staticFields.map((abiType, i) => [keyNames[i], abiType])), + valueSchema: Object.fromEntries(valueAbiTypes.map((abiType, i) => [fieldNames[i], abiType])), + }; +} diff --git a/packages/store-sync/src/postgres/buildColumn.ts b/packages/store-sync/src/postgres/buildColumn.ts index 86ea59c268..d9ffa6db99 100644 --- a/packages/store-sync/src/postgres/buildColumn.ts +++ b/packages/store-sync/src/postgres/buildColumn.ts @@ -1,9 +1,10 @@ -import { AnyPgColumnBuilder, boolean, text } from "drizzle-orm/pg-core"; +import { boolean, text } from "drizzle-orm/pg-core"; import { SchemaAbiType } from "@latticexyz/schema-type"; import { assertExhaustive } from "@latticexyz/common/utils"; import { asAddress, asBigInt, asHex, asJson, asNumber } from "./columnTypes"; -export function buildColumn(name: string, schemaAbiType: SchemaAbiType): AnyPgColumnBuilder { +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function buildColumn(name: string, schemaAbiType: SchemaAbiType) { switch (schemaAbiType) { case "bool": return boolean(name); diff --git a/packages/store-sync/src/postgres/buildTable.test.ts b/packages/store-sync/src/postgres/buildTable.test.ts index 5d9d192a88..b13b991393 100644 --- a/packages/store-sync/src/postgres/buildTable.test.ts +++ b/packages/store-sync/src/postgres/buildTable.test.ts @@ -13,35 +13,131 @@ describe("buildTable", () => { expect(table).toMatchInlineSnapshot(` PgTable { + "__dynamicData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__dynamicData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, "__isDeleted": PgBoolean { + "columnType": "PgBoolean", "config": { + "columnType": "PgBoolean", + "dataType": "boolean", "default": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primaryKey": false, + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, + "dataType": "boolean", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primary": false, "table": [Circular], + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, "__key": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__key", "notNull": true, "primaryKey": true, + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__key", @@ -49,22 +145,35 @@ describe("buildTable", () => { "primary": true, "sqlName": "bytea", "table": [Circular], + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, "__lastUpdatedBlockNumber": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__lastUpdatedBlockNumber", "notNull": true, "primaryKey": false, + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__lastUpdatedBlockNumber", @@ -72,22 +181,71 @@ describe("buildTable", () => { "primary": false, "sqlName": "numeric", "table": [Circular], + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__staticData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, }, "addr": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "addr", "notNull": true, "primaryKey": false, + "uniqueName": "users_addr_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "addr", @@ -95,38 +253,63 @@ describe("buildTable", () => { "primary": false, "sqlName": "bytea", "table": [Circular], + "uniqueName": "users_addr_unique", + "uniqueType": undefined, }, "name": PgText { + "columnType": "PgText", "config": { + "columnType": "PgText", + "dataType": "string", "default": undefined, - "enumValues": [], + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "name", "notNull": true, "primaryKey": false, + "uniqueName": "users_name_unique", + "uniqueType": undefined, }, + "dataType": "string", "default": undefined, - "enumValues": [], - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "name", "notNull": true, "primary": false, "table": [Circular], + "uniqueName": "users_name_unique", + "uniqueType": undefined, }, "x": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "x", "notNull": true, "primaryKey": false, + "uniqueName": "users_x_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "x", @@ -134,22 +317,35 @@ describe("buildTable", () => { "primary": false, "sqlName": "integer", "table": [Circular], + "uniqueName": "users_x_unique", + "uniqueType": undefined, }, "y": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "y", "notNull": true, "primaryKey": false, + "uniqueName": "users_y_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "y", @@ -157,40 +353,138 @@ describe("buildTable", () => { "primary": false, "sqlName": "integer", "table": [Circular], + "uniqueName": "users_y_unique", + "uniqueType": undefined, }, Symbol(drizzle:Name): "users", Symbol(drizzle:OriginalName): "users", Symbol(drizzle:Schema): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test", Symbol(drizzle:Columns): { + "__dynamicData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__dynamicData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, "__isDeleted": PgBoolean { + "columnType": "PgBoolean", "config": { + "columnType": "PgBoolean", + "dataType": "boolean", "default": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primaryKey": false, + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, + "dataType": "boolean", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primary": false, "table": [Circular], + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, "__key": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__key", "notNull": true, "primaryKey": true, + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__key", @@ -198,22 +492,35 @@ describe("buildTable", () => { "primary": true, "sqlName": "bytea", "table": [Circular], + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, "__lastUpdatedBlockNumber": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__lastUpdatedBlockNumber", "notNull": true, "primaryKey": false, + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__lastUpdatedBlockNumber", @@ -221,22 +528,71 @@ describe("buildTable", () => { "primary": false, "sqlName": "numeric", "table": [Circular], + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__staticData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, }, "addr": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "addr", "notNull": true, "primaryKey": false, + "uniqueName": "users_addr_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "addr", @@ -244,38 +600,63 @@ describe("buildTable", () => { "primary": false, "sqlName": "bytea", "table": [Circular], + "uniqueName": "users_addr_unique", + "uniqueType": undefined, }, "name": PgText { + "columnType": "PgText", "config": { + "columnType": "PgText", + "dataType": "string", "default": undefined, - "enumValues": [], + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "name", "notNull": true, "primaryKey": false, + "uniqueName": "users_name_unique", + "uniqueType": undefined, }, + "dataType": "string", "default": undefined, - "enumValues": [], - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "name", "notNull": true, "primary": false, "table": [Circular], + "uniqueName": "users_name_unique", + "uniqueType": undefined, }, "x": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "x", "notNull": true, "primaryKey": false, + "uniqueName": "users_x_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "x", @@ -283,22 +664,35 @@ describe("buildTable", () => { "primary": false, "sqlName": "integer", "table": [Circular], + "uniqueName": "users_x_unique", + "uniqueType": undefined, }, "y": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "y", "notNull": true, "primaryKey": false, + "uniqueName": "users_y_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "y", @@ -306,6 +700,8 @@ describe("buildTable", () => { "primary": false, "sqlName": "integer", "table": [Circular], + "uniqueName": "users_y_unique", + "uniqueType": undefined, }, }, Symbol(drizzle:BaseName): "users", @@ -328,35 +724,131 @@ describe("buildTable", () => { expect(table).toMatchInlineSnapshot(` PgTable { + "__dynamicData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__dynamicData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, "__isDeleted": PgBoolean { + "columnType": "PgBoolean", "config": { + "columnType": "PgBoolean", + "dataType": "boolean", "default": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primaryKey": false, + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, + "dataType": "boolean", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primary": false, "table": [Circular], + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, "__key": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__key", "notNull": true, "primaryKey": true, + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__key", @@ -364,22 +856,35 @@ describe("buildTable", () => { "primary": true, "sqlName": "bytea", "table": [Circular], + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, "__lastUpdatedBlockNumber": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__lastUpdatedBlockNumber", "notNull": true, "primaryKey": false, + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__lastUpdatedBlockNumber", @@ -387,22 +892,71 @@ describe("buildTable", () => { "primary": false, "sqlName": "numeric", "table": [Circular], + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__staticData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, }, "addrs": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "addrs", "notNull": true, "primaryKey": false, + "uniqueName": "users_addrs_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "addrs", @@ -410,40 +964,138 @@ describe("buildTable", () => { "primary": false, "sqlName": "text", "table": [Circular], + "uniqueName": "users_addrs_unique", + "uniqueType": undefined, }, Symbol(drizzle:Name): "users", Symbol(drizzle:OriginalName): "users", Symbol(drizzle:Schema): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test", Symbol(drizzle:Columns): { + "__dynamicData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__dynamicData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___encodedLengths_unique", + "uniqueType": undefined, + }, "__isDeleted": PgBoolean { + "columnType": "PgBoolean", "config": { + "columnType": "PgBoolean", + "dataType": "boolean", "default": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primaryKey": false, + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, + "dataType": "boolean", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "name": "__isDeleted", "notNull": true, "primary": false, "table": [Circular], + "uniqueName": "users___isDeleted_unique", + "uniqueType": undefined, }, "__key": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__key", "notNull": true, "primaryKey": true, + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__key", @@ -451,22 +1103,35 @@ describe("buildTable", () => { "primary": true, "sqlName": "bytea", "table": [Circular], + "uniqueName": "users___key_unique", + "uniqueType": undefined, }, "__lastUpdatedBlockNumber": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "__lastUpdatedBlockNumber", "notNull": true, "primaryKey": false, + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "__lastUpdatedBlockNumber", @@ -474,22 +1139,71 @@ describe("buildTable", () => { "primary": false, "sqlName": "numeric", "table": [Circular], + "uniqueName": "users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": PgCustomColumn { + "columnType": "PgCustomColumn", + "config": { + "columnType": "PgCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "__staticData", + "notNull": false, + "primary": false, + "sqlName": "bytea", + "table": [Circular], + "uniqueName": "users___staticData_unique", + "uniqueType": undefined, }, "addrs": PgCustomColumn { + "columnType": "PgCustomColumn", "config": { + "columnType": "PgCustomColumn", "customTypeParams": { "dataType": [Function], "fromDriver": [Function], "toDriver": [Function], }, + "dataType": "custom", "default": undefined, "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, "name": "addrs", "notNull": true, "primaryKey": false, + "uniqueName": "users_addrs_unique", + "uniqueType": undefined, }, + "dataType": "custom", "default": undefined, - "hasDefault": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, "mapFrom": [Function], "mapTo": [Function], "name": "addrs", @@ -497,6 +1211,8 @@ describe("buildTable", () => { "primary": false, "sqlName": "text", "table": [Circular], + "uniqueName": "users_addrs_unique", + "uniqueType": undefined, }, }, Symbol(drizzle:BaseName): "users", diff --git a/packages/store-sync/src/postgres/buildTable.ts b/packages/store-sync/src/postgres/buildTable.ts index 0a9e24cd42..2fb7feb499 100644 --- a/packages/store-sync/src/postgres/buildTable.ts +++ b/packages/store-sync/src/postgres/buildTable.ts @@ -1,4 +1,4 @@ -import { AnyPgColumnBuilder, PgTableWithColumns, pgSchema } from "drizzle-orm/pg-core"; +import { PgColumnBuilderBase, PgTableWithColumns, pgSchema } from "drizzle-orm/pg-core"; import { buildColumn } from "./buildColumn"; import { Address, getAddress } from "viem"; import { transformSchemaName } from "./transformSchemaName"; @@ -6,13 +6,17 @@ import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser"; // TODO: convert camel case to snake case for DB storage? export const metaColumns = { - __key: buildColumn("__key", "bytes").notNull().primaryKey(), + __key: buildColumn("__key", "bytes").primaryKey(), + __staticData: buildColumn("__staticData", "bytes"), + __encodedLengths: buildColumn("__encodedLengths", "bytes"), + __dynamicData: buildColumn("__dynamicData", "bytes"), __lastUpdatedBlockNumber: buildColumn("__lastUpdatedBlockNumber", "uint256").notNull(), // TODO: last updated block hash? __isDeleted: buildColumn("__isDeleted", "bool").notNull(), -} as const satisfies Record; +} as const satisfies Record; type PgTableFromSchema = PgTableWithColumns<{ + dialect: "pg"; name: string; schema: string; columns: { diff --git a/packages/store-sync/src/postgres/cleanDatabase.ts b/packages/store-sync/src/postgres/cleanDatabase.ts index eff82aaf50..26d22ad31c 100644 --- a/packages/store-sync/src/postgres/cleanDatabase.ts +++ b/packages/store-sync/src/postgres/cleanDatabase.ts @@ -1,11 +1,10 @@ -import { PgDatabase } from "drizzle-orm/pg-core"; +import { PgDatabase, getTableConfig } from "drizzle-orm/pg-core"; import { buildInternalTables } from "./buildInternalTables"; import { getTables } from "./getTables"; import { buildTable } from "./buildTable"; -import { getSchema } from "./getSchema"; import { isDefined } from "@latticexyz/common/utils"; import { debug } from "./debug"; -import { getTableName, sql } from "drizzle-orm"; +import { sql } from "drizzle-orm"; import { pgDialect } from "./pgDialect"; // This intentionally just cleans up known schemas/tables/rows. We could drop the database but that's scary. @@ -16,7 +15,7 @@ export async function cleanDatabase(db: PgDatabase): Promise { const tables = (await getTables(db)).map(buildTable); - const schemaNames = [...new Set(tables.map(getSchema))].filter(isDefined); + const schemaNames = [...new Set(tables.map((table) => getTableConfig(table).schema))].filter(isDefined); for (const schemaName of schemaNames) { try { @@ -28,7 +27,8 @@ export async function cleanDatabase(db: PgDatabase): Promise { } for (const internalTable of Object.values(internalTables)) { - debug(`deleting all rows from ${getSchema(internalTable)}.${getTableName(internalTable)}`); + const tableConfig = getTableConfig(internalTable); + debug(`deleting all rows from ${tableConfig.schema}.${tableConfig.name}`); await db.delete(internalTable); } } diff --git a/packages/store-sync/src/postgres/columnTypes.ts b/packages/store-sync/src/postgres/columnTypes.ts index 97fd352080..da9d9761f8 100644 --- a/packages/store-sync/src/postgres/columnTypes.ts +++ b/packages/store-sync/src/postgres/columnTypes.ts @@ -1,11 +1,9 @@ -import { customType, PgCustomColumnBuilder } from "drizzle-orm/pg-core"; -import { ColumnBuilderBaseConfig } from "drizzle-orm"; +import { customType } from "drizzle-orm/pg-core"; import superjson from "superjson"; import { Address, ByteArray, bytesToHex, getAddress, Hex, hexToBytes } from "viem"; -export const asJson = ( - name: string -): PgCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const asJson = (name: string) => customType<{ data: TData; driverData: string }>({ dataType() { // TODO: move to json column type? if we do, we'll prob wanna choose something other than superjson since it adds one level of depth (json/meta keys) @@ -19,10 +17,8 @@ export const asJson = ( }, })(name); -export const asNumber = ( - name: string, - columnType: string -): PgCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const asNumber = (name: string, columnType: string) => customType<{ data: number; driverData: string }>({ dataType() { return columnType; @@ -35,10 +31,8 @@ export const asNumber = ( }, })(name); -export const asBigInt = ( - name: string, - columnType: string -): PgCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const asBigInt = (name: string, columnType: string) => customType<{ data: bigint; driverData: string }>({ dataType() { return columnType; @@ -51,9 +45,8 @@ export const asBigInt = ( }, })(name); -export const asHex = ( - name: string -): PgCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const asHex = (name: string) => customType<{ data: Hex; driverData: ByteArray }>({ dataType() { return "bytea"; @@ -66,9 +59,8 @@ export const asHex = ( }, })(name); -export const asAddress = ( - name: string -): PgCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const asAddress = (name: string) => customType<{ data: Address; driverData: ByteArray }>({ dataType() { return "bytea"; diff --git a/packages/store-sync/src/postgres/getSchema.test.ts b/packages/store-sync/src/postgres/getSchema.test.ts deleted file mode 100644 index c0c9a045a5..0000000000 --- a/packages/store-sync/src/postgres/getSchema.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { getSchema } from "./getSchema"; -import { pgTable, pgSchema } from "drizzle-orm/pg-core"; - -// Test to make sure getSchema matches drizzle internals. May need to update getSchema if these tests start failing. Hopefully by then, drizzle will have exposed their own getSchema method. - -describe("getSchema", () => { - it("should return schema if set", async () => { - expect(getSchema(pgTable("no schema", {}))).toBeUndefined(); - expect(getSchema(pgSchema("some schema").table("with schema", {}))).toBe("some schema"); - }); -}); diff --git a/packages/store-sync/src/postgres/getSchema.ts b/packages/store-sync/src/postgres/getSchema.ts deleted file mode 100644 index 240046fcd4..0000000000 --- a/packages/store-sync/src/postgres/getSchema.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PgTable } from "drizzle-orm/pg-core"; - -// TODO: PR to drizzle to expose `getSchema` like `getTableName` -export function getSchema(table: PgTable): string | undefined { - return (table as any)[Symbol.for("drizzle:Schema")]; -} diff --git a/packages/store-sync/src/postgres/getTableKey.ts b/packages/store-sync/src/postgres/getTableKey.ts index 423830ce23..3c58cae80e 100644 --- a/packages/store-sync/src/postgres/getTableKey.ts +++ b/packages/store-sync/src/postgres/getTableKey.ts @@ -1,6 +1,8 @@ import { getAddress } from "viem"; import { Table } from "../common"; +import { hexToTableId } from "@latticexyz/common"; -export function getTableKey(table: Pick): string { - return `${getAddress(table.address)}:${table.namespace}:${table.name}`; +export function getTableKey({ address, tableId }: Pick): string { + const { namespace, name } = hexToTableId(tableId); + return `${getAddress(address)}:${namespace}:${name}`; } diff --git a/packages/store-sync/src/postgres/postgresStorage.test.ts b/packages/store-sync/src/postgres/postgresStorage.test.ts index 47c29b5ee9..341546978a 100644 --- a/packages/store-sync/src/postgres/postgresStorage.test.ts +++ b/packages/store-sync/src/postgres/postgresStorage.test.ts @@ -1,15 +1,29 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { DefaultLogger } from "drizzle-orm"; +import { DefaultLogger, eq } from "drizzle-orm"; import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; -import { createPublicClient, http } from "viem"; +import { Hex, RpcLog, createPublicClient, decodeEventLog, formatLog, http } from "viem"; import { foundry } from "viem/chains"; -import { blockLogsToStorage } from "../blockLogsToStorage"; import * as transformSchemaNameExports from "./transformSchemaName"; import { getTables } from "./getTables"; import { PostgresStorageAdapter, postgresStorage } from "./postgresStorage"; import { buildTable } from "./buildTable"; +import { groupLogsByBlockNumber } from "@latticexyz/block-logs-stream"; +import { storeEventsAbi } from "@latticexyz/store"; import { StoreEventsLog } from "../common"; +import worldRpcLogs from "../../../../test-data/world-logs.json"; + +const blocks = groupLogsByBlockNumber( + worldRpcLogs.map((log) => { + const { eventName, args } = decodeEventLog({ + abi: storeEventsAbi, + data: log.data as Hex, + topics: log.topics as [Hex, ...Hex[]], + strict: true, + }); + return formatLog(log as any as RpcLog, { args, eventName: eventName as string }) as StoreEventsLog; + }) +); vi.spyOn(transformSchemaNameExports, "transformSchemaName").mockImplementation( (schemaName) => `${process.pid}_${process.env.VITEST_POOL_ID}__${schemaName}` @@ -33,104 +47,79 @@ describe("postgresStorage", async () => { }); it("should create tables and data from block log", async () => { - await blockLogsToStorage(storageAdapter)({ - blockNumber: 5448n, - logs: [ - { - address: "0x5fbdb2315678afecb367f032d93f642f64180aa3", - topics: ["0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46"], - data: "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e76656e746f72790000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", - blockHash: "0x4ad3752c86f900332e0d2d8903480e7206747d233586574d16f006eebdb5138b", - blockNumber: 2n, - transactionHash: "0xaa54bf18053cce5d4d2906538a60cb1d9958cc3c10c34b5f9fdc92fe6a6abab4", - transactionIndex: 16, - logIndex: 54, - removed: false, - args: { - tableId: "0x000000000000000000000000000000005265736f757263655479706500000000", - keyTuple: ["0x00000000000000000000000000000000496e76656e746f727900000000000000"], - schemaIndex: 0, - data: "0x02", - }, - eventName: "StoreSetField", - }, - { - address: "0x5fbdb2315678afecb367f032d93f642f64180aa3", - topics: ["0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32"], - data: "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e76656e746f72790000000000000000000000000000000000000000000000000000000000000000000000000002800004010004000000000000000000000000000000000000000000000000000000001c030061030300000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000001600000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000056f776e657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046974656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6974656d56617269616e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006616d6f756e740000000000000000000000000000000000000000000000000000", - blockHash: "0x4ad3752c86f900332e0d2d8903480e7206747d233586574d16f006eebdb5138b", - blockNumber: 2n, - transactionHash: "0xaa54bf18053cce5d4d2906538a60cb1d9958cc3c10c34b5f9fdc92fe6a6abab4", - transactionIndex: 16, - logIndex: 55, - removed: false, - args: { - tableId: "0x6d756473746f726500000000000000005461626c657300000000000000000000", - keyTuple: ["0x00000000000000000000000000000000496e76656e746f727900000000000000"], - data: "0x0004010004000000000000000000000000000000000000000000000000000000001c030061030300000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000001600000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000056f776e657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046974656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6974656d56617269616e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006616d6f756e740000000000000000000000000000000000000000000000000000", - }, - eventName: "StoreSetRecord", - }, - ] satisfies StoreEventsLog[], - }); + for (const block of blocks) { + await storageAdapter.storageAdapter(block); + } expect(await db.select().from(storageAdapter.internalTables.chain)).toMatchInlineSnapshot(` [ { "chainId": 31337, "lastError": null, - "lastUpdatedBlockNumber": 5448n, + "lastUpdatedBlockNumber": 6n, "schemaVersion": 1, }, ] `); - expect(await db.select().from(storageAdapter.internalTables.tables)).toMatchInlineSnapshot(` + expect( + await db + .select() + .from(storageAdapter.internalTables.tables) + .where(eq(storageAdapter.internalTables.tables.name, "NumberList")) + ).toMatchInlineSnapshot(` [ { "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "key": "0x5FbDB2315678afecb367f032d93F642f64180aa3::Inventory", - "keySchema": { - "item": "uint32", - "itemVariant": "uint32", - "owner": "address", - }, + "key": "0x5FbDB2315678afecb367f032d93F642f64180aa3::NumberList", + "keySchema": {}, "lastError": null, - "lastUpdatedBlockNumber": 5448n, - "name": "Inventory", + "lastUpdatedBlockNumber": 6n, + "name": "NumberList", "namespace": "", "schemaVersion": 1, - "tableId": "0x00000000000000000000000000000000496e76656e746f727900000000000000", + "tableId": "0x000000000000000000000000000000004e756d6265724c697374000000000000", "valueSchema": { - "amount": "uint32", + "value": "uint32[]", }, }, ] `); - const tables = await getTables(db, []); + const tables = (await getTables(db)).filter((table) => table.name === "NumberList"); expect(tables).toMatchInlineSnapshot(` [ { "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "keySchema": { - "item": "uint32", - "itemVariant": "uint32", - "owner": "address", - }, - "lastUpdatedBlockNumber": 5448n, - "name": "Inventory", + "keySchema": {}, + "lastUpdatedBlockNumber": 6n, + "name": "NumberList", "namespace": "", - "tableId": "0x00000000000000000000000000000000496e76656e746f727900000000000000", + "tableId": "0x000000000000000000000000000000004e756d6265724c697374000000000000", "valueSchema": { - "amount": "uint32", + "value": "uint32[]", }, }, ] `); const sqlTable = buildTable(tables[0]); - expect(await db.select().from(sqlTable)).toMatchInlineSnapshot("[]"); + expect(await db.select().from(sqlTable)).toMatchInlineSnapshot(` + [ + { + "__dynamicData": "0x000001a400000045", + "__encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008", + "__isDeleted": false, + "__key": "0x", + "__lastUpdatedBlockNumber": 6n, + "__staticData": null, + "value": [ + 420, + 69, + ], + }, + ] + `); await storageAdapter.cleanUp(); }); diff --git a/packages/store-sync/src/postgres/postgresStorage.ts b/packages/store-sync/src/postgres/postgresStorage.ts index 1937a9796a..7e61e5f5d7 100644 --- a/packages/store-sync/src/postgres/postgresStorage.ts +++ b/packages/store-sync/src/postgres/postgresStorage.ts @@ -1,21 +1,24 @@ -import { PublicClient, concatHex, encodeAbiParameters } from "viem"; +import { Hex, PublicClient, concatHex } from "viem"; import { PgDatabase, QueryResultHKT } from "drizzle-orm/pg-core"; import { eq, inArray } from "drizzle-orm"; import { buildTable } from "./buildTable"; -import { schemaToDefaults } from "../schemaToDefaults"; import { StoreConfig } from "@latticexyz/store"; import { debug } from "./debug"; import { buildInternalTables } from "./buildInternalTables"; import { getTables } from "./getTables"; import { schemaVersion } from "./schemaVersion"; -import { tableIdToHex } from "@latticexyz/common"; +import { hexToTableId, spliceHex, tableIdToHex } from "@latticexyz/common"; import { setupTables } from "./setupTables"; import { getTableKey } from "./getTableKey"; -import { StorageAdapter } from "../common"; +import { StorageAdapter, StorageAdapterBlock } from "../common"; +import { isTableRegistrationLog } from "../isTableRegistrationLog"; +import { logToTable } from "../logToTable"; +import { decodeKey, decodeValueArgs } from "@latticexyz/protocol-parser"; // Currently assumes one DB per chain ID -export type PostgresStorageAdapter = StorageAdapter & { +export type PostgresStorageAdapter = { + storageAdapter: StorageAdapter; internalTables: ReturnType; cleanUp: () => Promise; }; @@ -27,7 +30,7 @@ export async function postgresStorage database: PgDatabase; publicClient: PublicClient; config?: TConfig; -}): Promise> { +}): Promise { const cleanUp: (() => Promise)[] = []; const chainId = publicClient.chain?.id ?? (await publicClient.getChainId()); @@ -35,153 +38,229 @@ export async function postgresStorage const internalTables = buildInternalTables(); cleanUp.push(await setupTables(database, Object.values(internalTables))); - const storageAdapter = { - async registerTables({ blockNumber, tables }) { - const sqlTables = tables.map((table) => - buildTable({ - address: table.address, - namespace: table.namespace, - name: table.name, - keySchema: table.keySchema, - valueSchema: table.valueSchema, - }) - ); + async function postgresStorageAdapter({ blockNumber, logs }: StorageAdapterBlock): Promise { + const newTables = logs.filter(isTableRegistrationLog).map(logToTable); + const newSqlTables = newTables.map((table) => + buildTable({ + address: table.address, + namespace: table.namespace, + name: table.name, + keySchema: table.keySchema, + valueSchema: table.valueSchema, + }) + ); - cleanUp.push(await setupTables(database, sqlTables)); + cleanUp.push(await setupTables(database, newSqlTables)); - await database.transaction(async (tx) => { - for (const table of tables) { - await tx - .insert(internalTables.tables) - .values({ - schemaVersion, - key: getTableKey(table), - address: table.address, - tableId: tableIdToHex(table.namespace, table.name), - namespace: table.namespace, - name: table.name, - keySchema: table.keySchema, - valueSchema: table.valueSchema, - lastUpdatedBlockNumber: blockNumber, - }) - .onConflictDoNothing() - .execute(); - } - }); - }, - async getTables({ tables }) { - // TODO: fetch any missing schemas from RPC - // TODO: cache schemas in memory? - return getTables(database, tables.map(getTableKey)); - }, - async storeOperations({ blockNumber, operations }) { - // This is currently parallelized per world (each world has its own database). - // This may need to change if we decide to put multiple worlds into one DB (e.g. a namespace per world, but all under one DB). - // If so, we'll probably want to wrap the entire block worth of operations in a transaction. + await database.transaction(async (tx) => { + for (const table of newTables) { + await tx + .insert(internalTables.tables) + .values({ + schemaVersion, + key: getTableKey(table), + address: table.address, + tableId: tableIdToHex(table.namespace, table.name), + namespace: table.namespace, + name: table.name, + keySchema: table.keySchema, + valueSchema: table.valueSchema, + lastUpdatedBlockNumber: blockNumber, + }) + .onConflictDoNothing() + .execute(); + } + }); + + const tables = await getTables( + database, + logs.map((log) => getTableKey({ address: log.address, tableId: log.args.tableId })) + ); + + // This is currently parallelized per world (each world has its own database). + // This may need to change if we decide to put multiple worlds into one DB (e.g. a namespace per world, but all under one DB). + // If so, we'll probably want to wrap the entire block worth of operations in a transaction. - const tables = await getTables(database, operations.map(getTableKey)); + await database.transaction(async (tx) => { + const tablesWithOperations = tables.filter((table) => + logs.some((log) => getTableKey({ address: log.address, tableId: log.args.tableId }) === getTableKey(table)) + ); + if (tablesWithOperations.length) { + await tx + .update(internalTables.tables) + .set({ lastUpdatedBlockNumber: blockNumber }) + .where(inArray(internalTables.tables.key, [...new Set(tablesWithOperations.map(getTableKey))])) + .execute(); + } - await database.transaction(async (tx) => { - const tablesWithOperations = tables.filter((table) => - operations.some((op) => getTableKey(op) === getTableKey(table)) + for (const log of logs) { + const table = tables.find( + (table) => getTableKey(table) === getTableKey({ address: log.address, tableId: log.args.tableId }) ); - if (tablesWithOperations.length) { - await tx - .update(internalTables.tables) - .set({ lastUpdatedBlockNumber: blockNumber }) - .where(inArray(internalTables.tables.key, [...new Set(tablesWithOperations.map(getTableKey))])) - .execute(); + if (!table) { + const { namespace, name } = hexToTableId(log.args.tableId); + debug(`table ${namespace}:${name} not found, skipping log`, log); + continue; } - for (const operation of operations) { - const table = tables.find((table) => getTableKey(table) === getTableKey(operation)); - if (!table) { - debug(`table ${operation.namespace}:${operation.name} not found, skipping operation`, operation); - continue; - } - - const sqlTable = buildTable(table); - const key = concatHex( - Object.entries(table.keySchema).map(([keyName, type]) => - encodeAbiParameters([{ type }], [operation.key[keyName]]) - ) - ); - - if (operation.type === "SetRecord") { - debug("SetRecord", operation); - await tx - .insert(sqlTable) - .values({ - __key: key, + const sqlTable = buildTable(table); + const uniqueKey = concatHex(log.args.keyTuple as Hex[]); + const key = decodeKey(table.keySchema, log.args.keyTuple); + + debug(log.eventName, log); + + if (log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord") { + const value = decodeValueArgs(table.valueSchema, log.args); + debug("upserting record", { + namespace: table.namespace, + name: table.name, + key, + value, + }); + await tx + .insert(sqlTable) + .values({ + __key: uniqueKey, + __staticData: log.args.staticData, + __encodedLengths: log.args.encodedLengths, + __dynamicData: log.args.dynamicData, + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: false, + ...key, + ...value, + }) + .onConflictDoUpdate({ + target: sqlTable.__key, + set: { + __staticData: log.args.staticData, + __encodedLengths: log.args.encodedLengths, + __dynamicData: log.args.dynamicData, __lastUpdatedBlockNumber: blockNumber, __isDeleted: false, - ...operation.key, - ...operation.value, - }) - .onConflictDoUpdate({ - target: sqlTable.__key, - set: { - __lastUpdatedBlockNumber: blockNumber, - __isDeleted: false, - ...operation.value, - }, - }) - .execute(); - } else if (operation.type === "SetField") { - debug("SetField", operation); - await tx - .insert(sqlTable) - .values({ - __key: key, + ...value, + }, + }) + .execute(); + } else if (log.eventName === "StoreSpliceStaticData") { + // TODO: verify that this returns what we expect (doesn't error/undefined on no record) + const previousValue = (await tx.select().from(sqlTable).where(eq(sqlTable.__key, uniqueKey)).execute())[0]; + const previousStaticData = (previousValue?.__staticData as Hex) ?? "0x"; + const newStaticData = spliceHex(previousStaticData, log.args.start, log.args.deleteCount, log.args.data); + const newValue = decodeValueArgs(table.valueSchema, { + staticData: newStaticData, + encodedLengths: (previousValue?.__encodedLengths as Hex) ?? "0x", + dynamicData: (previousValue?.__dynamicData as Hex) ?? "0x", + }); + debug("upserting record via splice static", { + namespace: table.namespace, + name: table.name, + key, + previousStaticData, + newStaticData, + previousValue, + newValue, + }); + await tx + .insert(sqlTable) + .values({ + __key: uniqueKey, + __staticData: newStaticData, + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: false, + ...key, + ...newValue, + }) + .onConflictDoUpdate({ + target: sqlTable.__key, + set: { + __staticData: newStaticData, __lastUpdatedBlockNumber: blockNumber, __isDeleted: false, - ...operation.key, - ...schemaToDefaults(table.valueSchema), - [operation.fieldName]: operation.fieldValue, - }) - .onConflictDoUpdate({ - target: sqlTable.__key, - set: { - __lastUpdatedBlockNumber: blockNumber, - __isDeleted: false, - [operation.fieldName]: operation.fieldValue, - }, - }) - .execute(); - } else if (operation.type === "DeleteRecord") { - // TODO: should we upsert so we at least have a DB record of when a thing was created/deleted within the same block? - debug("DeleteRecord", operation); - await tx - .update(sqlTable) - .set({ + ...newValue, + }, + }) + .execute(); + } else if (log.eventName === "StoreSpliceDynamicData") { + // TODO: verify that this returns what we expect (doesn't error/undefined on no record) + const previousValue = (await tx.select().from(sqlTable).where(eq(sqlTable.__key, uniqueKey)).execute())[0]; + const previousDynamicData = (previousValue?.__dynamicData as Hex) ?? "0x"; + const newDynamicData = spliceHex(previousDynamicData, log.args.start, log.args.deleteCount, log.args.data); + const newValue = decodeValueArgs(table.valueSchema, { + staticData: (previousValue?.__staticData as Hex) ?? "0x", + // TODO: handle unchanged encoded lengths + encodedLengths: log.args.encodedLengths, + dynamicData: newDynamicData, + }); + debug("upserting record via splice dynamic", { + namespace: table.namespace, + name: table.name, + key, + previousDynamicData, + newDynamicData, + previousValue, + newValue, + }); + await tx + .insert(sqlTable) + .values({ + __key: uniqueKey, + // TODO: handle unchanged encoded lengths + __encodedLengths: log.args.encodedLengths, + __dynamicData: newDynamicData, + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: false, + ...key, + ...newValue, + }) + .onConflictDoUpdate({ + target: sqlTable.__key, + set: { + // TODO: handle unchanged encoded lengths + __encodedLengths: log.args.encodedLengths, + __dynamicData: newDynamicData, __lastUpdatedBlockNumber: blockNumber, - __isDeleted: true, - }) - .where(eq(sqlTable.__key, key)) - .execute(); - } + __isDeleted: false, + ...newValue, + }, + }) + .execute(); + } else if (log.eventName === "StoreDeleteRecord") { + // TODO: should we upsert so we at least have a DB record of when a thing was created/deleted within the same block? + debug("deleting record", { + namespace: table.namespace, + name: table.name, + key, + }); + await tx + .update(sqlTable) + .set({ + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: true, + }) + .where(eq(sqlTable.__key, uniqueKey)) + .execute(); } + } - await tx - .insert(internalTables.chain) - .values({ - schemaVersion, - chainId, + await tx + .insert(internalTables.chain) + .values({ + schemaVersion, + chainId, + lastUpdatedBlockNumber: blockNumber, + }) + .onConflictDoUpdate({ + target: [internalTables.chain.schemaVersion, internalTables.chain.chainId], + set: { lastUpdatedBlockNumber: blockNumber, - }) - .onConflictDoUpdate({ - target: [internalTables.chain.schemaVersion, internalTables.chain.chainId], - set: { - lastUpdatedBlockNumber: blockNumber, - }, - }) - .execute(); - }); - }, - } as StorageAdapter; + }, + }) + .execute(); + }); + } return { - ...storageAdapter, + storageAdapter: postgresStorageAdapter, internalTables, cleanUp: async (): Promise => { for (const fn of cleanUp) { diff --git a/packages/store-sync/src/postgres/setupTables.ts b/packages/store-sync/src/postgres/setupTables.ts index 1ddcedaa6f..d15f92ce54 100644 --- a/packages/store-sync/src/postgres/setupTables.ts +++ b/packages/store-sync/src/postgres/setupTables.ts @@ -1,7 +1,6 @@ -import { AnyPgColumn, PgTableWithColumns, PgDatabase } from "drizzle-orm/pg-core"; -import { getTableColumns, getTableName, sql } from "drizzle-orm"; +import { AnyPgColumn, PgTableWithColumns, PgDatabase, getTableConfig } from "drizzle-orm/pg-core"; +import { getTableColumns, sql } from "drizzle-orm"; import { ColumnDataType } from "kysely"; -import { getSchema } from "./getSchema"; import { isDefined } from "@latticexyz/common/utils"; import { debug } from "./debug"; import { pgDialect } from "./pgDialect"; @@ -13,7 +12,7 @@ export async function setupTables( // TODO: add table to internal tables here // TODO: look up table schema and check if it matches expected schema, drop if not - const schemaNames = [...new Set(tables.map(getSchema).filter(isDefined))]; + const schemaNames = [...new Set(tables.map((table) => getTableConfig(table).schema).filter(isDefined))]; await db.transaction(async (tx) => { for (const schemaName of schemaNames) { @@ -22,12 +21,10 @@ export async function setupTables( } for (const table of tables) { - const schemaName = getSchema(table); - const scopedDb = schemaName ? pgDialect.withSchema(schemaName) : pgDialect; + const tableConfig = getTableConfig(table); + const scopedDb = tableConfig.schema ? pgDialect.withSchema(tableConfig.schema) : pgDialect; - const tableName = getTableName(table); - - let query = scopedDb.schema.createTable(tableName).ifNotExists(); + let query = scopedDb.schema.createTable(tableConfig.name).ifNotExists(); const columns = Object.values(getTableColumns(table)) as AnyPgColumn[]; for (const column of columns) { @@ -44,10 +41,10 @@ export async function setupTables( const primaryKeys = columns.filter((column) => column.primary).map((column) => column.name); if (primaryKeys.length) { - query = query.addPrimaryKeyConstraint(`${tableName}__pk`, primaryKeys as any); + query = query.addPrimaryKeyConstraint(`${tableConfig.name}__pk`, primaryKeys as any); } - debug(`creating table ${tableName} in namespace ${schemaName}`); + debug(`creating table ${tableConfig.name} in namespace ${tableConfig.schema}`); await tx.execute(sql.raw(query.compile().sql)); } }); diff --git a/packages/store-sync/src/postgres/syncToPostgres.ts b/packages/store-sync/src/postgres/syncToPostgres.ts index be649f4e78..1042ab2832 100644 --- a/packages/store-sync/src/postgres/syncToPostgres.ts +++ b/packages/store-sync/src/postgres/syncToPostgres.ts @@ -14,7 +14,7 @@ type SyncToPostgresOptions = SyncOpti startSync?: boolean; }; -type SyncToPostgresResult = SyncResult & { +type SyncToPostgresResult = SyncResult & { stopSync: () => void; }; @@ -34,9 +34,10 @@ export async function syncToPostgres( indexerUrl, initialState, startSync = true, -}: SyncToPostgresOptions): Promise> { +}: SyncToPostgresOptions): Promise { + const { storageAdapter } = await postgresStorage({ database, publicClient, config }); const storeSync = await createStoreSync({ - storageAdapter: await postgresStorage({ database, publicClient, config }), + storageAdapter, config, address, publicClient, @@ -46,7 +47,7 @@ export async function syncToPostgres( initialState, }); - const sub = startSync ? storeSync.blockStorageOperations$.subscribe() : null; + const sub = startSync ? storeSync.storedBlockLogs$.subscribe() : null; const stopSync = (): void => { sub?.unsubscribe(); }; diff --git a/packages/store-sync/src/recs/common.test-d.ts b/packages/store-sync/src/recs/common.test-d.ts index bf1e450859..a2f81ff7a4 100644 --- a/packages/store-sync/src/recs/common.test-d.ts +++ b/packages/store-sync/src/recs/common.test-d.ts @@ -4,18 +4,27 @@ import storeConfig from "@latticexyz/store/mud.config"; import { ConfigToRecsComponents } from "./common"; describe("ConfigToRecsComponents", () => { - expectTypeOf["StoreMetadata"]>().toEqualTypeOf< + expectTypeOf["Tables"]>().toEqualTypeOf< Component< { - tableName: RecsType.String; + keySchema: RecsType.String; + valueSchema: RecsType.String; + abiEncodedKeyNames: RecsType.String; abiEncodedFieldNames: RecsType.String; }, { - componentName: "StoreMetadata"; + componentName: "Tables"; // TODO: fix config namespace so it comes back as a const - tableName: `${string}:StoreMetadata`; - keySchema: { tableId: "bytes32" }; - valueSchema: { tableName: "string"; abiEncodedFieldNames: "bytes" }; + tableName: `${string}:Tables`; + keySchema: { + tableId: "bytes32"; + }; + valueSchema: { + keySchema: "bytes32"; + valueSchema: "bytes32"; + abiEncodedKeyNames: "bytes"; + abiEncodedFieldNames: "bytes"; + }; } > >(); diff --git a/packages/store-sync/src/recs/common.ts b/packages/store-sync/src/recs/common.ts index d33aeb5b93..09622f2fe8 100644 --- a/packages/store-sync/src/recs/common.ts +++ b/packages/store-sync/src/recs/common.ts @@ -14,6 +14,10 @@ export type StoreComponentMetadata = RecsMetadata & { export type ConfigToRecsComponents = { [tableName in keyof TConfig["tables"] & string]: RecsComponent< { + __staticData: RecsType.OptionalString; + __encodedLengths: RecsType.OptionalString; + __dynamicData: RecsType.OptionalString; + } & { [fieldName in keyof TConfig["tables"][tableName]["valueSchema"] & string]: RecsType & SchemaAbiTypeToRecsType; }, diff --git a/packages/store-sync/src/recs/configToRecsComponents.ts b/packages/store-sync/src/recs/configToRecsComponents.ts index 1d5b9e760e..9243f846c9 100644 --- a/packages/store-sync/src/recs/configToRecsComponents.ts +++ b/packages/store-sync/src/recs/configToRecsComponents.ts @@ -1,7 +1,7 @@ import { StoreConfig } from "@latticexyz/store"; import { SchemaAbiType } from "@latticexyz/schema-type"; import { tableIdToHex } from "@latticexyz/common"; -import { World, defineComponent } from "@latticexyz/recs"; +import { World, defineComponent, Type } from "@latticexyz/recs"; import { ConfigToRecsComponents } from "./common"; import { schemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType"; @@ -14,12 +14,17 @@ export function configToRecsComponents( tableName, defineComponent( world, - Object.fromEntries( - Object.entries(table.valueSchema).map(([fieldName, schemaAbiType]) => [ - fieldName, - schemaAbiTypeToRecsType[schemaAbiType as SchemaAbiType], - ]) - ), + { + ...Object.fromEntries( + Object.entries(table.valueSchema).map(([fieldName, schemaAbiType]) => [ + fieldName, + schemaAbiTypeToRecsType[schemaAbiType as SchemaAbiType], + ]) + ), + __staticData: Type.OptionalString, + __encodedLengths: Type.OptionalString, + __dynamicData: Type.OptionalString, + }, { id: tableIdToHex(config.namespace, tableName), metadata: { diff --git a/packages/store-sync/src/recs/decodeEntity.ts b/packages/store-sync/src/recs/decodeEntity.ts index 90167726f0..ba2c084a7f 100644 --- a/packages/store-sync/src/recs/decodeEntity.ts +++ b/packages/store-sync/src/recs/decodeEntity.ts @@ -1,7 +1,7 @@ import { Entity } from "@latticexyz/recs"; import { Hex, decodeAbiParameters } from "viem"; -import { KeySchema, SchemaToPrimitives } from "@latticexyz/protocol-parser"; import { entityToHexKeyTuple } from "./entityToHexKeyTuple"; +import { KeySchema, SchemaToPrimitives } from "@latticexyz/protocol-parser"; export function decodeEntity( keySchema: TKeySchema, diff --git a/packages/store-sync/src/recs/encodeEntity.ts b/packages/store-sync/src/recs/encodeEntity.ts index e0e155371c..bef8a33c1b 100644 --- a/packages/store-sync/src/recs/encodeEntity.ts +++ b/packages/store-sync/src/recs/encodeEntity.ts @@ -1,7 +1,7 @@ import { Entity } from "@latticexyz/recs"; import { encodeAbiParameters } from "viem"; -import { KeySchema, SchemaToPrimitives } from "@latticexyz/protocol-parser"; import { hexKeyTupleToEntity } from "./hexKeyTupleToEntity"; +import { KeySchema, SchemaToPrimitives } from "@latticexyz/protocol-parser"; export function encodeEntity( keySchema: TKeySchema, diff --git a/packages/store-sync/src/recs/getTableEntity.ts b/packages/store-sync/src/recs/getTableEntity.ts index 83b746e89b..5b92368da3 100644 --- a/packages/store-sync/src/recs/getTableEntity.ts +++ b/packages/store-sync/src/recs/getTableEntity.ts @@ -1,4 +1,4 @@ -import { Address, Hex, getAddress, stringToHex } from "viem"; +import { stringToHex } from "viem"; import { Table } from "../common"; import { Entity } from "@latticexyz/recs"; import { encodeEntity } from "./encodeEntity"; diff --git a/packages/store-sync/src/recs/recsStorage.test.ts b/packages/store-sync/src/recs/recsStorage.test.ts index 3b8e401444..4bb747b0dc 100644 --- a/packages/store-sync/src/recs/recsStorage.test.ts +++ b/packages/store-sync/src/recs/recsStorage.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from "vitest"; -import { blockLogsToStorage } from "../blockLogsToStorage"; import { recsStorage } from "./recsStorage"; import { createWorld, getComponentEntities, getComponentValue } from "@latticexyz/recs"; import mudConfig from "../../../../e2e/packages/contracts/mud.config"; @@ -10,15 +9,18 @@ import { singletonEntity } from "./singletonEntity"; import { RpcLog, formatLog, decodeEventLog, Hex } from "viem"; import { storeEventsAbi } from "@latticexyz/store"; -const worldLogs = worldRpcLogs.map((log) => { - const { eventName, args } = decodeEventLog({ - abi: storeEventsAbi, - data: log.data as Hex, - topics: log.topics as [Hex, ...Hex[]], - strict: true, - }); - return formatLog(log as any as RpcLog, { args, eventName: eventName as string }) as StoreEventsLog; -}); +// TODO: make test-data a proper package and export this +const blocks = groupLogsByBlockNumber( + worldRpcLogs.map((log) => { + const { eventName, args } = decodeEventLog({ + abi: storeEventsAbi, + data: log.data as Hex, + topics: log.topics as [Hex, ...Hex[]], + strict: true, + }); + return formatLog(log as any as RpcLog, { args, eventName: eventName as string }) as StoreEventsLog; + }) +); describe("recsStorage", () => { it("creates components", async () => { @@ -33,8 +35,9 @@ describe("recsStorage", () => { const world = createWorld(); const { storageAdapter, components } = recsStorage({ world, config: mudConfig }); - const blocks = groupLogsByBlockNumber(worldLogs); - await Promise.all(blocks.map(async (block) => await blockLogsToStorage(storageAdapter)(block))); + for (const block of blocks) { + await storageAdapter(block); + } expect(Array.from(getComponentEntities(components.NumberList))).toMatchInlineSnapshot(` [ @@ -44,6 +47,9 @@ describe("recsStorage", () => { expect(getComponentValue(components.NumberList, singletonEntity)).toMatchInlineSnapshot(` { + "__dynamicData": "0x000001a400000045", + "__encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008", + "__staticData": undefined, "value": [ 420, 69, diff --git a/packages/store-sync/src/recs/recsStorage.ts b/packages/store-sync/src/recs/recsStorage.ts index 9debe2652f..457eb88793 100644 --- a/packages/store-sync/src/recs/recsStorage.ts +++ b/packages/store-sync/src/recs/recsStorage.ts @@ -1,21 +1,16 @@ import { StoreConfig } from "@latticexyz/store"; import { debug } from "./debug"; -import { - ComponentValue, - World as RecsWorld, - getComponentValue, - removeComponent, - setComponent, - updateComponent, -} from "@latticexyz/recs"; -import { isDefined } from "@latticexyz/common/utils"; -import { schemaToDefaults } from "../schemaToDefaults"; +import { World as RecsWorld, getComponentValue, hasComponent, removeComponent, setComponent } from "@latticexyz/recs"; import { defineInternalComponents } from "./defineInternalComponents"; import { getTableEntity } from "./getTableEntity"; +import { hexToTableId, spliceHex } from "@latticexyz/common"; +import { decodeValueArgs } from "@latticexyz/protocol-parser"; +import { Hex } from "viem"; +import { isTableRegistrationLog } from "../isTableRegistrationLog"; +import { logToTable } from "../logToTable"; +import { hexKeyTupleToEntity } from "./hexKeyTupleToEntity"; import { ConfigToRecsComponents } from "./common"; -import { tableIdToHex } from "@latticexyz/common"; -import { encodeEntity } from "./encodeEntity"; -import { StorageAdapter } from "../common"; +import { StorageAdapter, StorageAdapterBlock } from "../common"; import { configToRecsComponents } from "./configToRecsComponents"; import { singletonEntity } from "./singletonEntity"; import storeConfig from "@latticexyz/store/mud.config"; @@ -23,18 +18,18 @@ import worldConfig from "@latticexyz/world/mud.config"; export type RecsStorageOptions = { world: RecsWorld; + // TODO: make config optional? config: TConfig; }; export type RecsStorageAdapter = { - storageAdapter: StorageAdapter; + storageAdapter: StorageAdapter; components: ConfigToRecsComponents & ConfigToRecsComponents & ConfigToRecsComponents & ReturnType; }; -// TODO: make config optional? export function recsStorage({ world, config, @@ -48,63 +43,115 @@ export function recsStorage({ ...defineInternalComponents(world), }; - const storageAdapter = { - async registerTables({ tables }) { - for (const table of tables) { - // TODO: check if table exists already and skip/warn? - setComponent(components.RegisteredTables, getTableEntity(table), { table }); + async function recsStorageAdapter({ logs }: StorageAdapterBlock): Promise { + const newTables = logs.filter(isTableRegistrationLog).map(logToTable); + for (const newTable of newTables) { + const tableEntity = getTableEntity(newTable); + if (hasComponent(components.RegisteredTables, tableEntity)) { + console.warn("table already registered, ignoring", { + newTable, + existingTable: getComponentValue(components.RegisteredTables, tableEntity)?.table, + }); + } else { + setComponent(components.RegisteredTables, tableEntity, { table: newTable }); } - }, - async getTables({ tables }) { - // TODO: fetch schema from RPC if table not found? - return tables - .map((table) => getComponentValue(components.RegisteredTables, getTableEntity(table))?.table) - .filter(isDefined); - }, - async storeOperations({ operations }) { - for (const operation of operations) { - const table = getComponentValue( - components.RegisteredTables, - getTableEntity({ - address: operation.address, - namespace: operation.namespace, - name: operation.name, - }) - )?.table; - if (!table) { - debug(`skipping update for unknown table: ${operation.namespace}:${operation.name} at ${operation.address}`); - continue; - } + } - const tableId = tableIdToHex(operation.namespace, operation.name); - const component = world.components.find((component) => component.id === tableId); - if (!component) { - debug(`skipping update for unknown component: ${tableId}. Available components: ${Object.keys(components)}`); - continue; - } + for (const log of logs) { + const { namespace, name } = hexToTableId(log.args.tableId); + const table = getComponentValue( + components.RegisteredTables, + getTableEntity({ address: log.address, namespace, name }) + )?.table; + if (!table) { + debug(`skipping update for unknown table: ${namespace}:${name} at ${log.address}`); + continue; + } + + const component = world.components.find((c) => c.id === table.tableId); + if (!component) { + debug( + `skipping update for unknown component: ${table.tableId} (${table.namespace}:${ + table.name + }). Available components: ${Object.keys(components)}` + ); + continue; + } - const entity = encodeEntity(table.keySchema, operation.key); + const entity = hexKeyTupleToEntity(log.args.keyTuple); - if (operation.type === "SetRecord") { - debug("setting component", tableId, entity, operation.value); - setComponent(component, entity, operation.value as ComponentValue); - } else if (operation.type === "SetField") { - debug("updating component", tableId, entity, { - [operation.fieldName]: operation.fieldValue, - }); - updateComponent( - component, - entity, - { [operation.fieldName]: operation.fieldValue } as ComponentValue, - schemaToDefaults(table.valueSchema) as ComponentValue - ); - } else if (operation.type === "DeleteRecord") { - debug("deleting component", tableId, entity); - removeComponent(component, entity); - } + if (log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord") { + const value = decodeValueArgs(table.valueSchema, log.args); + debug("setting component", { + namespace: table.namespace, + name: table.name, + entity, + value, + }); + setComponent(component, entity, { + ...value, + __staticData: log.args.staticData, + __encodedLengths: log.args.encodedLengths, + __dynamicData: log.args.dynamicData, + }); + } else if (log.eventName === "StoreSpliceStaticData") { + // TODO: add tests that this works when no record had been set before + const previousValue = getComponentValue(component, entity); + const previousStaticData = (previousValue?.__staticData as Hex) ?? "0x"; + const newStaticData = spliceHex(previousStaticData, log.args.start, log.args.deleteCount, log.args.data); + const newValue = decodeValueArgs(table.valueSchema, { + staticData: newStaticData, + encodedLengths: (previousValue?.__encodedLengths as Hex) ?? "0x", + dynamicData: (previousValue?.__dynamicData as Hex) ?? "0x", + }); + debug("setting component via splice static", { + namespace: table.namespace, + name: table.name, + entity, + previousStaticData, + newStaticData, + previousValue, + newValue, + }); + setComponent(component, entity, { + ...newValue, + __staticData: newStaticData, + }); + } else if (log.eventName === "StoreSpliceDynamicData") { + // TODO: add tests that this works when no record had been set before + const previousValue = getComponentValue(component, entity); + const previousDynamicData = (previousValue?.__dynamicData as Hex) ?? "0x"; + const newDynamicData = spliceHex(previousDynamicData, log.args.start, log.args.deleteCount, log.args.data); + const newValue = decodeValueArgs(table.valueSchema, { + staticData: (previousValue?.__staticData as Hex) ?? "0x", + // TODO: handle unchanged encoded lengths + encodedLengths: log.args.encodedLengths, + dynamicData: newDynamicData, + }); + debug("setting component via splice dynamic", { + namespace: table.namespace, + name: table.name, + entity, + previousDynamicData, + newDynamicData, + previousValue, + newValue, + }); + setComponent(component, entity, { + ...newValue, + __encodedLengths: log.args.encodedLengths, + __dynamicData: newDynamicData, + }); + } else if (log.eventName === "StoreDeleteRecord") { + debug("deleting component", { + namespace: table.namespace, + name: table.name, + entity, + }); + removeComponent(component, entity); } - }, - } as StorageAdapter; + } + } - return { storageAdapter, components }; + return { storageAdapter: recsStorageAdapter, components }; } diff --git a/packages/store-sync/src/recs/syncToRecs.ts b/packages/store-sync/src/recs/syncToRecs.ts index aecf0231ec..2d439e0512 100644 --- a/packages/store-sync/src/recs/syncToRecs.ts +++ b/packages/store-sync/src/recs/syncToRecs.ts @@ -12,7 +12,7 @@ type SyncToRecsOptions = SyncOptions< startSync?: boolean; }; -type SyncToRecsResult = SyncResult & { +type SyncToRecsResult = SyncResult & { components: RecsStorageAdapter["components"]; stopSync: () => void; }; @@ -52,7 +52,7 @@ export async function syncToRecs({ }, }); - const sub = startSync ? storeSync.blockStorageOperations$.subscribe() : null; + const sub = startSync ? storeSync.storedBlockLogs$.subscribe() : null; const stopSync = (): void => { sub?.unsubscribe(); }; diff --git a/packages/store-sync/src/schemaToDefaults.ts b/packages/store-sync/src/schemaToDefaults.ts index 15be27c54c..278b4c77c7 100644 --- a/packages/store-sync/src/schemaToDefaults.ts +++ b/packages/store-sync/src/schemaToDefaults.ts @@ -1,5 +1,5 @@ +import { SchemaToPrimitives, ValueSchema } from "@latticexyz/protocol-parser"; import { schemaAbiTypeToDefaultValue } from "@latticexyz/schema-type"; -import { ValueSchema, SchemaToPrimitives } from "@latticexyz/protocol-parser"; export function schemaToDefaults(valueSchema: TSchema): SchemaToPrimitives { return Object.fromEntries( diff --git a/packages/store-sync/src/sqlite/buildSqliteColumn.ts b/packages/store-sync/src/sqlite/buildColumn.ts similarity index 95% rename from packages/store-sync/src/sqlite/buildSqliteColumn.ts rename to packages/store-sync/src/sqlite/buildColumn.ts index f4e07e73cf..354600b146 100644 --- a/packages/store-sync/src/sqlite/buildSqliteColumn.ts +++ b/packages/store-sync/src/sqlite/buildColumn.ts @@ -1,9 +1,10 @@ -import { AnySQLiteColumnBuilder, blob, integer, text } from "drizzle-orm/sqlite-core"; +import { blob, integer, text } from "drizzle-orm/sqlite-core"; import { SchemaAbiType } from "@latticexyz/schema-type"; import { assertExhaustive } from "@latticexyz/common/utils"; import { address, json } from "./columnTypes"; -export function buildSqliteColumn(name: string, schemaAbiType: SchemaAbiType): AnySQLiteColumnBuilder { +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function buildColumn(name: string, schemaAbiType: SchemaAbiType) { switch (schemaAbiType) { case "bool": return integer(name, { mode: "boolean" }); diff --git a/packages/store-sync/src/sqlite/buildTable.test.ts b/packages/store-sync/src/sqlite/buildTable.test.ts new file mode 100644 index 0000000000..22b1eb0a42 --- /dev/null +++ b/packages/store-sync/src/sqlite/buildTable.test.ts @@ -0,0 +1,1086 @@ +import { describe, it, expect } from "vitest"; +import { buildTable } from "./buildTable"; + +describe("buildTable", () => { + it("should create table from schema", async () => { + const table = buildTable({ + address: "0xffffffffffffffffffffffffffffffffffffffff", + namespace: "test", + name: "users", + keySchema: { x: "uint32", y: "uint32" }, + valueSchema: { name: "string", addr: "address" }, + }); + + expect(table).toMatchInlineSnapshot(` + SQLiteTable { + "__dynamicData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "__isDeleted": SQLiteBoolean { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "config": { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "dataType": "boolean", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "dataType": "boolean", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "__key": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primaryKey": true, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primary": true, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "__lastUpdatedBlockNumber": SQLiteBigInt { + "columnType": "SQLiteBigInt", + "config": { + "columnType": "SQLiteBigInt", + "dataType": "bigint", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "dataType": "bigint", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "addr": SQLiteCustomColumn { + "columnType": "SQLiteCustomColumn", + "config": { + "columnType": "SQLiteCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "addr", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addr_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "addr", + "notNull": true, + "primary": false, + "sqlName": "text", + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addr_unique", + "uniqueType": undefined, + }, + "name": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "name", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_name_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "name", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_name_unique", + "uniqueType": undefined, + }, + "x": SQLiteInteger { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "config": { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "dataType": "number", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "x", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_x_unique", + "uniqueType": undefined, + }, + "dataType": "number", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "x", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_x_unique", + "uniqueType": undefined, + }, + "y": SQLiteInteger { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "config": { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "dataType": "number", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "y", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_y_unique", + "uniqueType": undefined, + }, + "dataType": "number", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "y", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_y_unique", + "uniqueType": undefined, + }, + Symbol(drizzle:Name): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", + Symbol(drizzle:OriginalName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", + Symbol(drizzle:Schema): undefined, + Symbol(drizzle:Columns): { + "__dynamicData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "__isDeleted": SQLiteBoolean { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "config": { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "dataType": "boolean", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "dataType": "boolean", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "__key": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primaryKey": true, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primary": true, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "__lastUpdatedBlockNumber": SQLiteBigInt { + "columnType": "SQLiteBigInt", + "config": { + "columnType": "SQLiteBigInt", + "dataType": "bigint", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "dataType": "bigint", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "addr": SQLiteCustomColumn { + "columnType": "SQLiteCustomColumn", + "config": { + "columnType": "SQLiteCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "addr", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addr_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "addr", + "notNull": true, + "primary": false, + "sqlName": "text", + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addr_unique", + "uniqueType": undefined, + }, + "name": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "name", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_name_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "name", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_name_unique", + "uniqueType": undefined, + }, + "x": SQLiteInteger { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "config": { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "dataType": "number", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "x", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_x_unique", + "uniqueType": undefined, + }, + "dataType": "number", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "x", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_x_unique", + "uniqueType": undefined, + }, + "y": SQLiteInteger { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "config": { + "autoIncrement": false, + "columnType": "SQLiteInteger", + "dataType": "number", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "y", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_y_unique", + "uniqueType": undefined, + }, + "dataType": "number", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "y", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_y_unique", + "uniqueType": undefined, + }, + }, + Symbol(drizzle:BaseName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", + Symbol(drizzle:IsAlias): false, + Symbol(drizzle:ExtraConfigBuilder): undefined, + Symbol(drizzle:IsDrizzleTable): true, + Symbol(drizzle:SQLiteInlineForeignKeys): [], + } + `); + }); + + it("can create a singleton table", async () => { + const table = buildTable({ + address: "0xffffffffffffffffffffffffffffffffffffffff", + namespace: "test", + name: "users", + keySchema: {}, + valueSchema: { addrs: "address[]" }, + }); + + expect(table).toMatchInlineSnapshot(` + SQLiteTable { + "__dynamicData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "__isDeleted": SQLiteBoolean { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "config": { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "dataType": "boolean", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "dataType": "boolean", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "__key": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primaryKey": true, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primary": true, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "__lastUpdatedBlockNumber": SQLiteBigInt { + "columnType": "SQLiteBigInt", + "config": { + "columnType": "SQLiteBigInt", + "dataType": "bigint", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "dataType": "bigint", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "addrs": SQLiteCustomColumn { + "columnType": "SQLiteCustomColumn", + "config": { + "columnType": "SQLiteCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "addrs", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addrs_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "addrs", + "notNull": true, + "primary": false, + "sqlName": "text", + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addrs_unique", + "uniqueType": undefined, + }, + Symbol(drizzle:Name): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", + Symbol(drizzle:OriginalName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", + Symbol(drizzle:Schema): undefined, + Symbol(drizzle:Columns): { + "__dynamicData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__dynamicData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___dynamicData_unique", + "uniqueType": undefined, + }, + "__encodedLengths": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__encodedLengths", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___encodedLengths_unique", + "uniqueType": undefined, + }, + "__isDeleted": SQLiteBoolean { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "config": { + "autoIncrement": false, + "columnType": "SQLiteBoolean", + "dataType": "boolean", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "dataType": "boolean", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mode": "boolean", + "name": "__isDeleted", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___isDeleted_unique", + "uniqueType": undefined, + }, + "__key": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primaryKey": true, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__key", + "notNull": true, + "primary": true, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___key_unique", + "uniqueType": undefined, + }, + "__lastUpdatedBlockNumber": SQLiteBigInt { + "columnType": "SQLiteBigInt", + "config": { + "columnType": "SQLiteBigInt", + "dataType": "bigint", + "default": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "dataType": "bigint", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "name": "__lastUpdatedBlockNumber", + "notNull": true, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___lastUpdatedBlockNumber_unique", + "uniqueType": undefined, + }, + "__staticData": SQLiteText { + "columnType": "SQLiteText", + "config": { + "columnType": "SQLiteText", + "dataType": "string", + "default": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "dataType": "string", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "length": undefined, + "name": "__staticData", + "notNull": false, + "primary": false, + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users___staticData_unique", + "uniqueType": undefined, + }, + "addrs": SQLiteCustomColumn { + "columnType": "SQLiteCustomColumn", + "config": { + "columnType": "SQLiteCustomColumn", + "customTypeParams": { + "dataType": [Function], + "fromDriver": [Function], + "toDriver": [Function], + }, + "dataType": "custom", + "default": undefined, + "fieldConfig": undefined, + "hasDefault": false, + "isUnique": false, + "name": "addrs", + "notNull": true, + "primaryKey": false, + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addrs_unique", + "uniqueType": undefined, + }, + "dataType": "custom", + "default": undefined, + "defaultFn": undefined, + "enumValues": undefined, + "hasDefault": false, + "isUnique": false, + "mapFrom": [Function], + "mapTo": [Function], + "name": "addrs", + "notNull": true, + "primary": false, + "sqlName": "text", + "table": [Circular], + "uniqueName": "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users_addrs_unique", + "uniqueType": undefined, + }, + }, + Symbol(drizzle:BaseName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", + Symbol(drizzle:IsAlias): false, + Symbol(drizzle:ExtraConfigBuilder): undefined, + Symbol(drizzle:IsDrizzleTable): true, + Symbol(drizzle:SQLiteInlineForeignKeys): [], + } + `); + }); +}); diff --git a/packages/store-sync/src/sqlite/createSqliteTable.ts b/packages/store-sync/src/sqlite/buildTable.ts similarity index 70% rename from packages/store-sync/src/sqlite/createSqliteTable.ts rename to packages/store-sync/src/sqlite/buildTable.ts index e8e8667b7d..2b9b0efd5c 100644 --- a/packages/store-sync/src/sqlite/createSqliteTable.ts +++ b/packages/store-sync/src/sqlite/buildTable.ts @@ -1,17 +1,21 @@ -import { AnySQLiteColumnBuilder, SQLiteTableWithColumns, sqliteTable } from "drizzle-orm/sqlite-core"; -import { buildSqliteColumn } from "./buildSqliteColumn"; +import { SQLiteColumnBuilderBase, SQLiteTableWithColumns, sqliteTable } from "drizzle-orm/sqlite-core"; +import { buildColumn } from "./buildColumn"; import { Address } from "viem"; import { getTableName } from "./getTableName"; import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser"; export const metaColumns = { - __key: buildSqliteColumn("__key", "bytes").notNull().primaryKey(), - __lastUpdatedBlockNumber: buildSqliteColumn("__lastUpdatedBlockNumber", "uint256").notNull(), + __key: buildColumn("__key", "bytes").primaryKey(), + __staticData: buildColumn("__staticData", "bytes"), + __encodedLengths: buildColumn("__encodedLengths", "bytes"), + __dynamicData: buildColumn("__dynamicData", "bytes"), + __lastUpdatedBlockNumber: buildColumn("__lastUpdatedBlockNumber", "uint256").notNull(), // TODO: last updated block hash? - __isDeleted: buildSqliteColumn("__isDeleted", "bool").notNull(), -} as const satisfies Record; + __isDeleted: buildColumn("__isDeleted", "bool").notNull(), +} as const satisfies Record; type SQLiteTableFromSchema = SQLiteTableWithColumns<{ + dialect: "sqlite"; name: string; schema: string | undefined; columns: { @@ -39,7 +43,7 @@ type CreateSqliteTableResult; -export function createSqliteTable({ +export function buildTable({ address, namespace, name, @@ -49,11 +53,11 @@ export function createSqliteTable [name, buildSqliteColumn(name, type).notNull()]) + Object.entries(keySchema).map(([name, type]) => [name, buildColumn(name, type).notNull()]) ); const valueColumns = Object.fromEntries( - Object.entries(valueSchema).map(([name, type]) => [name, buildSqliteColumn(name, type).notNull()]) + Object.entries(valueSchema).map(([name, type]) => [name, buildColumn(name, type).notNull()]) ); // TODO: unique constraint on key columns? diff --git a/packages/store-sync/src/sqlite/columnTypes.ts b/packages/store-sync/src/sqlite/columnTypes.ts index dc23a26f5c..3dd4c07e9f 100644 --- a/packages/store-sync/src/sqlite/columnTypes.ts +++ b/packages/store-sync/src/sqlite/columnTypes.ts @@ -1,9 +1,9 @@ -import { customType, SQLiteCustomColumnBuilder } from "drizzle-orm/sqlite-core"; -import { ColumnBuilderBaseConfig } from "drizzle-orm"; +import { customType } from "drizzle-orm/sqlite-core"; import superjson from "superjson"; import { Address, getAddress } from "viem"; -export const json = (name: string): SQLiteCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const json = (name: string) => customType<{ data: TData; driverData: string }>({ dataType() { return "text"; @@ -16,7 +16,8 @@ export const json = (name: string): SQLiteCustomColumnBuilder => +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const address = (name: string) => customType<{ data: Address; driverData: string }>({ dataType() { return "text"; diff --git a/packages/store-sync/src/sqlite/createSqliteTable.test.ts b/packages/store-sync/src/sqlite/createSqliteTable.test.ts deleted file mode 100644 index 756055c09d..0000000000 --- a/packages/store-sync/src/sqlite/createSqliteTable.test.ts +++ /dev/null @@ -1,446 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { createSqliteTable } from "./createSqliteTable"; - -describe("createSqliteTable", () => { - it("should create table from schema", async () => { - const table = createSqliteTable({ - address: "0xffffffffffffffffffffffffffffffffffffffff", - namespace: "test", - name: "users", - keySchema: { x: "uint32", y: "uint32" }, - valueSchema: { name: "string", addr: "address" }, - }); - - expect(table).toMatchInlineSnapshot(` - SQLiteTable { - "__isDeleted": SQLiteBoolean { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "__key": SQLiteText { - "config": { - "default": undefined, - "enumValues": [], - "length": undefined, - "name": "__key", - "notNull": true, - "primaryKey": true, - }, - "default": undefined, - "enumValues": [], - "hasDefault": undefined, - "length": undefined, - "name": "__key", - "notNull": true, - "primary": true, - "table": [Circular], - }, - "__lastUpdatedBlockNumber": SQLiteBigInt { - "config": { - "default": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "addr": SQLiteCustomColumn { - "config": { - "customTypeParams": { - "dataType": [Function], - "fromDriver": [Function], - "toDriver": [Function], - }, - "default": undefined, - "fieldConfig": undefined, - "name": "addr", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mapFrom": [Function], - "mapTo": [Function], - "name": "addr", - "notNull": true, - "primary": false, - "sqlName": "text", - "table": [Circular], - }, - "name": SQLiteText { - "config": { - "default": undefined, - "enumValues": [], - "length": undefined, - "name": "name", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "enumValues": [], - "hasDefault": undefined, - "length": undefined, - "name": "name", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "x": SQLiteInteger { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "name": "x", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "x", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "y": SQLiteInteger { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "name": "y", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "y", - "notNull": true, - "primary": false, - "table": [Circular], - }, - Symbol(drizzle:Name): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", - Symbol(drizzle:OriginalName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", - Symbol(drizzle:Schema): undefined, - Symbol(drizzle:Columns): { - "__isDeleted": SQLiteBoolean { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "__key": SQLiteText { - "config": { - "default": undefined, - "enumValues": [], - "length": undefined, - "name": "__key", - "notNull": true, - "primaryKey": true, - }, - "default": undefined, - "enumValues": [], - "hasDefault": undefined, - "length": undefined, - "name": "__key", - "notNull": true, - "primary": true, - "table": [Circular], - }, - "__lastUpdatedBlockNumber": SQLiteBigInt { - "config": { - "default": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "addr": SQLiteCustomColumn { - "config": { - "customTypeParams": { - "dataType": [Function], - "fromDriver": [Function], - "toDriver": [Function], - }, - "default": undefined, - "fieldConfig": undefined, - "name": "addr", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mapFrom": [Function], - "mapTo": [Function], - "name": "addr", - "notNull": true, - "primary": false, - "sqlName": "text", - "table": [Circular], - }, - "name": SQLiteText { - "config": { - "default": undefined, - "enumValues": [], - "length": undefined, - "name": "name", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "enumValues": [], - "hasDefault": undefined, - "length": undefined, - "name": "name", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "x": SQLiteInteger { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "name": "x", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "x", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "y": SQLiteInteger { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "name": "y", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "y", - "notNull": true, - "primary": false, - "table": [Circular], - }, - }, - Symbol(drizzle:BaseName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", - Symbol(drizzle:IsAlias): false, - Symbol(drizzle:ExtraConfigBuilder): undefined, - Symbol(drizzle:IsDrizzleTable): true, - Symbol(drizzle:SQLiteInlineForeignKeys): [], - } - `); - }); - - it("can create a singleton table", async () => { - const table = createSqliteTable({ - address: "0xffffffffffffffffffffffffffffffffffffffff", - namespace: "test", - name: "users", - keySchema: {}, - valueSchema: { addrs: "address[]" }, - }); - - expect(table).toMatchInlineSnapshot(` - SQLiteTable { - "__isDeleted": SQLiteBoolean { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "__key": SQLiteText { - "config": { - "default": undefined, - "enumValues": [], - "length": undefined, - "name": "__key", - "notNull": true, - "primaryKey": true, - }, - "default": undefined, - "enumValues": [], - "hasDefault": undefined, - "length": undefined, - "name": "__key", - "notNull": true, - "primary": true, - "table": [Circular], - }, - "__lastUpdatedBlockNumber": SQLiteBigInt { - "config": { - "default": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "addrs": SQLiteCustomColumn { - "config": { - "customTypeParams": { - "dataType": [Function], - "fromDriver": [Function], - "toDriver": [Function], - }, - "default": undefined, - "fieldConfig": undefined, - "name": "addrs", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mapFrom": [Function], - "mapTo": [Function], - "name": "addrs", - "notNull": true, - "primary": false, - "sqlName": "text", - "table": [Circular], - }, - Symbol(drizzle:Name): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", - Symbol(drizzle:OriginalName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", - Symbol(drizzle:Schema): undefined, - Symbol(drizzle:Columns): { - "__isDeleted": SQLiteBoolean { - "autoIncrement": false, - "config": { - "autoIncrement": false, - "default": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mode": "boolean", - "name": "__isDeleted", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "__key": SQLiteText { - "config": { - "default": undefined, - "enumValues": [], - "length": undefined, - "name": "__key", - "notNull": true, - "primaryKey": true, - }, - "default": undefined, - "enumValues": [], - "hasDefault": undefined, - "length": undefined, - "name": "__key", - "notNull": true, - "primary": true, - "table": [Circular], - }, - "__lastUpdatedBlockNumber": SQLiteBigInt { - "config": { - "default": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "name": "__lastUpdatedBlockNumber", - "notNull": true, - "primary": false, - "table": [Circular], - }, - "addrs": SQLiteCustomColumn { - "config": { - "customTypeParams": { - "dataType": [Function], - "fromDriver": [Function], - "toDriver": [Function], - }, - "default": undefined, - "fieldConfig": undefined, - "name": "addrs", - "notNull": true, - "primaryKey": false, - }, - "default": undefined, - "hasDefault": undefined, - "mapFrom": [Function], - "mapTo": [Function], - "name": "addrs", - "notNull": true, - "primary": false, - "sqlName": "text", - "table": [Circular], - }, - }, - Symbol(drizzle:BaseName): "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test__users", - Symbol(drizzle:IsAlias): false, - Symbol(drizzle:ExtraConfigBuilder): undefined, - Symbol(drizzle:IsDrizzleTable): true, - Symbol(drizzle:SQLiteInlineForeignKeys): [], - } - `); - }); -}); diff --git a/packages/store-sync/src/sqlite/getTables.ts b/packages/store-sync/src/sqlite/getTables.ts index 6d1a4f4d03..9a99afacbf 100644 --- a/packages/store-sync/src/sqlite/getTables.ts +++ b/packages/store-sync/src/sqlite/getTables.ts @@ -1,9 +1,9 @@ import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"; import { inArray } from "drizzle-orm"; import { Table } from "../common"; -import { TableId } from "@latticexyz/common/deprecated"; import { getTableName } from "./getTableName"; import { mudStoreTables } from "./internalTables"; +import { tableIdToHex } from "@latticexyz/common"; export function getTables( db: BaseSQLiteDatabase<"sync", void>, @@ -19,7 +19,7 @@ export function getTables( .all(); return tables.map((table) => { - const tableId = new TableId(table.namespace, table.name).toHex(); + const tableId = tableIdToHex(table.namespace, table.name); return { id: getTableName(table.address, table.namespace, table.name), address: table.address, diff --git a/packages/store-sync/src/sqlite/index.ts b/packages/store-sync/src/sqlite/index.ts index bcfd3948ad..8e6b2acd2e 100644 --- a/packages/store-sync/src/sqlite/index.ts +++ b/packages/store-sync/src/sqlite/index.ts @@ -1,4 +1,4 @@ -export * from "./createSqliteTable"; +export * from "./buildTable"; export * from "./getTables"; export * from "./internalTables"; export * from "./schemaVersion"; diff --git a/packages/store-sync/src/sqlite/sqliteStorage.test.ts b/packages/store-sync/src/sqlite/sqliteStorage.test.ts index 414c104b25..2834fa73eb 100644 --- a/packages/store-sync/src/sqlite/sqliteStorage.test.ts +++ b/packages/store-sync/src/sqlite/sqliteStorage.test.ts @@ -3,13 +3,29 @@ import { sqliteStorage } from "./sqliteStorage"; import { getTables } from "./getTables"; import { chainState, mudStoreTables } from "./internalTables"; import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"; -import { createSqliteTable } from "./createSqliteTable"; +import { buildTable } from "./buildTable"; import initSqlJs from "sql.js"; import { drizzle } from "drizzle-orm/sql-js"; -import { createPublicClient, http } from "viem"; +import { Hex, RpcLog, createPublicClient, decodeEventLog, formatLog, http } from "viem"; import { foundry } from "viem/chains"; -import { blockLogsToStorage } from "../blockLogsToStorage"; +import worldRpcLogs from "../../../../test-data/world-logs.json"; +import { storeEventsAbi } from "@latticexyz/store"; import { StoreEventsLog } from "../common"; +import { groupLogsByBlockNumber } from "@latticexyz/block-logs-stream"; +import { eq } from "drizzle-orm"; + +// TODO: make test-data a proper package and export this +const blocks = groupLogsByBlockNumber( + worldRpcLogs.map((log) => { + const { eventName, args } = decodeEventLog({ + abi: storeEventsAbi, + data: log.data as Hex, + topics: log.topics as [Hex, ...Hex[]], + strict: true, + }); + return formatLog(log as any as RpcLog, { args, eventName: eventName as string }) as StoreEventsLog; + }) +); describe("sqliteStorage", async () => { const SqlJs = await initSqlJs(); @@ -39,104 +55,74 @@ describe("sqliteStorage", async () => { expect(db.select().from(chainState).all()).toMatchInlineSnapshot("[]"); expect(db.select().from(mudStoreTables).all()).toMatchInlineSnapshot("[]"); - await blockLogsToStorage(storageAdapter)({ - blockNumber: 5448n, - logs: [ - { - address: "0x5fbdb2315678afecb367f032d93f642f64180aa3", - topics: ["0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46"], - data: "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e76656e746f72790000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", - blockHash: "0x4ad3752c86f900332e0d2d8903480e7206747d233586574d16f006eebdb5138b", - blockNumber: 2n, - transactionHash: "0xaa54bf18053cce5d4d2906538a60cb1d9958cc3c10c34b5f9fdc92fe6a6abab4", - transactionIndex: 16, - logIndex: 54, - removed: false, - args: { - tableId: "0x000000000000000000000000000000005265736f757263655479706500000000", - keyTuple: ["0x00000000000000000000000000000000496e76656e746f727900000000000000"], - schemaIndex: 0, - data: "0x02", - }, - eventName: "StoreSetField", - }, - { - address: "0x5fbdb2315678afecb367f032d93f642f64180aa3", - topics: ["0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32"], - data: "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e76656e746f72790000000000000000000000000000000000000000000000000000000000000000000000000002800004010004000000000000000000000000000000000000000000000000000000001c030061030300000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000001600000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000056f776e657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046974656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6974656d56617269616e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006616d6f756e740000000000000000000000000000000000000000000000000000", - blockHash: "0x4ad3752c86f900332e0d2d8903480e7206747d233586574d16f006eebdb5138b", - blockNumber: 2n, - transactionHash: "0xaa54bf18053cce5d4d2906538a60cb1d9958cc3c10c34b5f9fdc92fe6a6abab4", - transactionIndex: 16, - logIndex: 55, - removed: false, - args: { - tableId: "0x6d756473746f726500000000000000005461626c657300000000000000000000", - keyTuple: ["0x00000000000000000000000000000000496e76656e746f727900000000000000"], - data: "0x0004010004000000000000000000000000000000000000000000000000000000001c030061030300000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000001600000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000056f776e657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046974656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6974656d56617269616e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006616d6f756e740000000000000000000000000000000000000000000000000000", - }, - eventName: "StoreSetRecord", - }, - ] satisfies StoreEventsLog[], - }); + for (const block of blocks) { + await storageAdapter(block); + } expect(db.select().from(chainState).all()).toMatchInlineSnapshot(` [ { "chainId": 31337, "lastError": null, - "lastUpdatedBlockNumber": 5448n, + "lastUpdatedBlockNumber": 6n, "schemaVersion": 1, }, ] `); - expect(db.select().from(mudStoreTables).all()).toMatchInlineSnapshot(` + expect(db.select().from(mudStoreTables).where(eq(mudStoreTables.name, "NumberList")).all()).toMatchInlineSnapshot(` [ { "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "id": "0x5FbDB2315678afecb367f032d93F642f64180aa3____Inventory", - "keySchema": { - "item": "uint32", - "itemVariant": "uint32", - "owner": "address", - }, + "id": "0x5FbDB2315678afecb367f032d93F642f64180aa3____NumberList", + "keySchema": {}, "lastError": null, - "lastUpdatedBlockNumber": 5448n, - "name": "Inventory", + "lastUpdatedBlockNumber": 6n, + "name": "NumberList", "namespace": "", "schemaVersion": 1, - "tableId": "0x00000000000000000000000000000000496e76656e746f727900000000000000", + "tableId": "0x000000000000000000000000000000004e756d6265724c697374000000000000", "valueSchema": { - "amount": "uint32", + "value": "uint32[]", }, }, ] `); - const tables = getTables(db); + const tables = getTables(db).filter((table) => table.name === "NumberList"); expect(tables).toMatchInlineSnapshot(` [ { "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "id": "0x5FbDB2315678afecb367f032d93F642f64180aa3____Inventory", - "keySchema": { - "item": "uint32", - "itemVariant": "uint32", - "owner": "address", - }, - "lastUpdatedBlockNumber": 5448n, - "name": "Inventory", + "id": "0x5FbDB2315678afecb367f032d93F642f64180aa3____NumberList", + "keySchema": {}, + "lastUpdatedBlockNumber": 6n, + "name": "NumberList", "namespace": "", - "tableId": "0x00000000000000000000000000000000496e76656e746f727900000000000000", + "tableId": "0x000000000000000000000000000000004e756d6265724c697374000000000000", "valueSchema": { - "amount": "uint32", + "value": "uint32[]", }, }, ] `); - const sqliteTable = createSqliteTable(tables[0]); - expect(db.select().from(sqliteTable).all()).toMatchInlineSnapshot("[]"); + const sqlTable = buildTable(tables[0]); + expect(db.select().from(sqlTable).all()).toMatchInlineSnapshot(` + [ + { + "__dynamicData": "0x000001a400000045", + "__encodedLengths": "0x0000000000000000000000000000000000000000000000000800000000000008", + "__isDeleted": false, + "__key": "0x", + "__lastUpdatedBlockNumber": 6n, + "__staticData": null, + "value": [ + 420, + 69, + ], + }, + ] + `); }); }); diff --git a/packages/store-sync/src/sqlite/sqliteStorage.ts b/packages/store-sync/src/sqlite/sqliteStorage.ts index fb2073b11c..08ca78f547 100644 --- a/packages/store-sync/src/sqlite/sqliteStorage.ts +++ b/packages/store-sync/src/sqlite/sqliteStorage.ts @@ -1,10 +1,8 @@ -import { PublicClient, concatHex, encodeAbiParameters, getAddress } from "viem"; +import { Hex, PublicClient, concatHex, getAddress } from "viem"; import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"; import { and, eq, sql } from "drizzle-orm"; import { sqliteTableToSql } from "./sqliteTableToSql"; -import { createSqliteTable } from "./createSqliteTable"; -import { schemaToDefaults } from "../schemaToDefaults"; -import { TableId } from "@latticexyz/common/deprecated"; +import { buildTable } from "./buildTable"; import { StoreConfig } from "@latticexyz/store"; import { debug } from "./debug"; import { getTableName } from "./getTableName"; @@ -12,6 +10,12 @@ import { chainState, mudStoreTables } from "./internalTables"; import { getTables } from "./getTables"; import { schemaVersion } from "./schemaVersion"; import { StorageAdapter } from "../common"; +import { isTableRegistrationLog } from "../isTableRegistrationLog"; +import { logToTable } from "../logToTable"; +import { hexToTableId, spliceHex, tableIdToHex } from "@latticexyz/common"; +import { decodeKey, decodeValueArgs } from "@latticexyz/protocol-parser"; + +// TODO: upgrade drizzle and use async sqlite interface for consistency export async function sqliteStorage({ database, @@ -20,170 +24,230 @@ export async function sqliteStorage({ database: BaseSQLiteDatabase<"sync", void>; publicClient: PublicClient; config?: TConfig; -}): Promise> { +}): Promise { const chainId = publicClient.chain?.id ?? (await publicClient.getChainId()); // TODO: should these run lazily before first `registerTables`? database.run(sql.raw(sqliteTableToSql(chainState))); database.run(sql.raw(sqliteTableToSql(mudStoreTables))); - return { - async registerTables({ blockNumber, tables }) { - await database.transaction(async (tx) => { - for (const table of tables) { - debug(`creating table ${table.namespace}:${table.name} for world ${chainId}:${table.address}`); + return async function sqliteStorageAdapter({ blockNumber, logs }) { + // Find table registration logs and create new tables + const newTables = logs.filter(isTableRegistrationLog).map(logToTable); + await database.transaction(async (tx) => { + for (const table of newTables) { + debug(`creating table ${table.namespace}:${table.name} for world ${chainId}:${table.address}`); + + const sqliteTable = buildTable({ + address: table.address, + namespace: table.namespace, + name: table.name, + keySchema: table.keySchema, + valueSchema: table.valueSchema, + }); - const sqliteTable = createSqliteTable({ + tx.run(sql.raw(sqliteTableToSql(sqliteTable))); + + tx.insert(mudStoreTables) + .values({ + schemaVersion, + id: getTableName(table.address, table.namespace, table.name), address: table.address, + tableId: tableIdToHex(table.namespace, table.name), namespace: table.namespace, name: table.name, keySchema: table.keySchema, valueSchema: table.valueSchema, - }); - - tx.run(sql.raw(sqliteTableToSql(sqliteTable))); + lastUpdatedBlockNumber: blockNumber, + }) + .onConflictDoNothing() + .run(); + } + }); - tx.insert(mudStoreTables) - .values({ - schemaVersion, - id: getTableName(table.address, table.namespace, table.name), - address: table.address, - tableId: new TableId(table.namespace, table.name).toHex(), - namespace: table.namespace, - name: table.name, - keySchema: table.keySchema, - valueSchema: table.valueSchema, - lastUpdatedBlockNumber: blockNumber, + const tables = getTables( + database, + Array.from( + new Set( + logs.map((log) => + JSON.stringify({ + address: getAddress(log.address), + ...hexToTableId(log.args.tableId), }) - .onConflictDoNothing() - .run(); - } - }); - }, - async getTables({ tables }) { - // TODO: fetch any missing schemas from RPC - // TODO: cache schemas in memory? - return getTables(database, tables); - }, - async storeOperations({ blockNumber, operations }) { - // This is currently parallelized per world (each world has its own database). - // This may need to change if we decide to put multiple worlds into one DB (e.g. a namespace per world, but all under one DB). - // If so, we'll probably want to wrap the entire block worth of operations in a transaction. + ) + ) + ).map((json) => JSON.parse(json)) + ); - const tables = getTables( - database, - Array.from( - new Set( - operations.map((operation) => - JSON.stringify({ - address: getAddress(operation.address), - namespace: operation.namespace, - name: operation.name, - }) + await database.transaction(async (tx) => { + for (const { address, namespace, name } of tables) { + tx.update(mudStoreTables) + .set({ lastUpdatedBlockNumber: blockNumber }) + .where( + and( + eq(mudStoreTables.address, address), + eq(mudStoreTables.namespace, namespace), + eq(mudStoreTables.name, name) ) ) - ).map((json) => JSON.parse(json)) - ); + .run(); + } - await database.transaction(async (tx) => { - for (const { address, namespace, name } of tables) { - tx.update(mudStoreTables) - .set({ lastUpdatedBlockNumber: blockNumber }) - .where( - and( - eq(mudStoreTables.address, address), - eq(mudStoreTables.namespace, namespace), - eq(mudStoreTables.name, name) - ) - ) - .run(); + for (const log of logs) { + const table = tables.find( + (table) => table.address === getAddress(log.address) && table.tableId === log.args.tableId + ); + if (!table) { + const tableId = hexToTableId(log.args.tableId); + debug(`table ${tableId.namespace}:${tableId.name} not found, skipping log`, log); + continue; } - for (const operation of operations) { - const table = tables.find( - (table) => - table.address === getAddress(operation.address) && - table.namespace === operation.namespace && - table.name === operation.name - ); - if (!table) { - debug(`table ${operation.namespace}:${operation.name} not found, skipping operation`, operation); - continue; - } + const sqlTable = buildTable(table); + const uniqueKey = concatHex(log.args.keyTuple as Hex[]); + const key = decodeKey(table.keySchema, log.args.keyTuple); - const sqliteTable = createSqliteTable(table); - const key = concatHex( - Object.entries(table.keySchema).map(([keyName, type]) => - encodeAbiParameters([{ type }], [operation.key[keyName]]) - ) - ); - - if (operation.type === "SetRecord") { - debug("SetRecord", operation); - tx.insert(sqliteTable) - .values({ - __key: key, + if (log.eventName === "StoreSetRecord" || log.eventName === "StoreEphemeralRecord") { + const value = decodeValueArgs(table.valueSchema, log.args); + debug("upserting record", { + namespace: table.namespace, + name: table.name, + key, + value, + }); + tx.insert(sqlTable) + .values({ + __key: uniqueKey, + __staticData: log.args.staticData, + __encodedLengths: log.args.encodedLengths, + __dynamicData: log.args.dynamicData, + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: false, + ...key, + ...value, + }) + .onConflictDoUpdate({ + target: sqlTable.__key, + set: { + __staticData: log.args.staticData, + __encodedLengths: log.args.encodedLengths, + __dynamicData: log.args.dynamicData, __lastUpdatedBlockNumber: blockNumber, __isDeleted: false, - ...operation.key, - ...operation.value, - }) - .onConflictDoUpdate({ - target: sqliteTable.__key, - set: { - __lastUpdatedBlockNumber: blockNumber, - __isDeleted: false, - ...operation.value, - }, - }) - .run(); - } else if (operation.type === "SetField") { - debug("SetField", operation); - tx.insert(sqliteTable) - .values({ - __key: key, + ...value, + }, + }) + .run(); + } else if (log.eventName === "StoreSpliceStaticData") { + // TODO: verify that this returns what we expect (doesn't error/undefined on no record) + const previousValue = (await tx.select().from(sqlTable).where(eq(sqlTable.__key, uniqueKey)).execute())[0]; + const previousStaticData = (previousValue?.__staticData as Hex) ?? "0x"; + const newStaticData = spliceHex(previousStaticData, log.args.start, log.args.deleteCount, log.args.data); + const newValue = decodeValueArgs(table.valueSchema, { + staticData: newStaticData, + encodedLengths: (previousValue?.__encodedLengths as Hex) ?? "0x", + dynamicData: (previousValue?.__dynamicData as Hex) ?? "0x", + }); + debug("upserting record via splice static", { + namespace: table.namespace, + name: table.name, + key, + previousStaticData, + newStaticData, + previousValue, + newValue, + }); + tx.insert(sqlTable) + .values({ + __key: uniqueKey, + __staticData: newStaticData, + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: false, + ...key, + ...newValue, + }) + .onConflictDoUpdate({ + target: sqlTable.__key, + set: { + __staticData: newStaticData, __lastUpdatedBlockNumber: blockNumber, __isDeleted: false, - ...operation.key, - ...schemaToDefaults(table.valueSchema), - [operation.fieldName]: operation.fieldValue, - }) - .onConflictDoUpdate({ - target: sqliteTable.__key, - set: { - __lastUpdatedBlockNumber: blockNumber, - __isDeleted: false, - [operation.fieldName]: operation.fieldValue, - }, - }) - .run(); - } else if (operation.type === "DeleteRecord") { - // TODO: should we upsert so we at least have a DB record of when a thing was created/deleted within the same block? - debug("DeleteRecord", operation); - tx.update(sqliteTable) - .set({ + ...newValue, + }, + }) + .run(); + } else if (log.eventName === "StoreSpliceDynamicData") { + const previousValue = (await tx.select().from(sqlTable).where(eq(sqlTable.__key, uniqueKey)).execute())[0]; + const previousDynamicData = (previousValue?.__dynamicData as Hex) ?? "0x"; + const newDynamicData = spliceHex(previousDynamicData, log.args.start, log.args.deleteCount, log.args.data); + const newValue = decodeValueArgs(table.valueSchema, { + staticData: (previousValue?.__staticData as Hex) ?? "0x", + // TODO: handle unchanged encoded lengths + encodedLengths: log.args.encodedLengths, + dynamicData: newDynamicData, + }); + debug("upserting record via splice dynamic", { + namespace: table.namespace, + name: table.name, + key, + previousDynamicData, + newDynamicData, + previousValue, + newValue, + }); + tx.insert(sqlTable) + .values({ + __key: uniqueKey, + // TODO: handle unchanged encoded lengths + __encodedLengths: log.args.encodedLengths, + __dynamicData: newDynamicData, + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: false, + ...key, + ...newValue, + }) + .onConflictDoUpdate({ + target: sqlTable.__key, + set: { + // TODO: handle unchanged encoded lengths + __encodedLengths: log.args.encodedLengths, + __dynamicData: newDynamicData, __lastUpdatedBlockNumber: blockNumber, - __isDeleted: true, - }) - .where(eq(sqliteTable.__key, key)) - .run(); - } + __isDeleted: false, + ...newValue, + }, + }) + .run(); + } else if (log.eventName === "StoreDeleteRecord") { + // TODO: should we upsert so we at least have a DB record of when a thing was created/deleted within the same block? + debug("deleting record", { + namespace: table.namespace, + name: table.name, + key, + }); + tx.update(sqlTable) + .set({ + __lastUpdatedBlockNumber: blockNumber, + __isDeleted: true, + }) + .where(eq(sqlTable.__key, uniqueKey)) + .run(); } + } - tx.insert(chainState) - .values({ - schemaVersion, - chainId, + tx.insert(chainState) + .values({ + schemaVersion, + chainId, + lastUpdatedBlockNumber: blockNumber, + }) + .onConflictDoUpdate({ + target: [chainState.schemaVersion, chainState.chainId], + set: { lastUpdatedBlockNumber: blockNumber, - }) - .onConflictDoUpdate({ - target: [chainState.schemaVersion, chainState.chainId], - set: { - lastUpdatedBlockNumber: blockNumber, - }, - }) - .run(); - }); - }, - } as StorageAdapter; + }, + }) + .run(); + }); + }; } diff --git a/packages/store-sync/src/sqlite/syncToSqlite.ts b/packages/store-sync/src/sqlite/syncToSqlite.ts index 2da2185bf9..210b9677d0 100644 --- a/packages/store-sync/src/sqlite/syncToSqlite.ts +++ b/packages/store-sync/src/sqlite/syncToSqlite.ts @@ -14,14 +14,14 @@ type SyncToSqliteOptions = SyncOption startSync?: boolean; }; -type SyncToSqliteResult = SyncResult & { +type SyncToSqliteResult = SyncResult & { stopSync: () => void; }; /** * Creates an indexer to process and store blockchain events. * - * @param {CreateIndexerOptions} options See `CreateIndexerOptions`. + * @param {SyncToSqliteOptions} options See `SyncToSqliteOptions`. * @returns A function to unsubscribe from the block stream, effectively stopping the indexer. */ export async function syncToSqlite({ @@ -34,7 +34,7 @@ export async function syncToSqlite({ indexerUrl, initialState, startSync = true, -}: SyncToSqliteOptions): Promise> { +}: SyncToSqliteOptions): Promise { const storeSync = await createStoreSync({ storageAdapter: await sqliteStorage({ database, publicClient, config }), config, @@ -46,7 +46,7 @@ export async function syncToSqlite({ initialState, }); - const sub = startSync ? storeSync.blockStorageOperations$.subscribe() : null; + const sub = startSync ? storeSync.storedBlockLogs$.subscribe() : null; const stopSync = (): void => { sub?.unsubscribe(); }; diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 06772c450b..68427b3528 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -119,41 +119,71 @@ "name": "validate field layout", "gasUsed": 3944 }, + { + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", + "name": "abi encode (static)", + "gasUsed": 133 + }, + { + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", + "name": "abi encode (dynamic)", + "gasUsed": 849 + }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "abi encode", - "gasUsed": 949 + "gasUsed": 943 }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "abi decode", - "gasUsed": 1738 + "gasUsed": 1741 + }, + { + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", + "name": "custom encode (static)", + "gasUsed": 191 + }, + { + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", + "name": "custom encode (length)", + "gasUsed": 141 + }, + { + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", + "name": "custom encode (dynamic)", + "gasUsed": 1152 }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "custom encode", - "gasUsed": 1394 + "gasUsed": 2065 }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "custom decode", - "gasUsed": 1976 + "gasUsed": 1981 }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "pass abi encoded bytes to external contract", - "gasUsed": 6550 + "gasUsed": 6562 }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "pass custom encoded bytes to external contract", - "gasUsed": 1444 + "gasUsed": 1451 }, { "file": "test/Gas.t.sol", @@ -309,7 +339,7 @@ "file": "test/KeyEncoding.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register KeyEncoding table", - "gasUsed": 697905 + "gasUsed": 697932 }, { "file": "test/Mixed.t.sol", @@ -321,19 +351,19 @@ "file": "test/Mixed.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register Mixed table", - "gasUsed": 560027 + "gasUsed": 560113 }, { "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "set record in Mixed", - "gasUsed": 108640 + "gasUsed": 108795 }, { "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "get record from Mixed", - "gasUsed": 9640 + "gasUsed": 9639 }, { "file": "test/PackedCounter.t.sol", @@ -567,25 +597,25 @@ "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromSecondField", "name": "pop from field (cold, 1 slot, 1 uint32 item)", - "gasUsed": 22898 + "gasUsed": 23281 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromSecondField", "name": "pop from field (warm, 1 slot, 1 uint32 item)", - "gasUsed": 16928 + "gasUsed": 17317 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromThirdField", "name": "pop from field (cold, 2 slots, 10 uint32 items)", - "gasUsed": 24647 + "gasUsed": 25478 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromThirdField", "name": "pop from field (warm, 2 slots, 10 uint32 items)", - "gasUsed": 16678 + "gasUsed": 17514 }, { "file": "test/StoreCoreGas.t.sol", @@ -639,67 +669,67 @@ "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "register subscriber", - "gasUsed": 61935 + "gasUsed": 62552 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set record on table with subscriber", - "gasUsed": 73748 + "gasUsed": 76175 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set static field on table with subscriber", - "gasUsed": 25441 + "gasUsed": 25942 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "delete record on table with subscriber", - "gasUsed": 21483 + "gasUsed": 21493 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "register subscriber", - "gasUsed": 61935 + "gasUsed": 62552 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) record on table with subscriber", - "gasUsed": 167808 + "gasUsed": 169944 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) field on table with subscriber", - "gasUsed": 28269 + "gasUsed": 29579 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "delete (dynamic) record on table with subscriber", - "gasUsed": 22901 + "gasUsed": 22878 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (1 slot, 1 uint32 item)", - "gasUsed": 14558 + "gasUsed": 15223 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (2 slots, 10 uint32 items)", - "gasUsed": 37206 + "gasUsed": 37997 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: register table", - "gasUsed": 620356 + "gasUsed": 620426 }, { "file": "test/StoreCoreGas.t.sol", @@ -723,7 +753,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "set complex record with dynamic data (4 slots)", - "gasUsed": 104856 + "gasUsed": 104895 }, { "file": "test/StoreCoreGas.t.sol", @@ -735,7 +765,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "compare: Set complex record with dynamic data using native solidity", - "gasUsed": 116842 + "gasUsed": 116845 }, { "file": "test/StoreCoreGas.t.sol", @@ -765,7 +795,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (1 slot)", - "gasUsed": 33831 + "gasUsed": 34114 }, { "file": "test/StoreCoreGas.t.sol", @@ -777,7 +807,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (overlap 2 slot)", - "gasUsed": 32479 + "gasUsed": 32761 }, { "file": "test/StoreCoreGas.t.sol", @@ -789,7 +819,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, first dynamic field)", - "gasUsed": 55195 + "gasUsed": 55894 }, { "file": "test/StoreCoreGas.t.sol", @@ -801,7 +831,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, second dynamic field)", - "gasUsed": 33313 + "gasUsed": 34134 }, { "file": "test/StoreCoreGas.t.sol", @@ -813,7 +843,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "set static record (1 slot)", - "gasUsed": 34181 + "gasUsed": 34695 }, { "file": "test/StoreCoreGas.t.sol", @@ -825,7 +855,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "set static record (2 slots)", - "gasUsed": 56684 + "gasUsed": 57199 }, { "file": "test/StoreCoreGas.t.sol", @@ -837,25 +867,25 @@ "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "update in field (1 slot, 1 uint32 item)", - "gasUsed": 14835 + "gasUsed": 16261 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "push to field (2 slots, 6 uint64 items)", - "gasUsed": 15865 + "gasUsed": 17082 }, { "file": "test/StoreHook.t.sol", "test": "testCallHook", "name": "call an enabled hook", - "gasUsed": 10157 + "gasUsed": 14598 }, { "file": "test/StoreHook.t.sol", "test": "testCallHook", "name": "call a disabled hook", - "gasUsed": 144 + "gasUsed": 133 }, { "file": "test/StoreHook.t.sol", @@ -891,7 +921,7 @@ "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "Callbacks: set field", - "gasUsed": 59331 + "gasUsed": 60015 }, { "file": "test/tables/Callbacks.t.sol", @@ -903,19 +933,19 @@ "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "Callbacks: push 1 element", - "gasUsed": 39001 + "gasUsed": 39345 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testOneSlot", "name": "StoreHooks: set field with one elements (cold)", - "gasUsed": 61323 + "gasUsed": 61997 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: set field (cold)", - "gasUsed": 61323 + "gasUsed": 61997 }, { "file": "test/tables/StoreHooks.t.sol", @@ -927,49 +957,49 @@ "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: push 1 element (cold)", - "gasUsed": 19075 + "gasUsed": 19410 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: pop 1 element (warm)", - "gasUsed": 15453 + "gasUsed": 15844 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: push 1 element (warm)", - "gasUsed": 17134 + "gasUsed": 17479 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: update 1 element (warm)", - "gasUsed": 37483 + "gasUsed": 38603 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: delete record (warm)", - "gasUsed": 10992 + "gasUsed": 10994 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTable", "name": "StoreHooks: set field (warm)", - "gasUsed": 33544 + "gasUsed": 34250 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testThreeSlots", "name": "StoreHooks: set field with three elements (cold)", - "gasUsed": 84011 + "gasUsed": 84688 }, { "file": "test/tables/StoreHooks.t.sol", "test": "testTwoSlots", "name": "StoreHooks: set field with two elements (cold)", - "gasUsed": 83923 + "gasUsed": 84600 }, { "file": "test/tables/StoreHooksColdLoad.t.sol", @@ -999,13 +1029,13 @@ "file": "test/tables/StoreHooksColdLoad.t.sol", "test": "testPop", "name": "StoreHooks: pop 1 element (cold)", - "gasUsed": 25580 + "gasUsed": 26291 }, { "file": "test/tables/StoreHooksColdLoad.t.sol", "test": "testUpdate", "name": "StoreHooks: update 1 element (cold)", - "gasUsed": 27157 + "gasUsed": 28565 }, { "file": "test/tightcoder/DecodeSlice.t.sol", @@ -1059,13 +1089,13 @@ "file": "test/Vector2.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register Vector2 field layout", - "gasUsed": 421491 + "gasUsed": 421554 }, { "file": "test/Vector2.t.sol", "test": "testSetAndGet", "name": "set Vector2 record", - "gasUsed": 36809 + "gasUsed": 37413 }, { "file": "test/Vector2.t.sol", diff --git a/packages/store/src/IStore.sol b/packages/store/src/IStore.sol index 68df303f9e..95bf0e301f 100644 --- a/packages/store/src/IStore.sol +++ b/packages/store/src/IStore.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import { IStoreErrors } from "./IStoreErrors.sol"; +import { PackedCounter } from "./PackedCounter.sol"; import { FieldLayout } from "./FieldLayout.sol"; import { Schema } from "./Schema.sol"; import { IStoreHook } from "./IStoreHook.sol"; @@ -24,7 +25,7 @@ interface IStoreRead { function getField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) external view returns (bytes memory data); @@ -32,7 +33,7 @@ interface IStoreRead { function getFieldLength( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) external view returns (uint256); @@ -40,7 +41,7 @@ interface IStoreRead { function getFieldSlice( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout, uint256 start, uint256 end @@ -48,15 +49,32 @@ interface IStoreRead { } interface IStoreWrite { - event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data); - event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 schemaIndex, bytes data); + event StoreSetRecord( + bytes32 tableId, + bytes32[] keyTuple, + bytes staticData, + bytes32 encodedLengths, + bytes dynamicData + ); + + event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data); + event StoreSpliceDynamicData( + bytes32 tableId, + bytes32[] keyTuple, + uint48 start, + uint40 deleteCount, + bytes data, + bytes32 encodedLengths + ); event StoreDeleteRecord(bytes32 tableId, bytes32[] keyTuple); // Set full record (including full dynamic data) function setRecord( bytes32 tableId, bytes32[] calldata keyTuple, - bytes calldata data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) external; @@ -64,7 +82,7 @@ interface IStoreWrite { function setField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes calldata data, FieldLayout fieldLayout ) external; @@ -73,7 +91,7 @@ interface IStoreWrite { function pushToField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes calldata dataToPush, FieldLayout fieldLayout ) external; @@ -82,7 +100,7 @@ interface IStoreWrite { function popFromField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 byteLengthToPop, FieldLayout fieldLayout ) external; @@ -91,7 +109,7 @@ interface IStoreWrite { function updateInField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 startByteIndex, bytes calldata dataToSet, FieldLayout fieldLayout @@ -102,13 +120,21 @@ interface IStoreWrite { } interface IStoreEphemeral { - event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data); + event StoreEphemeralRecord( + bytes32 tableId, + bytes32[] keyTuple, + bytes staticData, + bytes32 encodedLengths, + bytes dynamicData + ); // Emit the ephemeral event without modifying storage function emitEphemeralRecord( bytes32 tableId, bytes32[] calldata keyTuple, - bytes calldata data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) external; } diff --git a/packages/store/src/IStoreErrors.sol b/packages/store/src/IStoreErrors.sol index 8763723b5d..e5214d19cd 100644 --- a/packages/store/src/IStoreErrors.sol +++ b/packages/store/src/IStoreErrors.sol @@ -8,7 +8,8 @@ interface IStoreErrors { error StoreCore_NotImplemented(); error StoreCore_NotDynamicField(); - error StoreCore_InvalidDataLength(uint256 expected, uint256 received); + error StoreCore_InvalidStaticDataLength(uint256 expected, uint256 received); + error StoreCore_InvalidDynamicDataLength(uint256 expected, uint256 received); error StoreCore_InvalidKeyNamesLength(uint256 expected, uint256 received); error StoreCore_InvalidFieldNamesLength(uint256 expected, uint256 received); error StoreCore_InvalidValueSchemaLength(uint256 expected, uint256 received); diff --git a/packages/store/src/IStoreHook.sol b/packages/store/src/IStoreHook.sol index fa65ff4cff..c3132fcea7 100644 --- a/packages/store/src/IStoreHook.sol +++ b/packages/store/src/IStoreHook.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.0; import { FieldLayout } from "./FieldLayout.sol"; import { IERC165, ERC165_INTERFACE_ID } from "./IERC165.sol"; +import { PackedCounter } from "./PackedCounter.sol"; // ERC-165 Interface ID (see https://eips.ethereum.org/EIPS/eip-165) bytes4 constant STORE_HOOK_INTERFACE_ID = IStoreHook.onBeforeSetRecord.selector ^ @@ -17,21 +18,25 @@ interface IStoreHook is IERC165 { function onBeforeSetRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) external; function onAfterSetRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) external; function onBeforeSetField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data, FieldLayout fieldLayout ) external; @@ -39,7 +44,7 @@ interface IStoreHook is IERC165 { function onAfterSetField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data, FieldLayout fieldLayout ) external; diff --git a/packages/store/src/StoreCore.sol b/packages/store/src/StoreCore.sol index c2b81a57a8..6187beb974 100644 --- a/packages/store/src/StoreCore.sol +++ b/packages/store/src/StoreCore.sol @@ -17,10 +17,30 @@ import { StoreHookLib, StoreHookType } from "./StoreHook.sol"; library StoreCore { // note: the preimage of the tuple of keys used to index is part of the event, so it can be used by indexers - event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data); - event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data); + event StoreSetRecord( + bytes32 tableId, + bytes32[] keyTuple, + bytes staticData, + bytes32 encodedLengths, + bytes dynamicData + ); + event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data); + event StoreSpliceDynamicData( + bytes32 tableId, + bytes32[] keyTuple, + uint48 start, + uint40 deleteCount, + bytes data, + bytes32 encodedLengths + ); event StoreDeleteRecord(bytes32 tableId, bytes32[] keyTuple); - event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data); + event StoreEphemeralRecord( + bytes32 tableId, + bytes32[] keyTuple, + bytes staticData, + bytes32 encodedLengths, + bytes dynamicData + ); /** * Intialize the store address to use in StoreSwitch. @@ -101,6 +121,7 @@ library StoreCore { ) internal { // Verify the field layout is valid fieldLayout.validate({ allowEmpty: false }); + // Verify the schema is valid keySchema.validate({ allowEmpty: true }); valueSchema.validate({ allowEmpty: false }); @@ -114,6 +135,7 @@ library StoreCore { if (fieldNames.length != fieldLayout.numFields()) { revert IStoreErrors.StoreCore_InvalidFieldNamesLength(fieldLayout.numFields(), fieldNames.length); } + // Verify the number of value schema types if (valueSchema.numFields() != fieldLayout.numFields()) { revert IStoreErrors.StoreCore_InvalidValueSchemaLength(fieldLayout.numFields(), valueSchema.numFields()); @@ -164,48 +186,64 @@ library StoreCore { /** * Set full data record for the given tableId and key tuple and field layout */ - function setRecord(bytes32 tableId, bytes32[] memory keyTuple, bytes memory data, FieldLayout fieldLayout) internal { - // verify the value has the correct length for the table (based on the table's field layout) + function setRecord( + bytes32 tableId, + bytes32[] memory keyTuple, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, + FieldLayout fieldLayout + ) internal { + // verify the value has the correct length for the tableId (based on the tableId's field layout) // to prevent invalid data from being stored // Verify static data length + dynamic data length matches the given data - (uint256 staticLength, PackedCounter dynamicLength) = StoreCoreInternal._validateDataLength(fieldLayout, data); + StoreCoreInternal._validateDataLength(fieldLayout, staticData, encodedLengths, dynamicData); // Emit event to notify indexers - emit StoreSetRecord(tableId, keyTuple, data); + emit StoreSetRecord(tableId, keyTuple, staticData, encodedLengths.unwrap(), dynamicData); // Call onBeforeSetRecord hooks (before actually modifying the state, so observers have access to the previous state if needed) bytes21[] memory hooks = StoreHooks.get(tableId); for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.BEFORE_SET_RECORD))) { - IStoreHook(hook.getAddress()).onBeforeSetRecord(tableId, keyTuple, data, fieldLayout); + IStoreHook(hook.getAddress()).onBeforeSetRecord( + tableId, + keyTuple, + staticData, + encodedLengths, + dynamicData, + fieldLayout + ); } } // Store the static data at the static data location uint256 staticDataLocation = StoreCoreInternal._getStaticDataLocation(tableId, keyTuple); - uint256 memoryPointer = Memory.dataPointer(data); + uint256 memoryPointer = Memory.dataPointer(staticData); Storage.store({ storagePointer: staticDataLocation, offset: 0, memoryPointer: memoryPointer, - length: staticLength + length: staticData.length }); - memoryPointer += staticLength + 32; // move the memory pointer to the start of the dynamic data (skip the encoded dynamic length) // Set the dynamic data if there are dynamic fields if (fieldLayout.numDynamicFields() > 0) { // Store the dynamic data length at the dynamic data length location uint256 dynamicDataLengthLocation = StoreCoreInternal._getDynamicDataLengthLocation(tableId, keyTuple); - Storage.store({ storagePointer: dynamicDataLengthLocation, data: dynamicLength.unwrap() }); + Storage.store({ storagePointer: dynamicDataLengthLocation, data: encodedLengths.unwrap() }); + + // Move the memory pointer to the start of the dynamic data + memoryPointer = Memory.dataPointer(dynamicData); // For every dynamic element, slice off the dynamic data and store it at the dynamic location uint256 dynamicDataLocation; uint256 dynamicDataLength; for (uint8 i; i < fieldLayout.numDynamicFields(); ) { dynamicDataLocation = StoreCoreInternal._getDynamicDataLocation(tableId, keyTuple, i); - dynamicDataLength = dynamicLength.atIndex(i); + dynamicDataLength = encodedLengths.atIndex(i); Storage.store({ storagePointer: dynamicDataLocation, offset: 0, @@ -223,7 +261,14 @@ library StoreCore { for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.AFTER_SET_RECORD))) { - IStoreHook(hook.getAddress()).onAfterSetRecord(tableId, keyTuple, data, fieldLayout); + IStoreHook(hook.getAddress()).onAfterSetRecord( + tableId, + keyTuple, + staticData, + encodedLengths, + dynamicData, + fieldLayout + ); } } } @@ -234,33 +279,30 @@ library StoreCore { function setField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data, FieldLayout fieldLayout ) internal { - // Emit event to notify indexers - emit StoreSetField(tableId, keyTuple, schemaIndex, data); - // Call onBeforeSetField hooks (before modifying the state) bytes21[] memory hooks = StoreHooks.get(tableId); for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.BEFORE_SET_FIELD))) { - IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, schemaIndex, data, fieldLayout); + IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, fieldIndex, data, fieldLayout); } } - if (schemaIndex < fieldLayout.numStaticFields()) { - StoreCoreInternal._setStaticField(tableId, keyTuple, fieldLayout, schemaIndex, data); + if (fieldIndex < fieldLayout.numStaticFields()) { + StoreCoreInternal._setStaticField(tableId, keyTuple, fieldLayout, fieldIndex, data); } else { - StoreCoreInternal._setDynamicField(tableId, keyTuple, fieldLayout, schemaIndex, data); + StoreCoreInternal._setDynamicField(tableId, keyTuple, fieldLayout, fieldIndex, data); } // Call onAfterSetField hooks (after modifying the state) for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.AFTER_SET_FIELD))) { - IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, schemaIndex, data, fieldLayout); + IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, fieldIndex, data, fieldLayout); } } } @@ -306,39 +348,36 @@ library StoreCore { function pushToField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory dataToPush, FieldLayout fieldLayout ) internal { - if (schemaIndex < fieldLayout.numStaticFields()) { + if (fieldIndex < fieldLayout.numStaticFields()) { revert IStoreErrors.StoreCore_NotDynamicField(); } - // TODO add push-specific event and hook to avoid the storage read? (https://github.com/latticexyz/mud/issues/444) + // TODO add push-specific hook to avoid the storage read? (https://github.com/latticexyz/mud/issues/444) bytes memory fullData = abi.encodePacked( - StoreCoreInternal._getDynamicField(tableId, keyTuple, schemaIndex, fieldLayout), + StoreCoreInternal._getDynamicField(tableId, keyTuple, fieldIndex, fieldLayout), dataToPush ); - // Emit event to notify indexers - emit StoreSetField(tableId, keyTuple, schemaIndex, fullData); - // Call onBeforeSetField hooks (before modifying the state) bytes21[] memory hooks = StoreHooks.get(tableId); for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.BEFORE_SET_FIELD))) { - IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, schemaIndex, fullData, fieldLayout); + IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, fieldIndex, fullData, fieldLayout); } } - StoreCoreInternal._pushToDynamicField(tableId, keyTuple, fieldLayout, schemaIndex, dataToPush); + StoreCoreInternal._pushToDynamicField(tableId, keyTuple, fieldLayout, fieldIndex, dataToPush); // Call onAfterSetField hooks (after modifying the state) for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.AFTER_SET_FIELD))) { - IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, schemaIndex, fullData, fieldLayout); + IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, fieldIndex, fullData, fieldLayout); } } } @@ -349,40 +388,37 @@ library StoreCore { function popFromField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 byteLengthToPop, FieldLayout fieldLayout ) internal { - if (schemaIndex < fieldLayout.numStaticFields()) { + if (fieldIndex < fieldLayout.numStaticFields()) { revert IStoreErrors.StoreCore_NotDynamicField(); } - // TODO add pop-specific event and hook to avoid the storage read? (https://github.com/latticexyz/mud/issues/444) + // TODO add pop-specific hook to avoid the storage read? (https://github.com/latticexyz/mud/issues/444) bytes memory fullData; { - bytes memory oldData = StoreCoreInternal._getDynamicField(tableId, keyTuple, schemaIndex, fieldLayout); + bytes memory oldData = StoreCoreInternal._getDynamicField(tableId, keyTuple, fieldIndex, fieldLayout); fullData = SliceLib.getSubslice(oldData, 0, oldData.length - byteLengthToPop).toBytes(); } - // Emit event to notify indexers - emit StoreSetField(tableId, keyTuple, schemaIndex, fullData); - // Call onBeforeSetField hooks (before modifying the state) bytes21[] memory hooks = StoreHooks.get(tableId); for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.BEFORE_SET_FIELD))) { - IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, schemaIndex, fullData, fieldLayout); + IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, fieldIndex, fullData, fieldLayout); } } - StoreCoreInternal._popFromDynamicField(tableId, keyTuple, fieldLayout, schemaIndex, byteLengthToPop); + StoreCoreInternal._popFromDynamicField(tableId, keyTuple, fieldLayout, fieldIndex, byteLengthToPop); // Call onAfterSetField hooks (after modifying the state) for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.AFTER_SET_FIELD))) { - IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, schemaIndex, fullData, fieldLayout); + IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, fieldIndex, fullData, fieldLayout); } } } @@ -393,24 +429,25 @@ library StoreCore { function updateInField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 startByteIndex, bytes memory dataToSet, FieldLayout fieldLayout ) internal { - if (schemaIndex < fieldLayout.numStaticFields()) { + if (fieldIndex < fieldLayout.numStaticFields()) { revert IStoreErrors.StoreCore_NotDynamicField(); } + // index must be checked because it could be arbitrarily large // (but dataToSet.length can be unchecked - it won't overflow into another slot due to gas costs and hashed slots) if (startByteIndex > type(uint40).max) { revert IStoreErrors.StoreCore_DataIndexOverflow(type(uint40).max, startByteIndex); } - // TODO add setItem-specific event and hook to avoid the storage read? (https://github.com/latticexyz/mud/issues/444) + // TODO add setItem-specific hook to avoid the storage read? (https://github.com/latticexyz/mud/issues/444) bytes memory fullData; { - bytes memory oldData = StoreCoreInternal._getDynamicField(tableId, keyTuple, schemaIndex, fieldLayout); + bytes memory oldData = StoreCoreInternal._getDynamicField(tableId, keyTuple, fieldIndex, fieldLayout); fullData = abi.encodePacked( SliceLib.getSubslice(oldData, 0, startByteIndex).toBytes(), dataToSet, @@ -418,25 +455,22 @@ library StoreCore { ); } - // Emit event to notify indexers - emit StoreSetField(tableId, keyTuple, schemaIndex, fullData); - // Call onBeforeSetField hooks (before modifying the state) bytes21[] memory hooks = StoreHooks.get(tableId); for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.BEFORE_SET_FIELD))) { - IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, schemaIndex, fullData, fieldLayout); + IStoreHook(hook.getAddress()).onBeforeSetField(tableId, keyTuple, fieldIndex, fullData, fieldLayout); } } - StoreCoreInternal._setDynamicFieldItem(tableId, keyTuple, fieldLayout, schemaIndex, startByteIndex, dataToSet); + StoreCoreInternal._setDynamicFieldItem(tableId, keyTuple, fieldLayout, fieldIndex, startByteIndex, dataToSet); // Call onAfterSetField hooks (after modifying the state) for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); if (hook.isEnabled(uint8(StoreHookType.AFTER_SET_FIELD))) { - IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, schemaIndex, fullData, fieldLayout); + IStoreHook(hook.getAddress()).onAfterSetField(tableId, keyTuple, fieldIndex, fullData, fieldLayout); } } } @@ -453,14 +487,16 @@ library StoreCore { function emitEphemeralRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, FieldLayout fieldLayout ) internal { // Verify static data length + dynamic data length matches the given data - StoreCoreInternal._validateDataLength(fieldLayout, data); + StoreCoreInternal._validateDataLength(fieldLayout, staticData, encodedLengths, dynamicData); // Emit event to notify indexers - emit StoreEphemeralRecord(tableId, keyTuple, data); + emit StoreEphemeralRecord(tableId, keyTuple, staticData, encodedLengths.unwrap(), dynamicData); } /************************************************************************ @@ -501,6 +537,7 @@ library StoreCore { // Early return if there are no dynamic fields if (dynamicDataLength.total() == 0) return data; + // Advance memoryPointer to the dynamic data section memoryPointer += staticLength; @@ -516,6 +553,7 @@ library StoreCore { uint256 dynamicDataLocation = StoreCoreInternal._getDynamicDataLocation(tableId, keyTuple, i); uint256 length = dynamicDataLength.atIndex(i); Storage.load({ storagePointer: dynamicDataLocation, length: length, offset: 0, memoryPointer: memoryPointer }); + // Advance memoryPointer by the length of this dynamic field memoryPointer += length; } @@ -530,13 +568,13 @@ library StoreCore { function getField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) internal view returns (bytes memory) { - if (schemaIndex < fieldLayout.numStaticFields()) { - return StoreCoreInternal._getStaticField(tableId, keyTuple, schemaIndex, fieldLayout); + if (fieldIndex < fieldLayout.numStaticFields()) { + return StoreCoreInternal._getStaticField(tableId, keyTuple, fieldIndex, fieldLayout); } else { - return StoreCoreInternal._getDynamicField(tableId, keyTuple, schemaIndex, fieldLayout); + return StoreCoreInternal._getDynamicField(tableId, keyTuple, fieldIndex, fieldLayout); } } @@ -546,15 +584,15 @@ library StoreCore { function getFieldLength( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) internal view returns (uint256) { uint8 numStaticFields = uint8(fieldLayout.numStaticFields()); - if (schemaIndex < numStaticFields) { - return fieldLayout.atIndex(schemaIndex); + if (fieldIndex < numStaticFields) { + return fieldLayout.atIndex(fieldIndex); } else { // Get the length and storage location of the dynamic field - uint8 dynamicFieldLayoutIndex = schemaIndex - numStaticFields; + uint8 dynamicFieldLayoutIndex = fieldIndex - numStaticFields; return StoreCoreInternal._loadEncodedDynamicDataLength(tableId, keyTuple).atIndex(dynamicFieldLayoutIndex); } } @@ -566,18 +604,18 @@ library StoreCore { function getFieldSlice( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout, uint256 start, uint256 end ) internal view returns (bytes memory) { uint8 numStaticFields = uint8(fieldLayout.numStaticFields()); - if (schemaIndex < fieldLayout.numStaticFields()) { + if (fieldIndex < fieldLayout.numStaticFields()) { revert IStoreErrors.StoreCore_NotDynamicField(); } // Get the length and storage location of the dynamic field - uint8 dynamicSchemaIndex = schemaIndex - numStaticFields; + uint8 dynamicSchemaIndex = fieldIndex - numStaticFields; uint256 location = StoreCoreInternal._getDynamicDataLocation(tableId, keyTuple, dynamicSchemaIndex); return Storage.load({ storagePointer: location, length: end - start, offset: start }); @@ -597,46 +635,76 @@ library StoreCoreInternal { bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data ) internal { - // verify the value has the correct length for the field - uint256 staticByteLength = fieldLayout.atIndex(schemaIndex); - if (staticByteLength != data.length) { - revert IStoreErrors.StoreCore_InvalidDataLength(staticByteLength, data.length); - } - - // Store the provided value in storage uint256 location = _getStaticDataLocation(tableId, keyTuple); - uint256 offset = _getStaticDataOffset(fieldLayout, schemaIndex); + uint256 offset = _getStaticDataOffset(fieldLayout, fieldIndex); + Storage.store({ storagePointer: location, offset: offset, data: data }); + + // Emit event to notify indexers + emit StoreCore.StoreSpliceStaticData({ + tableId: tableId, + keyTuple: keyTuple, + start: uint48(offset), + deleteCount: uint40(fieldLayout.atIndex(fieldIndex)), + data: data + }); } function _setDynamicField( bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data ) internal { - uint8 dynamicSchemaIndex = schemaIndex - uint8(fieldLayout.numStaticFields()); + uint8 dynamicSchemaIndex = fieldIndex - uint8(fieldLayout.numStaticFields()); - // Update the dynamic data length - _setDynamicDataLengthAtIndex(tableId, keyTuple, dynamicSchemaIndex, data.length); + // Load dynamic data length from storage + uint256 dynamicSchemaLengthSlot = _getDynamicDataLengthLocation(tableId, keyTuple); + PackedCounter encodedLengths = PackedCounter.wrap(Storage.load({ storagePointer: dynamicSchemaLengthSlot })); + + // Update the encoded length + uint256 oldFieldLength = encodedLengths.atIndex(dynamicSchemaIndex); + encodedLengths = encodedLengths.setAtIndex(dynamicSchemaIndex, data.length); + + // Set the new lengths + Storage.store({ storagePointer: dynamicSchemaLengthSlot, data: encodedLengths.unwrap() }); // Store the provided value in storage uint256 dynamicDataLocation = _getDynamicDataLocation(tableId, keyTuple, dynamicSchemaIndex); Storage.store({ storagePointer: dynamicDataLocation, offset: 0, data: data }); + + // Compute start index for the splice event + uint256 start; + unchecked { + // (safe because it's a few uint40 values, which can't overflow uint48) + for (uint8 i; i < dynamicSchemaIndex; i++) { + start += encodedLengths.atIndex(i); + } + } + + // Emit event to notify indexers + emit StoreCore.StoreSpliceDynamicData({ + tableId: tableId, + keyTuple: keyTuple, + start: uint48(start), + deleteCount: uint40(oldFieldLength), + data: data, + encodedLengths: encodedLengths.unwrap() + }); } function _pushToDynamicField( bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory dataToPush ) internal { - uint8 dynamicSchemaIndex = schemaIndex - uint8(fieldLayout.numStaticFields()); + uint8 dynamicSchemaIndex = fieldIndex - uint8(fieldLayout.numStaticFields()); // Load dynamic data length from storage uint256 dynamicDataLengthSlot = _getDynamicDataLengthLocation(tableId, keyTuple); @@ -651,16 +719,35 @@ library StoreCoreInternal { // Append `dataToPush` to the end of the data in storage _setPartialDynamicData(tableId, keyTuple, dynamicSchemaIndex, oldFieldLength, dataToPush); + + // Compute start index for the splice event + uint256 start = oldFieldLength; + unchecked { + // (safe because it's a few uint40 values, which can't overflow uint48) + for (uint8 i; i < dynamicSchemaIndex; i++) { + start += encodedLengths.atIndex(i); + } + } + + // Emit event to notify indexers + emit StoreCore.StoreSpliceDynamicData({ + tableId: tableId, + keyTuple: keyTuple, + start: uint48(start), + deleteCount: uint40(0), + data: dataToPush, + encodedLengths: encodedLengths.unwrap() + }); } function _popFromDynamicField( bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout, - uint8 schemaIndex, + uint8 fieldIndex, uint256 byteLengthToPop ) internal { - uint8 dynamicSchemaIndex = schemaIndex - uint8(fieldLayout.numStaticFields()); + uint8 dynamicSchemaIndex = fieldIndex - uint8(fieldLayout.numStaticFields()); // Load dynamic data length from storage uint256 dynamicDataLengthSlot = _getDynamicDataLengthLocation(tableId, keyTuple); @@ -674,6 +761,27 @@ library StoreCoreInternal { Storage.store({ storagePointer: dynamicDataLengthSlot, data: encodedLengths.unwrap() }); // Data can be left unchanged, push/set do not assume storage to be 0s + + // Compute start index for the splice event + uint256 start; + unchecked { + // (safe because it's a few uint40 values, which can't overflow uint48) + start = oldFieldLength; + for (uint8 i; i < dynamicSchemaIndex; i++) { + start += encodedLengths.atIndex(i); + } + start -= byteLengthToPop; + } + + // Emit event to notify indexers + emit StoreCore.StoreSpliceDynamicData({ + tableId: tableId, + keyTuple: keyTuple, + start: uint48(start), + deleteCount: uint40(byteLengthToPop), + data: new bytes(0), + encodedLengths: encodedLengths.unwrap() + }); } // startOffset is measured in bytes @@ -681,14 +789,38 @@ library StoreCoreInternal { bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout, - uint8 schemaIndex, + uint8 fieldIndex, uint256 startByteIndex, bytes memory dataToSet ) internal { - uint8 dynamicSchemaIndex = schemaIndex - uint8(fieldLayout.numStaticFields()); + uint8 dynamicSchemaIndex = fieldIndex - uint8(fieldLayout.numStaticFields()); + + // Load dynamic data length from storage + uint256 dynamicSchemaLengthSlot = _getDynamicDataLengthLocation(tableId, keyTuple); + PackedCounter encodedLengths = PackedCounter.wrap(Storage.load({ storagePointer: dynamicSchemaLengthSlot })); // Set `dataToSet` at the given index _setPartialDynamicData(tableId, keyTuple, dynamicSchemaIndex, startByteIndex, dataToSet); + + // Compute start index for the splice event + uint256 start; + unchecked { + // (safe because it's a few uint40 values, which can't overflow uint48) + start = startByteIndex; + for (uint8 i; i < dynamicSchemaIndex; i++) { + start += encodedLengths.atIndex(i); + } + } + + // Emit event to notify indexers + emit StoreCore.StoreSpliceDynamicData({ + tableId: tableId, + keyTuple: keyTuple, + start: uint48(start), + deleteCount: uint40(dataToSet.length), + data: dataToSet, + encodedLengths: encodedLengths.unwrap() + }); } /************************************************************************ @@ -719,16 +851,15 @@ library StoreCoreInternal { function _getStaticField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) internal view returns (bytes memory) { // Get the length, storage location and offset of the static field - uint256 staticByteLength = fieldLayout.atIndex(schemaIndex); + uint256 staticByteLength = fieldLayout.atIndex(fieldIndex); uint256 location = _getStaticDataLocation(tableId, keyTuple); - uint256 offset = _getStaticDataOffset(fieldLayout, schemaIndex); + uint256 offset = _getStaticDataOffset(fieldLayout, fieldIndex); // Load the data from storage - return Storage.load({ storagePointer: location, length: staticByteLength, offset: offset }); } @@ -738,11 +869,11 @@ library StoreCoreInternal { function _getDynamicField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) internal view returns (bytes memory) { // Get the length and storage location of the dynamic field - uint8 dynamicSchemaIndex = schemaIndex - uint8(fieldLayout.numStaticFields()); + uint8 dynamicSchemaIndex = fieldIndex - uint8(fieldLayout.numStaticFields()); uint256 location = _getDynamicDataLocation(tableId, keyTuple, dynamicSchemaIndex); uint256 dataLength = _loadEncodedDynamicDataLength(tableId, keyTuple).atIndex(dynamicSchemaIndex); @@ -761,19 +892,15 @@ library StoreCoreInternal { */ function _validateDataLength( FieldLayout fieldLayout, - bytes memory data - ) internal pure returns (uint256 staticLength, PackedCounter dynamicLength) { - staticLength = fieldLayout.staticDataLength(); - uint256 expectedLength = staticLength; - dynamicLength; - if (fieldLayout.numDynamicFields() > 0) { - // Dynamic length is encoded at the start of the dynamic length blob - dynamicLength = PackedCounter.wrap(Bytes.slice32(data, staticLength)); - expectedLength += 32 + dynamicLength.total(); // encoded length + data + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData + ) internal pure { + if (fieldLayout.staticDataLength() != staticData.length) { + revert IStoreErrors.StoreCore_InvalidStaticDataLength(fieldLayout.staticDataLength(), staticData.length); } - - if (expectedLength != data.length) { - revert IStoreErrors.StoreCore_InvalidDataLength(expectedLength, data.length); + if (encodedLengths.total() != dynamicData.length) { + revert IStoreErrors.StoreCore_InvalidDynamicDataLength(encodedLengths.total(), dynamicData.length); } } @@ -791,9 +918,9 @@ library StoreCoreInternal { /** * Get storage offset for the given value field layout and (static length) index */ - function _getStaticDataOffset(FieldLayout fieldLayout, uint8 schemaIndex) internal pure returns (uint256) { + function _getStaticDataOffset(FieldLayout fieldLayout, uint8 fieldIndex) internal pure returns (uint256) { uint256 offset = 0; - for (uint256 i; i < schemaIndex; i++) { + for (uint256 i; i < fieldIndex; i++) { offset += fieldLayout.atIndex(i); } return offset; @@ -809,9 +936,9 @@ library StoreCoreInternal { function _getDynamicDataLocation( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex + uint8 fieldIndex ) internal pure returns (uint256) { - return uint256(keccak256(abi.encode(SLOT, tableId, keyTuple, schemaIndex))); + return uint256(keccak256(abi.encode(SLOT, tableId, keyTuple, fieldIndex))); } /** @@ -839,7 +966,7 @@ library StoreCoreInternal { function _setDynamicDataLengthAtIndex( bytes32 tableId, bytes32[] memory keyTuple, - uint8 dynamicSchemaIndex, // schemaIndex - numStaticFields + uint8 dynamicSchemaIndex, // fieldIndex - numStaticFields uint256 newLengthAtIndex ) internal { // Load dynamic data length from storage @@ -866,6 +993,7 @@ library StoreCoreInternal { uint256 dynamicDataLocation = _getDynamicDataLocation(tableId, keyTuple, dynamicSchemaIndex); // start index is in bytes, whereas storage slots are in 32-byte words dynamicDataLocation += startByteIndex / 32; + // partial storage slot offset (there is no inherent offset, as each dynamic field starts at its own storage slot) uint256 offset = startByteIndex % 32; Storage.store({ storagePointer: dynamicDataLocation, offset: offset, data: partialData }); diff --git a/packages/store/src/StoreRead.sol b/packages/store/src/StoreRead.sol index ced24eb297..92125f5bd0 100644 --- a/packages/store/src/StoreRead.sol +++ b/packages/store/src/StoreRead.sol @@ -32,29 +32,29 @@ contract StoreRead is IStoreRead { function getField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) public view virtual returns (bytes memory data) { - data = StoreCore.getField(tableId, keyTuple, schemaIndex, fieldLayout); + data = StoreCore.getField(tableId, keyTuple, fieldIndex, fieldLayout); } function getFieldLength( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout ) public view virtual returns (uint256) { - return StoreCore.getFieldLength(tableId, keyTuple, schemaIndex, fieldLayout); + return StoreCore.getFieldLength(tableId, keyTuple, fieldIndex, fieldLayout); } function getFieldSlice( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, FieldLayout fieldLayout, uint256 start, uint256 end ) public view virtual returns (bytes memory) { - return StoreCore.getFieldSlice(tableId, keyTuple, schemaIndex, fieldLayout, start, end); + return StoreCore.getFieldSlice(tableId, keyTuple, fieldIndex, fieldLayout, start, end); } } diff --git a/packages/store/src/StoreSwitch.sol b/packages/store/src/StoreSwitch.sol index 86aa1e311d..ed3e243b0e 100644 --- a/packages/store/src/StoreSwitch.sol +++ b/packages/store/src/StoreSwitch.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import { IStore } from "./IStore.sol"; +import { PackedCounter } from "../src/PackedCounter.sol"; import { IStoreHook } from "./IStoreHook.sol"; import { StoreCore } from "./StoreCore.sol"; import { Schema } from "./Schema.sol"; @@ -107,12 +108,19 @@ library StoreSwitch { } } - function setRecord(bytes32 tableId, bytes32[] memory keyTuple, bytes memory data, FieldLayout fieldLayout) internal { + function setRecord( + bytes32 tableId, + bytes32[] memory keyTuple, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, + FieldLayout fieldLayout + ) internal { address _storeAddress = getStoreAddress(); if (_storeAddress == address(this)) { - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } else { - IStore(_storeAddress).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(_storeAddress).setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } } @@ -189,14 +197,23 @@ library StoreSwitch { function emitEphemeralRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, FieldLayout fieldLayout ) internal { address _storeAddress = getStoreAddress(); if (_storeAddress == address(this)) { - StoreCore.emitEphemeralRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.emitEphemeralRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } else { - IStore(_storeAddress).emitEphemeralRecord(tableId, keyTuple, data, fieldLayout); + IStore(_storeAddress).emitEphemeralRecord( + tableId, + keyTuple, + staticData, + encodedLengths, + dynamicData, + fieldLayout + ); } } diff --git a/packages/store/src/codegen/tables/Callbacks.sol b/packages/store/src/codegen/tables/Callbacks.sol index 0126d86c18..5a370f8a20 100644 --- a/packages/store/src/codegen/tables/Callbacks.sol +++ b/packages/store/src/codegen/tables/Callbacks.sol @@ -230,15 +230,26 @@ library Callbacks { } } - /** Tightly pack full data using this table's field layout */ - function encode(bytes24[] memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(bytes24[] memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(value.length * 24); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(bytes24[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(bytes24[] memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Hooks.sol b/packages/store/src/codegen/tables/Hooks.sol index 242d5c9730..4d12e0babb 100644 --- a/packages/store/src/codegen/tables/Hooks.sol +++ b/packages/store/src/codegen/tables/Hooks.sol @@ -227,15 +227,26 @@ library Hooks { } } - /** Tightly pack full data using this table's field layout */ - function encode(bytes21[] memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(bytes21[] memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(value.length * 21); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(bytes21[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(bytes21[] memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/KeyEncoding.sol b/packages/store/src/codegen/tables/KeyEncoding.sol index f2344cf382..f8db386345 100644 --- a/packages/store/src/codegen/tables/KeyEncoding.sol +++ b/packages/store/src/codegen/tables/KeyEncoding.sol @@ -166,9 +166,19 @@ library KeyEncoding { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bool value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(bool value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Mixed.sol b/packages/store/src/codegen/tables/Mixed.sol index 22f664c8e1..9be293952b 100644 --- a/packages/store/src/codegen/tables/Mixed.sol +++ b/packages/store/src/codegen/tables/Mixed.sol @@ -475,22 +475,28 @@ library Mixed { /** Set the full data using individual values */ function set(bytes32 key, uint32 u32, uint128 u128, uint32[] memory a32, string memory s) internal { - bytes memory _data = encode(u32, u128, a32, s); + bytes memory _staticData = encodeStatic(u32, u128); + + PackedCounter _encodedLengths = encodeLengths(a32, s); + bytes memory _dynamicData = encodeDynamic(a32, s); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, bytes32 key, uint32 u32, uint128 u128, uint32[] memory a32, string memory s) internal { - bytes memory _data = encode(u32, u128, a32, s); + bytes memory _staticData = encodeStatic(u32, u128); + + PackedCounter _encodedLengths = encodeLengths(a32, s); + bytes memory _dynamicData = encodeDynamic(a32, s); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -533,15 +539,32 @@ library Mixed { } } - /** Tightly pack full data using this table's field layout */ - function encode(uint32 u32, uint128 u128, uint32[] memory a32, string memory s) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 u32, uint128 u128) internal pure returns (bytes memory) { + return abi.encodePacked(u32, u128); + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(uint32[] memory a32, string memory s) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(a32.length * 4, bytes(s).length); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(uint32[] memory a32, string memory s) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((a32)), bytes((s))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(uint32 u32, uint128 u128, uint32[] memory a32, string memory s) internal pure returns (bytes memory) { + bytes memory _staticData = encodeStatic(u32, u128); + + PackedCounter _encodedLengths = encodeLengths(a32, s); + bytes memory _dynamicData = encodeDynamic(a32, s); - return abi.encodePacked(u32, u128, _encodedLengths.unwrap(), EncodeArray.encode((a32)), bytes((s))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/StoreHooks.sol b/packages/store/src/codegen/tables/StoreHooks.sol index 9b84a3549e..fe82a2a292 100644 --- a/packages/store/src/codegen/tables/StoreHooks.sol +++ b/packages/store/src/codegen/tables/StoreHooks.sol @@ -230,15 +230,26 @@ library StoreHooks { } } - /** Tightly pack full data using this table's field layout */ - function encode(bytes21[] memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(bytes21[] memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(value.length * 21); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(bytes21[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(bytes21[] memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Tables.sol b/packages/store/src/codegen/tables/Tables.sol index fc82b0dedb..520bbd4497 100644 --- a/packages/store/src/codegen/tables/Tables.sol +++ b/packages/store/src/codegen/tables/Tables.sol @@ -534,12 +534,15 @@ library Tables { bytes memory abiEncodedKeyNames, bytes memory abiEncodedFieldNames ) internal { - bytes memory _data = encode(fieldLayout, keySchema, valueSchema, abiEncodedKeyNames, abiEncodedFieldNames); + bytes memory _staticData = encodeStatic(fieldLayout, keySchema, valueSchema); + + PackedCounter _encodedLengths = encodeLengths(abiEncodedKeyNames, abiEncodedFieldNames); + bytes memory _dynamicData = encodeDynamic(abiEncodedKeyNames, abiEncodedFieldNames); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = tableId; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ @@ -552,12 +555,15 @@ library Tables { bytes memory abiEncodedKeyNames, bytes memory abiEncodedFieldNames ) internal { - bytes memory _data = encode(fieldLayout, keySchema, valueSchema, abiEncodedKeyNames, abiEncodedFieldNames); + bytes memory _staticData = encodeStatic(fieldLayout, keySchema, valueSchema); + + PackedCounter _encodedLengths = encodeLengths(abiEncodedKeyNames, abiEncodedFieldNames); + bytes memory _dynamicData = encodeDynamic(abiEncodedKeyNames, abiEncodedFieldNames); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = tableId; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -617,29 +623,48 @@ library Tables { } } - /** Tightly pack full data using this table's field layout */ - function encode( + /** Tightly pack static data using this table's schema */ + function encodeStatic( bytes32 fieldLayout, bytes32 keySchema, - bytes32 valueSchema, + bytes32 valueSchema + ) internal pure returns (bytes memory) { + return abi.encodePacked(fieldLayout, keySchema, valueSchema); + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths( bytes memory abiEncodedKeyNames, bytes memory abiEncodedFieldNames - ) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + ) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(bytes(abiEncodedKeyNames).length, bytes(abiEncodedFieldNames).length); } + } - return - abi.encodePacked( - fieldLayout, - keySchema, - valueSchema, - _encodedLengths.unwrap(), - bytes((abiEncodedKeyNames)), - bytes((abiEncodedFieldNames)) - ); + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic( + bytes memory abiEncodedKeyNames, + bytes memory abiEncodedFieldNames + ) internal pure returns (bytes memory) { + return abi.encodePacked(bytes((abiEncodedKeyNames)), bytes((abiEncodedFieldNames))); + } + + /** Tightly pack full data using this table's field layout */ + function encode( + bytes32 fieldLayout, + bytes32 keySchema, + bytes32 valueSchema, + bytes memory abiEncodedKeyNames, + bytes memory abiEncodedFieldNames + ) internal pure returns (bytes memory) { + bytes memory _staticData = encodeStatic(fieldLayout, keySchema, valueSchema); + + PackedCounter _encodedLengths = encodeLengths(abiEncodedKeyNames, abiEncodedFieldNames); + bytes memory _dynamicData = encodeDynamic(abiEncodedKeyNames, abiEncodedFieldNames); + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Vector2.sol b/packages/store/src/codegen/tables/Vector2.sol index 82a58d420b..f71080845a 100644 --- a/packages/store/src/codegen/tables/Vector2.sol +++ b/packages/store/src/codegen/tables/Vector2.sol @@ -171,22 +171,28 @@ library Vector2 { /** Set the full data using individual values */ function set(bytes32 key, uint32 x, uint32 y) internal { - bytes memory _data = encode(x, y); + bytes memory _staticData = encodeStatic(x, y); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, bytes32 key, uint32 x, uint32 y) internal { - bytes memory _data = encode(x, y); + bytes memory _staticData = encodeStatic(x, y); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -206,9 +212,19 @@ library Vector2 { _table.y = (uint32(Bytes.slice4(_blob, 4))); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 x, uint32 y) internal pure returns (bytes memory) { + return abi.encodePacked(x, y); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 x, uint32 y) internal pure returns (bytes memory) { - return abi.encodePacked(x, y); + bytes memory _staticData = encodeStatic(x, y); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/test/EchoSubscriber.sol b/packages/store/test/EchoSubscriber.sol index 505502dcde..e416d1753c 100644 --- a/packages/store/test/EchoSubscriber.sol +++ b/packages/store/test/EchoSubscriber.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; +import { PackedCounter } from "../src/PackedCounter.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; import { StoreHook } from "../src/StoreHook.sol"; @@ -10,39 +11,43 @@ contract EchoSubscriber is StoreHook { function onBeforeSetRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, FieldLayout fieldLayout ) public { - emit HookCalled(abi.encode(tableId, keyTuple, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout)); } function onAfterSetRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, FieldLayout fieldLayout ) public { - emit HookCalled(abi.encode(tableId, keyTuple, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout)); } function onBeforeSetField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data, FieldLayout fieldLayout ) public { - emit HookCalled(abi.encode(tableId, keyTuple, schemaIndex, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, keyTuple, fieldIndex, data, fieldLayout)); } function onAfterSetField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data, FieldLayout fieldLayout ) public { - emit HookCalled(abi.encode(tableId, keyTuple, schemaIndex, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, keyTuple, fieldIndex, data, fieldLayout)); } function onBeforeDeleteRecord(bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout) public { diff --git a/packages/store/test/Gas.t.sol b/packages/store/test/Gas.t.sol index 884b019ebd..ea0a6299e9 100644 --- a/packages/store/test/Gas.t.sol +++ b/packages/store/test/Gas.t.sol @@ -6,6 +6,7 @@ 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 { PackedCounter } from "../src/PackedCounter.sol"; import { Mixed, MixedData } from "../src/codegen/Tables.sol"; contract SomeContract { @@ -21,6 +22,14 @@ contract GasTest is Test, GasReporter { mixed.a32[1] = 2; mixed.a32[2] = 3; + startGasReport("abi encode (static)"); + bytes memory abiEncodedStatic = abi.encode(mixed.u32, mixed.u128); + endGasReport(); + + startGasReport("abi encode (dynamic)"); + bytes memory abiEncodedDynamic = abi.encode(mixed.a32, mixed.s); + endGasReport(); + startGasReport("abi encode"); bytes memory abiEncoded = abi.encode(mixed); endGasReport(); @@ -29,6 +38,19 @@ contract GasTest is Test, GasReporter { MixedData memory abiDecoded = abi.decode(abiEncoded, (MixedData)); endGasReport(); + startGasReport("custom encode (static)"); + bytes memory customEncodedStatic = Mixed.encodeStatic(mixed.u32, mixed.u128); + endGasReport(); + + startGasReport("custom encode (length)"); + PackedCounter packedCounter = Mixed.encodeLengths(mixed.a32, mixed.s); + endGasReport(); + PackedCounter.unwrap(packedCounter); + + startGasReport("custom encode (dynamic)"); + bytes memory customEncodedDynamic = Mixed.encodeDynamic(mixed.a32, mixed.s); + endGasReport(); + startGasReport("custom encode"); bytes memory customEncoded = Mixed.encode(mixed.u32, mixed.u128, mixed.a32, mixed.s); endGasReport(); @@ -38,6 +60,16 @@ contract GasTest is Test, GasReporter { endGasReport(); console.log("Length comparison: abi encode %s, custom %s", abiEncoded.length, customEncoded.length); + console.log( + "Length comparison (static): abi encode %s, custom %s", + abiEncodedStatic.length, + customEncodedStatic.length + ); + console.log( + "Length comparison (dynamic): abi encode %s, custom %s", + abiEncodedDynamic.length, + customEncodedDynamic.length + ); startGasReport("pass abi encoded bytes to external contract"); someContract.doSomethingWithBytes(abiEncoded); diff --git a/packages/store/test/MirrorSubscriber.sol b/packages/store/test/MirrorSubscriber.sol index 34d6179682..38681feb23 100644 --- a/packages/store/test/MirrorSubscriber.sol +++ b/packages/store/test/MirrorSubscriber.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.0; import { IStore } from "../src/IStore.sol"; import { StoreHook } from "../src/StoreHook.sol"; +import { PackedCounter } from "../src/PackedCounter.sol"; import { StoreSwitch } from "../src/StoreSwitch.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; import { Schema } from "../src/Schema.sol"; @@ -27,17 +28,21 @@ contract MirrorSubscriber is StoreHook { function onBeforeSetRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) public { - if (tableId != tableId) revert("invalid tableId"); - StoreSwitch.setRecord(indexerTableId, keyTuple, data, fieldLayout); + if (tableId != _tableId) revert("invalid table"); + StoreSwitch.setRecord(indexerTableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } function onAfterSetRecord( bytes32 tableId, bytes32[] memory keyTuple, - bytes memory data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) public { // NOOP @@ -46,12 +51,12 @@ contract MirrorSubscriber is StoreHook { function onBeforeSetField( bytes32 tableId, bytes32[] memory keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes memory data, FieldLayout fieldLayout ) public { if (tableId != tableId) revert("invalid tableId"); - StoreSwitch.setField(indexerTableId, keyTuple, schemaIndex, data, fieldLayout); + StoreSwitch.setField(indexerTableId, keyTuple, fieldIndex, data, fieldLayout); } function onAfterSetField(bytes32, bytes32[] memory, uint8, bytes memory, FieldLayout) public { diff --git a/packages/store/test/PackedCounter.t.sol b/packages/store/test/PackedCounter.t.sol index 5da1cfcf2e..18b8bb14e9 100644 --- a/packages/store/test/PackedCounter.t.sol +++ b/packages/store/test/PackedCounter.t.sol @@ -102,4 +102,9 @@ contract PackedCounterTest is Test, GasReporter { endGasReport(); assertEq(packedCounter.total(), 1 * 32 + 2 * 20 + 3 * 1 + 4 * 16); } + + function testHexEncoding() public { + PackedCounter packedCounter = PackedCounterLib.pack(160, 544); + assertEq(packedCounter.unwrap(), hex"000000000000000000000000000000000000022000000000a0000000000002c0"); + } } diff --git a/packages/store/test/RevertSubscriber.sol b/packages/store/test/RevertSubscriber.sol index 2d157d9850..7c2ae092f7 100644 --- a/packages/store/test/RevertSubscriber.sol +++ b/packages/store/test/RevertSubscriber.sol @@ -1,15 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { FieldLayout } from "../src/FieldLayout.sol"; import { StoreHook } from "../src/StoreHook.sol"; +import { FieldLayout } from "../src/FieldLayout.sol"; +import { PackedCounter } from "../src/PackedCounter.sol"; contract RevertSubscriber is StoreHook { - function onBeforeSetRecord(bytes32, bytes32[] memory, bytes memory, FieldLayout) public pure { + function onBeforeSetRecord( + bytes32, + bytes32[] memory, + bytes memory, + PackedCounter, + bytes memory, + FieldLayout + ) public pure { revert("onBeforeSetRecord"); } - function onAfterSetRecord(bytes32, bytes32[] memory, bytes memory, FieldLayout) public pure { + function onAfterSetRecord( + bytes32, + bytes32[] memory, + bytes memory, + PackedCounter, + bytes memory, + FieldLayout + ) public pure { revert("onAfterSetRecord"); } diff --git a/packages/store/test/StoreCore.t.sol b/packages/store/test/StoreCore.t.sol index 6f502597f2..fc1f25134e 100644 --- a/packages/store/test/StoreCore.t.sol +++ b/packages/store/test/StoreCore.t.sol @@ -64,13 +64,9 @@ contract StoreCoreTest is Test, StoreMock { emit StoreSetRecord( TablesTableId, keyTuple, - Tables.encode( - fieldLayout.unwrap(), - keySchema.unwrap(), - valueSchema.unwrap(), - abi.encode(keyNames), - abi.encode(fieldNames) - ) + Tables.encodeStatic(fieldLayout.unwrap(), keySchema.unwrap(), valueSchema.unwrap()), + Tables.encodeLengths(abi.encode(keyNames), abi.encode(fieldNames)).unwrap(), + Tables.encodeDynamic(abi.encode(keyNames), abi.encode(fieldNames)) ); IStore(this).registerTable(tableId, fieldLayout, keySchema, valueSchema, keyNames, fieldNames); @@ -227,21 +223,21 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); // Set data - bytes memory data = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04), bytes2(0x0506)); + 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 StoreSetRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetRecord(tableId, keyTuple, data); + emit StoreSetRecord(tableId, keyTuple, staticData, bytes32(0), new bytes(0)); - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Get data bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); - assertTrue(Bytes.equals(data, loadedData)); + assertTrue(Bytes.equals(staticData, loadedData)); } function testFailSetAndGetStaticData() public { @@ -257,24 +253,26 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); // Set data - bytes memory data = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04)); + bytes memory staticData = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04)); bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = "some key"; // This should fail because the data is not 6 bytes long - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); } function testSetAndGetStaticDataSpanningWords() public { // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 0); - Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT128, SchemaType.UINT256); - bytes32 tableId = keccak256("some.tableId"); - IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](2)); + bytes32 tableId = keccak256("some.table"); + { + Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT128, SchemaType.UINT256); + IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](2)); + } // Set data - bytes memory data = abi.encodePacked( + bytes memory staticData = abi.encodePacked( bytes16(0x0102030405060708090a0b0c0d0e0f10), bytes32(0x1112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30) ); @@ -284,14 +282,14 @@ contract StoreCoreTest is Test, StoreMock { // Expect a StoreSetRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetRecord(tableId, keyTuple, data); + emit StoreSetRecord(tableId, keyTuple, staticData, bytes32(0), new bytes(0)); - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Get data bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); - assertTrue(Bytes.equals(data, loadedData)); + assertTrue(Bytes.equals(staticData, loadedData)); } function testSetAndGetDynamicData() public { @@ -299,12 +297,14 @@ contract StoreCoreTest is Test, StoreMock { // 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)); + { + 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); @@ -331,6 +331,8 @@ contract StoreCoreTest is Test, StoreMock { } // Concat data + bytes memory staticData = abi.encodePacked(firstDataBytes); + bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); bytes memory data = abi.encodePacked( firstDataBytes, encodedDynamicLength.unwrap(), @@ -344,10 +346,10 @@ contract StoreCoreTest is Test, StoreMock { // Expect a StoreSetRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetRecord(tableId, keyTuple, data); + emit StoreSetRecord(tableId, keyTuple, staticData, encodedDynamicLength.unwrap(), dynamicData); // Set data - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData, fieldLayout); // Get data bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); @@ -374,13 +376,15 @@ contract StoreCoreTest is Test, StoreMock { // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 2); - Schema valueSchema = SchemaEncodeHelper.encode( - SchemaType.UINT128, - SchemaType.UINT256, - SchemaType.UINT32_ARRAY, - SchemaType.UINT32_ARRAY - ); - IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); + { + Schema valueSchema = SchemaEncodeHelper.encode( + SchemaType.UINT128, + SchemaType.UINT256, + SchemaType.UINT32_ARRAY, + SchemaType.UINT32_ARRAY + ); + IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); + } bytes16 firstDataBytes = bytes16(0x0102030405060708090a0b0c0d0e0f10); @@ -390,9 +394,9 @@ contract StoreCoreTest is Test, StoreMock { bytes memory firstDataPacked = abi.encodePacked(firstDataBytes); - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceStaticData event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(tableId, keyTuple, 0, firstDataPacked); + emit StoreSpliceStaticData(tableId, keyTuple, 0, uint40(firstDataPacked.length), firstDataPacked); // Set first field IStore(this).setField(tableId, keyTuple, 0, firstDataPacked, fieldLayout); @@ -416,9 +420,15 @@ contract StoreCoreTest is Test, StoreMock { bytes memory secondDataPacked = abi.encodePacked(secondDataBytes); - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(tableId, keyTuple, 1, secondDataPacked); + emit StoreSpliceStaticData( + tableId, + keyTuple, + uint48(firstDataPacked.length), + uint40(secondDataPacked.length), + secondDataPacked + ); IStore(this).setField(tableId, keyTuple, 1, secondDataPacked, fieldLayout); @@ -463,9 +473,16 @@ contract StoreCoreTest is Test, StoreMock { fourthDataBytes = EncodeArray.encode(fourthData); } - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(tableId, keyTuple, 2, thirdDataBytes); + emit StoreSpliceDynamicData( + tableId, + keyTuple, + uint48(0), + 0, + thirdDataBytes, + PackedCounterLib.pack(thirdDataBytes.length, 0).unwrap() + ); // Set third field IStore(this).setField(tableId, keyTuple, 2, thirdDataBytes, fieldLayout); @@ -485,9 +502,16 @@ contract StoreCoreTest is Test, StoreMock { assertEq(bytes16(IStore(this).getField(tableId, keyTuple, 0, fieldLayout)), bytes16(firstDataBytes)); assertEq(bytes32(IStore(this).getField(tableId, keyTuple, 1, fieldLayout)), bytes32(secondDataBytes)); - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(tableId, keyTuple, 3, fourthDataBytes); + emit StoreSpliceDynamicData( + tableId, + keyTuple, + uint48(thirdDataBytes.length), + 0, + fourthDataBytes, + PackedCounterLib.pack(thirdDataBytes.length, fourthDataBytes.length).unwrap() + ); // Set fourth field IStore(this).setField(tableId, keyTuple, 3, fourthDataBytes, fieldLayout); @@ -507,6 +531,30 @@ contract StoreCoreTest is Test, StoreMock { abi.encodePacked(firstDataBytes, secondDataBytes, encodedLengths.unwrap(), thirdDataBytes, fourthDataBytes) ) ); + + // Set fourth field again, changing it to be equal to third field + // (non-zero deleteCount must be emitted when the array exists) + + // Expect a StoreSpliceRecord event to be emitted + vm.expectEmit(true, true, true, true); + emit StoreSpliceDynamicData( + tableId, + keyTuple, + uint48(thirdDataBytes.length), + uint40(fourthDataBytes.length), + thirdDataBytes, + PackedCounterLib.pack(thirdDataBytes.length, thirdDataBytes.length).unwrap() + ); + + // Set fourth field + IStore(this).setField(tableId, keyTuple, 3, thirdDataBytes, fieldLayout); + + // Get fourth field + loadedData = IStore(this).getField(tableId, keyTuple, 3, fieldLayout); + + // Verify loaded data is correct + assertEq(loadedData.length, thirdDataBytes.length); + assertEq(keccak256(loadedData), keccak256(thirdDataBytes)); } function testDeleteData() public { @@ -514,12 +562,14 @@ contract StoreCoreTest is Test, StoreMock { // 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)); + { + 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); @@ -546,6 +596,8 @@ contract StoreCoreTest is Test, StoreMock { } // Concat data + bytes memory staticData = abi.encodePacked(firstDataBytes); + bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); bytes memory data = abi.encodePacked( firstDataBytes, encodedDynamicLength.unwrap(), @@ -558,7 +610,7 @@ contract StoreCoreTest is Test, StoreMock { keyTuple[0] = bytes32("some key"); // Set data - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData, fieldLayout); // Get data bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); @@ -647,9 +699,16 @@ contract StoreCoreTest is Test, StoreMock { } data.newSecondDataBytes = abi.encodePacked(data.secondDataBytes, data.secondDataToPush); - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceDynamicData event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(data.tableId, data.keyTuple, 1, data.newSecondDataBytes); + emit StoreSpliceDynamicData( + data.tableId, + data.keyTuple, + uint48(data.secondDataBytes.length), + 0, + data.secondDataToPush, + PackedCounterLib.pack(data.newSecondDataBytes.length, data.thirdDataBytes.length).unwrap() + ); // Push to second field IStore(this).pushToField(data.tableId, data.keyTuple, 1, data.secondDataToPush, fieldLayout); @@ -683,9 +742,16 @@ contract StoreCoreTest is Test, StoreMock { } data.newThirdDataBytes = abi.encodePacked(data.thirdDataBytes, data.thirdDataToPush); - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(data.tableId, data.keyTuple, 2, data.newThirdDataBytes); + emit StoreSpliceDynamicData( + data.tableId, + data.keyTuple, + uint48(data.newSecondDataBytes.length + data.thirdDataBytes.length), + 0, + data.thirdDataToPush, + PackedCounterLib.pack(data.newSecondDataBytes.length, data.newThirdDataBytes.length).unwrap() + ); // Push to third field IStore(this).pushToField(data.tableId, data.keyTuple, 2, data.thirdDataToPush, fieldLayout); @@ -786,9 +852,16 @@ contract StoreCoreTest is Test, StoreMock { data.newSecondDataBytes = abi.encodePacked(data.secondData[0], _secondDataForUpdate[0]); } - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(data.tableId, data.keyTuple, 1, data.newSecondDataBytes); + emit StoreSpliceDynamicData( + data.tableId, + data.keyTuple, + uint48(4 * 1), + 4 * 1, + data.secondDataForUpdate, + PackedCounterLib.pack(data.newSecondDataBytes.length, data.thirdDataBytes.length).unwrap() + ); // Update index 1 in second field (4 = byte length of uint32) IStore(this).updateInField(data.tableId, data.keyTuple, 1, 4 * 1, data.secondDataForUpdate, fieldLayout); @@ -824,9 +897,16 @@ contract StoreCoreTest is Test, StoreMock { ); } - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(data.tableId, data.keyTuple, 2, data.newThirdDataBytes); + emit StoreSpliceDynamicData( + data.tableId, + data.keyTuple, + uint48(data.newSecondDataBytes.length + 8 * 1), + 8 * 4, + data.thirdDataForUpdate, + PackedCounterLib.pack(data.newSecondDataBytes.length, data.newThirdDataBytes.length).unwrap() + ); // Update indexes 1,2,3,4 in third field (8 = byte length of uint64) IStore(this).updateInField(data.tableId, data.keyTuple, 2, 8 * 1, data.thirdDataForUpdate, fieldLayout); @@ -910,21 +990,21 @@ contract StoreCoreTest is Test, StoreMock { }) ); - bytes memory data = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); + bytes memory staticData = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there bytes memory indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); - assertEq(keccak256(data), keccak256(indexedData)); + assertEq(keccak256(staticData), keccak256(indexedData)); - data = abi.encodePacked(bytes16(0x1112131415161718191a1b1c1d1e1f20)); + staticData = abi.encodePacked(bytes16(0x1112131415161718191a1b1c1d1e1f20)); - IStore(this).setField(tableId, keyTuple, 0, data, fieldLayout); + IStore(this).setField(tableId, keyTuple, 0, staticData, fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); - assertEq(keccak256(data), keccak256(indexedData)); + assertEq(keccak256(staticData), keccak256(indexedData)); IStore(this).deleteRecord(tableId, keyTuple, fieldLayout); @@ -978,7 +1058,7 @@ contract StoreCoreTest is Test, StoreMock { // Expect a revert when the RevertSubscriber's onBeforeSetRecord hook is called vm.expectRevert(bytes("onBeforeSetRecord")); - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, data, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Expect a revert when the RevertSubscriber's onBeforeSetField hook is called vm.expectRevert(bytes("onBeforeSetField")); @@ -993,13 +1073,13 @@ contract StoreCoreTest is Test, StoreMock { // Expect a HookCalled event to be emitted when the EchoSubscriber's onBeforeSetRecord hook is called vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, keyTuple, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, keyTuple, data, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout)); // Expect a HookCalled event to be emitted when the EchoSubscriber's onAfterSetRecord hook is called vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, keyTuple, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, keyTuple, data, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout)); - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, data, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Expect a HookCalled event to be emitted when the EchoSubscriber's onBeforeSetField hook is called vm.expectEmit(true, true, true, true); @@ -1059,11 +1139,11 @@ contract StoreCoreTest is Test, StoreMock { arrayData[0] = 0x01020304; bytes memory arrayDataBytes = EncodeArray.encode(arrayData); PackedCounter encodedArrayDataLength = PackedCounterLib.pack(uint40(arrayDataBytes.length)); - bytes memory dynamicData = abi.encodePacked(encodedArrayDataLength.unwrap(), arrayDataBytes); + bytes memory dynamicData = arrayDataBytes; bytes memory staticData = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); - bytes memory data = abi.encodePacked(staticData, dynamicData); + bytes memory data = abi.encodePacked(staticData, encodedArrayDataLength, dynamicData); - IStore(this).setRecord(tableId, keyTuple, data, fieldLayout); + IStore(this).setRecord(tableId, keyTuple, staticData, encodedArrayDataLength, dynamicData, fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there bytes memory indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); @@ -1072,8 +1152,8 @@ contract StoreCoreTest is Test, StoreMock { // Update dynamic data arrayData[0] = 0x11121314; arrayDataBytes = EncodeArray.encode(arrayData); - dynamicData = abi.encodePacked(encodedArrayDataLength.unwrap(), arrayDataBytes); - data = abi.encodePacked(staticData, dynamicData); + dynamicData = arrayDataBytes; + data = abi.encodePacked(staticData, encodedArrayDataLength, dynamicData); IStore(this).setField(tableId, keyTuple, 1, arrayDataBytes, fieldLayout); diff --git a/packages/store/test/StoreCoreDynamic.t.sol b/packages/store/test/StoreCoreDynamic.t.sol index 28c25130a0..d5979239c3 100644 --- a/packages/store/test/StoreCoreDynamic.t.sol +++ b/packages/store/test/StoreCoreDynamic.t.sol @@ -7,6 +7,7 @@ import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol" import { StoreCore } from "../src/StoreCore.sol"; import { SliceLib } from "../src/Slice.sol"; import { EncodeArray } from "../src/tightcoder/EncodeArray.sol"; +import { PackedCounterLib } from "../src/PackedCounter.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; import { Schema } from "../src/Schema.sol"; import { StoreMock } from "../test/StoreMock.sol"; @@ -29,11 +30,11 @@ contract StoreCoreDynamicTest is Test, GasReporter, StoreMock { function popFromField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 byteLengthToPop, FieldLayout fieldLayout ) public override { - StoreCore.popFromField(tableId, keyTuple, schemaIndex, byteLengthToPop, fieldLayout); + StoreCore.popFromField(tableId, keyTuple, fieldIndex, byteLengthToPop, fieldLayout); } function setUp() public { @@ -90,9 +91,16 @@ contract StoreCoreDynamicTest is Test, GasReporter, StoreMock { assertEq(SliceLib.fromBytes(dataBytes).decodeArray_uint32().length, 2); assertEq(SliceLib.fromBytes(newDataBytes).decodeArray_uint32().length, 2 - 1); - // Expect a StoreSetField event to be emitted + // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSetField(_tableId, _keyTuple, 1, newDataBytes); + emit StoreSpliceDynamicData( + _tableId, + _keyTuple, + uint48(secondDataBytes.length - byteLengthToPop), + uint40(byteLengthToPop), + new bytes(0), + PackedCounterLib.pack(newDataBytes.length, thirdDataBytes.length).unwrap() + ); // Pop from second field startGasReport("pop from field (cold, 1 slot, 1 uint32 item)"); @@ -129,9 +137,16 @@ contract StoreCoreDynamicTest is Test, GasReporter, StoreMock { assertEq(SliceLib.fromBytes(dataBytes).decodeArray_uint32().length, 10); assertEq(SliceLib.fromBytes(newDataBytes).decodeArray_uint32().length, 10 - 10); - // Expect a StoreSetField event to be emitted after pop + // Expect a StoreSpliceRecord event to be emitted after pop vm.expectEmit(true, true, true, true); - emit StoreSetField(_tableId, _keyTuple, 2, dataBytes); + emit StoreSpliceDynamicData( + _tableId, + _keyTuple, + uint48(secondDataBytes.length + thirdDataBytes.length - byteLengthToPop), + uint40(byteLengthToPop), + new bytes(0), + PackedCounterLib.pack(secondDataBytes.length, newDataBytes.length).unwrap() + ); // Pop from the field startGasReport("pop from field (cold, 2 slots, 10 uint32 items)"); diff --git a/packages/store/test/StoreCoreGas.t.sol b/packages/store/test/StoreCoreGas.t.sol index badbc33472..5c30514ccd 100644 --- a/packages/store/test/StoreCoreGas.t.sol +++ b/packages/store/test/StoreCoreGas.t.sol @@ -139,12 +139,13 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { StoreCore.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); // Set data - bytes memory data = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04), bytes2(0x0506)); + bytes memory staticData = abi.encodePacked(bytes1(0x01), bytes2(0x0203), bytes1(0x04), bytes2(0x0506)); + bytes memory dynamicData = new bytes(0); bytes32[] memory keyTuple = new bytes32[](1); - keyTuple[0] = "some key"; + keyTuple[0] = keccak256("some.key"); startGasReport("set static record (1 slot)"); - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), dynamicData, fieldLayout); endGasReport(); // Get data @@ -161,16 +162,17 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { StoreCore.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](2)); // Set data - bytes memory data = abi.encodePacked( + bytes memory staticData = abi.encodePacked( bytes16(0x0102030405060708090a0b0c0d0e0f10), bytes32(0x1112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30) ); + bytes memory dynamicData = new bytes(0); bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = "some key"; startGasReport("set static record (2 slots)"); - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), dynamicData, fieldLayout); endGasReport(); // Get data @@ -216,12 +218,8 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { ); // Concat data - bytes memory data = abi.encodePacked( - firstDataBytes, - encodedDynamicLength.unwrap(), - secondDataBytes, - thirdDataBytes - ); + bytes memory staticData = abi.encodePacked(firstDataBytes); + bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); // Create keyTuple bytes32[] memory keyTuple = new bytes32[](1); @@ -229,7 +227,7 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { // Set data startGasReport("set complex record with dynamic data (4 slots)"); - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData, fieldLayout); endGasReport(); // Get data @@ -382,19 +380,15 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { ); // Concat data - bytes memory data = abi.encodePacked( - firstDataBytes, - encodedDynamicLength.unwrap(), - secondDataBytes, - thirdDataBytes - ); + bytes memory staticData = abi.encodePacked(firstDataBytes); + bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); // Create keyTuple bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = "some key"; // Set data - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData, fieldLayout); // Delete data startGasReport("delete record (complex data, 3 slots)"); @@ -632,16 +626,17 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { ); endGasReport(); - bytes memory data = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); + bytes memory staticData = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); + bytes memory dynamicData = new bytes(0); startGasReport("set record on table with subscriber"); - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), dynamicData, fieldLayout); endGasReport(); - data = abi.encodePacked(bytes16(0x1112131415161718191a1b1c1d1e1f20)); + staticData = abi.encodePacked(bytes16(0x1112131415161718191a1b1c1d1e1f20)); startGasReport("set static field on table with subscriber"); - StoreCore.setField(tableId, keyTuple, 0, data, fieldLayout); + StoreCore.setField(tableId, keyTuple, 0, staticData, fieldLayout); endGasReport(); startGasReport("delete record on table with subscriber"); @@ -688,19 +683,19 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { arrayData[0] = 0x01020304; bytes memory arrayDataBytes = EncodeArray.encode(arrayData); PackedCounter encodedArrayDataLength = PackedCounterLib.pack(uint40(arrayDataBytes.length)); - bytes memory dynamicData = abi.encodePacked(encodedArrayDataLength.unwrap(), arrayDataBytes); + bytes memory dynamicData = arrayDataBytes; bytes memory staticData = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); - bytes memory data = abi.encodePacked(staticData, dynamicData); + bytes memory data = abi.encodePacked(staticData, encodedArrayDataLength, dynamicData); startGasReport("set (dynamic) record on table with subscriber"); - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, encodedArrayDataLength, dynamicData, fieldLayout); endGasReport(); // Update dynamic data arrayData[0] = 0x11121314; arrayDataBytes = EncodeArray.encode(arrayData); - dynamicData = abi.encodePacked(encodedArrayDataLength.unwrap(), arrayDataBytes); - data = abi.encodePacked(staticData, dynamicData); + dynamicData = arrayDataBytes; + data = abi.encodePacked(staticData, encodedArrayDataLength, dynamicData); startGasReport("set (dynamic) field on table with subscriber"); StoreCore.setField(tableId, keyTuple, 1, arrayDataBytes, fieldLayout); diff --git a/packages/store/test/StoreHook.t.sol b/packages/store/test/StoreHook.t.sol index 56950bd351..311dcbc90a 100644 --- a/packages/store/test/StoreHook.t.sol +++ b/packages/store/test/StoreHook.t.sol @@ -11,6 +11,7 @@ import { Hook } from "../src/Hook.sol"; import { StoreHookType } from "../src/StoreHook.sol"; import { StoreHookLib } from "../src/StoreHook.sol"; import { IStoreHook } from "../src/IStore.sol"; +import { PackedCounter } from "../src/PackedCounter.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; contract StoreHookTest is Test, GasReporter { @@ -21,7 +22,10 @@ contract StoreHookTest is Test, GasReporter { RevertSubscriber private revertSubscriber = new RevertSubscriber(); bytes32 private tableId = "table"; bytes32[] private key = new bytes32[](1); - bytes private data = "data"; + bytes private staticData = abi.encodePacked(bytes32(0)); + PackedCounter private encodedLengths = PackedCounter.wrap(bytes32(0)); + bytes private dynamicData = new bytes(0); + uint8 private fieldIndex = 1; FieldLayout private fieldLayout = FieldLayout.wrap(0); function testEncodeBitmap() public { @@ -278,11 +282,21 @@ contract StoreHookTest is Test, GasReporter { }) ); + // TODO temporary variable until https://github.com/foundry-rs/foundry/issues/5811 is fixed + bytes memory emptyDynamicData = new bytes(0); + vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, key, data, fieldLayout)); + emit HookCalled(abi.encode(tableId, key, staticData, encodedLengths, emptyDynamicData, fieldLayout)); startGasReport("call an enabled hook"); if (storeHook.isEnabled(uint8(StoreHookType.BEFORE_SET_RECORD))) { - IStoreHook(storeHook.getAddress()).onBeforeSetRecord(tableId, key, data, fieldLayout); + IStoreHook(storeHook.getAddress()).onBeforeSetRecord( + tableId, + key, + staticData, + encodedLengths, + dynamicData, + fieldLayout + ); } endGasReport(); @@ -301,7 +315,14 @@ contract StoreHookTest is Test, GasReporter { // Expect the to not be called - otherwise the test will fail with a revert startGasReport("call a disabled hook"); if (revertHook.isEnabled(uint8(StoreHookType.BEFORE_SET_RECORD))) { - IStoreHook(revertHook.getAddress()).onBeforeSetRecord(tableId, key, data, fieldLayout); + IStoreHook(revertHook.getAddress()).onBeforeSetRecord( + tableId, + key, + staticData, + encodedLengths, + dynamicData, + fieldLayout + ); } endGasReport(); } diff --git a/packages/store/test/StoreMock.sol b/packages/store/test/StoreMock.sol index 96e23f6664..9b515b4af5 100644 --- a/packages/store/test/StoreMock.sol +++ b/packages/store/test/StoreMock.sol @@ -2,8 +2,9 @@ pragma solidity >=0.8.0; import { IStore, IStoreHook } from "../src/IStore.sol"; -import { Schema } from "../src/Schema.sol"; +import { PackedCounter } from "../src/PackedCounter.sol"; import { StoreCore } from "../src/StoreCore.sol"; +import { Schema } from "../src/Schema.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; import { StoreRead } from "../src/StoreRead.sol"; @@ -20,55 +21,57 @@ contract StoreMock is IStore, StoreRead { function setRecord( bytes32 tableId, bytes32[] calldata keyTuple, - bytes calldata data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout - ) public virtual { - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + ) public { + StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } // Set partial data at schema index function setField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes calldata data, FieldLayout fieldLayout ) public virtual { - StoreCore.setField(tableId, keyTuple, schemaIndex, data, fieldLayout); + StoreCore.setField(tableId, keyTuple, fieldIndex, data, fieldLayout); } // Push encoded items to the dynamic field at schema index function pushToField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes calldata dataToPush, FieldLayout fieldLayout ) public virtual { - StoreCore.pushToField(tableId, keyTuple, schemaIndex, dataToPush, fieldLayout); + StoreCore.pushToField(tableId, keyTuple, fieldIndex, dataToPush, fieldLayout); } // Pop byte length from the dynamic field at schema index function popFromField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 byteLengthToPop, FieldLayout fieldLayout ) public virtual { - StoreCore.popFromField(tableId, keyTuple, schemaIndex, byteLengthToPop, fieldLayout); + StoreCore.popFromField(tableId, keyTuple, fieldIndex, byteLengthToPop, fieldLayout); } // Change encoded items within the dynamic field at schema index function updateInField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 startByteIndex, bytes calldata dataToSet, FieldLayout fieldLayout ) public virtual { - StoreCore.updateInField(tableId, keyTuple, schemaIndex, startByteIndex, dataToSet, fieldLayout); + StoreCore.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout); } // Set full record (including full dynamic data) @@ -80,10 +83,12 @@ contract StoreMock is IStore, StoreRead { function emitEphemeralRecord( bytes32 tableId, bytes32[] calldata keyTuple, - bytes calldata data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout - ) public virtual { - StoreCore.emitEphemeralRecord(tableId, keyTuple, data, fieldLayout); + ) public { + StoreCore.emitEphemeralRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } function registerTable( diff --git a/packages/store/ts/codegen/ephemeral.ts b/packages/store/ts/codegen/ephemeral.ts index c7fbc9ba4f..96182f65fb 100644 --- a/packages/store/ts/codegen/ephemeral.ts +++ b/packages/store/ts/codegen/ephemeral.ts @@ -1,5 +1,6 @@ import { renderArguments, renderCommonData, renderWithStore } from "@latticexyz/common/codegen"; import { RenderTableOptions } from "./types"; +import { renderRecordData } from "./record"; export function renderEphemeralMethods(options: RenderTableOptions) { const { structName, storeArgument } = options; @@ -15,11 +16,11 @@ export function renderEphemeralMethods(options: RenderTableOptions) { _typedKeyArgs, renderArguments(options.fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`)), ])}) internal { - bytes memory _data = encode(${renderArguments(options.fields.map(({ name }) => name))}); + ${renderRecordData(options)} ${_keyTupleDefinition} - ${_store}.emitEphemeralRecord(_tableId, _keyTuple, _data, getFieldLayout()); + ${_store}.emitEphemeralRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } ` ); diff --git a/packages/store/ts/codegen/record.ts b/packages/store/ts/codegen/record.ts index f334a01247..b13dfd25d7 100644 --- a/packages/store/ts/codegen/record.ts +++ b/packages/store/ts/codegen/record.ts @@ -38,11 +38,11 @@ export function renderRecordMethods(options: RenderTableOptions) { _typedKeyArgs, renderArguments(options.fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`)), ])}) internal { - bytes memory _data = encode(${renderArguments(options.fields.map(({ name }) => name))}); + ${renderRecordData(options)} ${_keyTupleDefinition} - ${_store}.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + ${_store}.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } ` ); @@ -74,6 +74,31 @@ export function renderRecordMethods(options: RenderTableOptions) { return result; } +export function renderRecordData(options: RenderTableOptions) { + let result = ""; + if (options.staticFields.length > 0) { + result += ` + bytes memory _staticData = encodeStatic(${renderArguments(options.staticFields.map(({ name }) => name))}); + `; + } else { + result += `bytes memory _staticData;`; + } + + if (options.dynamicFields.length > 0) { + result += ` + PackedCounter _encodedLengths = encodeLengths(${renderArguments(options.dynamicFields.map(({ name }) => name))}); + bytes memory _dynamicData = encodeDynamic(${renderArguments(options.dynamicFields.map(({ name }) => name))}); + `; + } else { + result += ` + PackedCounter _encodedLengths; + bytes memory _dynamicData; + `; + } + + return result; +} + // Renders the `decode` function that parses a bytes blob into the table data function renderDecodeFunction({ structName, fields, staticFields, dynamicFields }: RenderTableOptions) { // either set struct properties, or just variables @@ -94,63 +119,63 @@ function renderDecodeFunction({ structName, fields, staticFields, dynamicFields const totalStaticLength = staticFields.reduce((acc, { staticByteLength }) => acc + staticByteLength, 0); // decode static (optionally) and dynamic data return ` - /** - * Decode the tightly packed blob using this table's field layout. - * Undefined behaviour for invalid blobs. - */ - function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) { - // ${totalStaticLength} is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, ${totalStaticLength})); - - ${renderList( - staticFields, - (field, index) => ` - ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; - ` - )} - // Store trims the blob if dynamic fields are all empty - if (_blob.length > ${totalStaticLength}) { + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) { + // ${totalStaticLength} is the total byte length of static data + PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, ${totalStaticLength})); + ${renderList( - dynamicFields, - // unchecked is only dangerous if _encodedLengths (and _blob) is invalid, - // but it's assumed to be valid, and this function is meant to be mostly used internally - (field, index) => { - if (index === 0) { - return ` - // skip static data length + dynamic lengths word - uint256 _start = ${totalStaticLength + 32}; - uint256 _end; - unchecked { - _end = ${totalStaticLength + 32} + _encodedLengths.atIndex(${index}); - } - ${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)}; - `; - } else { - return ` - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(${index}); + staticFields, + (field, index) => ` + ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; + ` + )} + // Store trims the blob if dynamic fields are all empty + if (_blob.length > ${totalStaticLength}) { + ${renderList( + dynamicFields, + // unchecked is only dangerous if _encodedLengths (and _blob) is invalid, + // but it's assumed to be valid, and this function is meant to be mostly used internally + (field, index) => { + if (index === 0) { + return ` + // skip static data length + dynamic lengths word + uint256 _start = ${totalStaticLength + 32}; + uint256 _end; + unchecked { + _end = ${totalStaticLength + 32} + _encodedLengths.atIndex(${index}); + } + ${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)}; + `; + } else { + return ` + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(${index}); + } + ${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)}; + `; } - ${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)}; - `; } - } - )} + )} + } } - } - `; + `; } else { // decode only static data return ` - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) { - ${renderList( - staticFields, - (field, index) => ` - ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; - ` - )} - } + /** Decode the tightly packed blob using this table's field layout */ + function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) { + ${renderList( + staticFields, + (field, index) => ` + ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; + ` + )} + } `; } } diff --git a/packages/store/ts/codegen/renderTable.ts b/packages/store/ts/codegen/renderTable.ts index 8181133cfb..72816bf28f 100644 --- a/packages/store/ts/codegen/renderTable.ts +++ b/packages/store/ts/codegen/renderTable.ts @@ -8,10 +8,11 @@ import { renderWithStore, renderTypeHelpers, RenderDynamicField, + RenderStaticField, } from "@latticexyz/common/codegen"; import { renderEphemeralMethods } from "./ephemeral"; import { renderEncodeFieldSingle, renderFieldMethods } from "./field"; -import { renderRecordMethods } from "./record"; +import { renderRecordData, renderRecordMethods } from "./record"; import { RenderTableOptions } from "./types"; export function renderTable(options: RenderTableOptions) { @@ -113,11 +114,11 @@ export function renderTable(options: RenderTableOptions) { ${renderWithStore( storeArgument, (_typedStore, _store, _commentSuffix) => ` - /** Register the table with its config${_commentSuffix} */ - function register(${renderArguments([_typedStore, _typedTableId])}) internal { - ${_store}.registerTable(_tableId, getFieldLayout(), getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); - } - ` + /** Register the table with its config${_commentSuffix} */ + function register(${renderArguments([_typedStore, _typedTableId])}) internal { + ${_store}.registerTable(_tableId, getFieldLayout(), getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + ` )} ${withFieldMethods ? renderFieldMethods(options) : ""} @@ -126,20 +127,19 @@ export function renderTable(options: RenderTableOptions) { ${withEphemeralMethods ? renderEphemeralMethods(options) : ""} + ${renderEncodeStatic(staticFields)} + + ${renderEncodedLengths(dynamicFields)} + + ${renderEncodeDynamic(dynamicFields)} + /** Tightly pack full data using this table's field layout */ function encode(${renderArguments( options.fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) )}) internal pure returns (bytes memory) { - ${renderEncodedLengths(dynamicFields)} - return abi.encodePacked(${renderArguments([ - renderArguments(staticFields.map(({ name }) => name)), - ...(dynamicFields.length === 0 - ? [] - : [ - "_encodedLengths.unwrap()", - renderArguments(dynamicFields.map((field) => renderEncodeFieldSingle(field))), - ]), - ])}); + ${renderRecordData(options)} + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ @@ -168,26 +168,54 @@ export function renderTable(options: RenderTableOptions) { `; } +function renderEncodeStatic(staticFields: RenderStaticField[]) { + if (staticFields.length === 0) return ""; + + return ` + /** Tightly pack static data using this table's schema */ + function encodeStatic(${renderArguments( + staticFields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) + )}) internal pure returns (bytes memory) { + return abi.encodePacked(${renderArguments(staticFields.map(({ name }) => name))}); + } + `; +} + function renderEncodedLengths(dynamicFields: RenderDynamicField[]) { - if (dynamicFields.length > 0) { - return ` - PackedCounter _encodedLengths; - // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits - unchecked { - _encodedLengths = PackedCounterLib.pack( - ${renderArguments( - dynamicFields.map(({ name, arrayElement }) => { - if (arrayElement) { - return `${name}.length * ${arrayElement.staticByteLength}`; - } else { - return `bytes(${name}).length`; - } - }) - )} - ); + if (dynamicFields.length === 0) return ""; + + return ` + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(${renderArguments( + dynamicFields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) + )}) internal pure returns (PackedCounter _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = PackedCounterLib.pack( + ${renderArguments( + dynamicFields.map(({ name, arrayElement }) => { + if (arrayElement) { + return `${name}.length * ${arrayElement.staticByteLength}`; + } else { + return `bytes(${name}).length`; + } + }) + )} + ); + } + } + `; +} + +function renderEncodeDynamic(dynamicFields: RenderDynamicField[]) { + if (dynamicFields.length === 0) return ""; + + return ` + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(${renderArguments( + dynamicFields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) + )}) internal pure returns (bytes memory) { + return abi.encodePacked(${renderArguments(dynamicFields.map((field) => renderEncodeFieldSingle(field)))}); } - `; - } else { - return ""; - } + `; } diff --git a/packages/store/ts/storeEvents.ts b/packages/store/ts/storeEvents.ts index 77b308b8e2..4148e5cd68 100644 --- a/packages/store/ts/storeEvents.ts +++ b/packages/store/ts/storeEvents.ts @@ -1,6 +1,7 @@ export const storeEvents = [ + "event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "event StoreSpliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data)", + "event StoreSpliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data, bytes32 encodedLengths)", + "event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)", "event StoreDeleteRecord(bytes32 tableId, bytes32[] keyTuple)", - "event StoreSetField(bytes32 tableId, bytes32[] keyTuple, uint8 schemaIndex, bytes data)", - "event StoreSetRecord(bytes32 tableId, bytes32[] keyTuple, bytes data)", - "event StoreEphemeralRecord(bytes32 tableId, bytes32[] keyTuple, bytes data)", ] as const; diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 30962ed0d7..64b6bfac8c 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -39,73 +39,73 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1519335 + "gasUsed": 1521715 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1519335 + "gasUsed": 1521715 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", - "gasUsed": 183388 + "gasUsed": 187187 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1519335 + "gasUsed": 1521715 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1519335 + "gasUsed": 1521715 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "change a composite record on a table with keysInTableModule installed", - "gasUsed": 28498 + "gasUsed": 30396 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 251457 + "gasUsed": 256692 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1519335 + "gasUsed": 1521715 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "change a record on a table with keysInTableModule installed", - "gasUsed": 27221 + "gasUsed": 29117 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 132823 + "gasUsed": 134551 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 728346 + "gasUsed": 730803 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "Get list of keys with a given value", - "gasUsed": 7508 + "gasUsed": 7584 }, { "file": "test/KeysWithValueModule.t.sol", @@ -117,163 +117,163 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 728346 + "gasUsed": 730803 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", - "gasUsed": 157348 + "gasUsed": 159933 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 728346 + "gasUsed": 730803 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", - "gasUsed": 126291 + "gasUsed": 129535 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", - "gasUsed": 49099 + "gasUsed": 49133 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 728346 + "gasUsed": 730803 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", - "gasUsed": 163792 + "gasUsed": 164678 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", - "gasUsed": 128551 + "gasUsed": 129437 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueNotQuery", "name": "CombinedHasHasValueNotQuery", - "gasUsed": 141196 + "gasUsed": 141735 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueQuery", "name": "CombinedHasHasValueQuery", - "gasUsed": 69053 + "gasUsed": 69540 }, { "file": "test/query.t.sol", "test": "testCombinedHasNotQuery", "name": "CombinedHasNotQuery", - "gasUsed": 184150 + "gasUsed": 184268 }, { "file": "test/query.t.sol", "test": "testCombinedHasQuery", "name": "CombinedHasQuery", - "gasUsed": 121994 + "gasUsed": 122085 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueNotQuery", "name": "CombinedHasValueNotQuery", - "gasUsed": 117772 + "gasUsed": 117986 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueQuery", "name": "CombinedHasValueQuery", - "gasUsed": 19164 + "gasUsed": 19461 }, { "file": "test/query.t.sol", "test": "testHasQuery", "name": "HasQuery", - "gasUsed": 27950 + "gasUsed": 27973 }, { "file": "test/query.t.sol", "test": "testHasQuery1000Keys", "name": "HasQuery with 1000 keys", - "gasUsed": 7068010 + "gasUsed": 7118904 }, { "file": "test/query.t.sol", "test": "testHasQuery100Keys", "name": "HasQuery with 100 keys", - "gasUsed": 672230 + "gasUsed": 672769 }, { "file": "test/query.t.sol", "test": "testHasValueQuery", "name": "HasValueQuery", - "gasUsed": 9259 + "gasUsed": 9401 }, { "file": "test/query.t.sol", "test": "testNotValueQuery", "name": "NotValueQuery", - "gasUsed": 62652 + "gasUsed": 63136 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", - "gasUsed": 133325 + "gasUsed": 133856 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "call a system via a callbound delegation", - "gasUsed": 49383 + "gasUsed": 49405 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", - "gasUsed": 127681 + "gasUsed": 128205 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "call a system via a timebound delegation", - "gasUsed": 38474 + "gasUsed": 38496 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 787645 + "gasUsed": 790963 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", - "gasUsed": 67456 + "gasUsed": 67727 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 771167 + "gasUsed": 774507 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", - "gasUsed": 67456 + "gasUsed": 67727 }, { "file": "test/World.t.sol", @@ -285,96 +285,96 @@ "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 59044 + "gasUsed": 59312 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "call a system via an unlimited delegation", - "gasUsed": 19950 + "gasUsed": 19972 }, { "file": "test/World.t.sol", "test": "testDeleteRecord", "name": "Delete record", - "gasUsed": 13886 + "gasUsed": 13885 }, { "file": "test/World.t.sol", "test": "testPushToField", "name": "Push data to the table", - "gasUsed": 93261 + "gasUsed": 93919 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a fallback system", - "gasUsed": 75414 + "gasUsed": 75955 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a root fallback system", - "gasUsed": 68663 + "gasUsed": 69204 }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 96008 + "gasUsed": 96549 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 146378 + "gasUsed": 147155 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 90581 + "gasUsed": 91122 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 685344 + "gasUsed": 686517 }, { "file": "test/World.t.sol", "test": "testSetField", "name": "Write data to a table field", - "gasUsed": 41899 + "gasUsed": 42174 }, { "file": "test/World.t.sol", "test": "testSetRecord", "name": "Write data to the table", - "gasUsed": 41344 + "gasUsed": 41619 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (cold)", - "gasUsed": 32940 + "gasUsed": 32933 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (warm)", - "gasUsed": 19729 + "gasUsed": 20046 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (cold)", - "gasUsed": 35373 + "gasUsed": 36456 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (warm)", - "gasUsed": 22578 + "gasUsed": 23661 } ] diff --git a/packages/world/src/World.sol b/packages/world/src/World.sol index 1c80c16249..f813a96f46 100644 --- a/packages/world/src/World.sol +++ b/packages/world/src/World.sol @@ -6,6 +6,8 @@ import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; import { IStoreData } from "@latticexyz/store/src/IStore.sol"; import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; import { System } from "./System.sol"; @@ -98,14 +100,16 @@ contract World is StoreRead, IStoreData, IWorldKernel { function setRecord( bytes32 tableId, bytes32[] calldata keyTuple, - bytes calldata data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) public virtual { // Require access to the namespace or name AccessControl.requireAccess(tableId, msg.sender); // Set the record - StoreCore.setRecord(tableId, keyTuple, data, fieldLayout); + StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } /** @@ -115,7 +119,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { function setField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes calldata data, FieldLayout fieldLayout ) public virtual { @@ -123,7 +127,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { AccessControl.requireAccess(tableId, msg.sender); // Set the field - StoreCore.setField(tableId, keyTuple, schemaIndex, data, fieldLayout); + StoreCore.setField(tableId, keyTuple, fieldIndex, data, fieldLayout); } /** @@ -133,7 +137,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { function pushToField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, bytes calldata dataToPush, FieldLayout fieldLayout ) public virtual { @@ -141,7 +145,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { AccessControl.requireAccess(tableId, msg.sender); // Push to the field - StoreCore.pushToField(tableId, keyTuple, schemaIndex, dataToPush, fieldLayout); + StoreCore.pushToField(tableId, keyTuple, fieldIndex, dataToPush, fieldLayout); } /** @@ -151,7 +155,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { function popFromField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 byteLengthToPop, FieldLayout fieldLayout ) public virtual { @@ -159,7 +163,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { AccessControl.requireAccess(tableId, msg.sender); // Push to the field - StoreCore.popFromField(tableId, keyTuple, schemaIndex, byteLengthToPop, fieldLayout); + StoreCore.popFromField(tableId, keyTuple, fieldIndex, byteLengthToPop, fieldLayout); } /** @@ -169,7 +173,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { function updateInField( bytes32 tableId, bytes32[] calldata keyTuple, - uint8 schemaIndex, + uint8 fieldIndex, uint256 startByteIndex, bytes calldata dataToSet, FieldLayout fieldLayout @@ -178,7 +182,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { AccessControl.requireAccess(tableId, msg.sender); // Update data in the field - StoreCore.updateInField(tableId, keyTuple, schemaIndex, startByteIndex, dataToSet, fieldLayout); + StoreCore.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout); } /** diff --git a/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol b/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol index 5a41ebf30b..35f2309d8c 100644 --- a/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol +++ b/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol @@ -2,12 +2,12 @@ pragma solidity >=0.8.0; import { IStoreEphemeral } from "@latticexyz/store/src/IStore.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; -import { IModule } from "../../../interfaces/IModule.sol"; import { System } from "../../../System.sol"; import { ResourceSelector } from "../../../ResourceSelector.sol"; import { AccessControl } from "../../../AccessControl.sol"; -import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; contract EphemeralRecordSystem is IStoreEphemeral, System { using ResourceSelector for bytes32; @@ -19,13 +19,15 @@ contract EphemeralRecordSystem is IStoreEphemeral, System { function emitEphemeralRecord( bytes32 resourceSelector, bytes32[] calldata keyTuple, - bytes calldata data, + bytes calldata staticData, + PackedCounter encodedLengths, + bytes calldata dynamicData, FieldLayout fieldLayout ) public virtual { // Require access to the namespace or name AccessControl.requireAccess(resourceSelector, msg.sender); // Set the record - StoreCore.emitEphemeralRecord(resourceSelector, keyTuple, data, fieldLayout); + StoreCore.emitEphemeralRecord(resourceSelector, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout); } } diff --git a/packages/world/src/modules/core/tables/Balances.sol b/packages/world/src/modules/core/tables/Balances.sol index 76f03b4f3c..417d4b2751 100644 --- a/packages/world/src/modules/core/tables/Balances.sol +++ b/packages/world/src/modules/core/tables/Balances.sol @@ -109,9 +109,19 @@ library Balances { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((balance)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint256 balance) internal pure returns (bytes memory) { + return abi.encodePacked(balance); + } + /** Tightly pack full data using this table's field layout */ function encode(uint256 balance) internal pure returns (bytes memory) { - return abi.encodePacked(balance); + bytes memory _staticData = encodeStatic(balance); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/FunctionSelectors.sol b/packages/world/src/modules/core/tables/FunctionSelectors.sol index e3023b3273..152672637c 100644 --- a/packages/world/src/modules/core/tables/FunctionSelectors.sol +++ b/packages/world/src/modules/core/tables/FunctionSelectors.sol @@ -177,12 +177,15 @@ library FunctionSelectors { /** Set the full data using individual values */ function set(bytes4 functionSelector, bytes32 resourceSelector, bytes4 systemFunctionSelector) internal { - bytes memory _data = encode(resourceSelector, systemFunctionSelector); + bytes memory _staticData = encodeStatic(resourceSelector, systemFunctionSelector); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(functionSelector); - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ @@ -192,12 +195,15 @@ library FunctionSelectors { bytes32 resourceSelector, bytes4 systemFunctionSelector ) internal { - bytes memory _data = encode(resourceSelector, systemFunctionSelector); + bytes memory _staticData = encodeStatic(resourceSelector, systemFunctionSelector); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(functionSelector); - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Decode the tightly packed blob using this table's field layout */ @@ -207,9 +213,19 @@ library FunctionSelectors { systemFunctionSelector = (Bytes.slice4(_blob, 32)); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bytes32 resourceSelector, bytes4 systemFunctionSelector) internal pure returns (bytes memory) { + return abi.encodePacked(resourceSelector, systemFunctionSelector); + } + /** Tightly pack full data using this table's field layout */ function encode(bytes32 resourceSelector, bytes4 systemFunctionSelector) internal pure returns (bytes memory) { - return abi.encodePacked(resourceSelector, systemFunctionSelector); + bytes memory _staticData = encodeStatic(resourceSelector, systemFunctionSelector); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/ResourceType.sol b/packages/world/src/modules/core/tables/ResourceType.sol index 45ba98b6fb..ef1ce9ce3d 100644 --- a/packages/world/src/modules/core/tables/ResourceType.sol +++ b/packages/world/src/modules/core/tables/ResourceType.sol @@ -112,9 +112,19 @@ library ResourceType { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(Resource resourceType) internal pure returns (bytes memory) { + return abi.encodePacked(resourceType); + } + /** Tightly pack full data using this table's field layout */ function encode(Resource resourceType) internal pure returns (bytes memory) { - return abi.encodePacked(resourceType); + bytes memory _staticData = encodeStatic(resourceType); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/SystemHooks.sol b/packages/world/src/modules/core/tables/SystemHooks.sol index 0bb1d03e8a..d0503ff496 100644 --- a/packages/world/src/modules/core/tables/SystemHooks.sol +++ b/packages/world/src/modules/core/tables/SystemHooks.sol @@ -230,15 +230,26 @@ library SystemHooks { } } - /** Tightly pack full data using this table's field layout */ - function encode(bytes21[] memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(bytes21[] memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(value.length * 21); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(bytes21[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(bytes21[] memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/SystemRegistry.sol b/packages/world/src/modules/core/tables/SystemRegistry.sol index 1bae5fb67b..fa5e247567 100644 --- a/packages/world/src/modules/core/tables/SystemRegistry.sol +++ b/packages/world/src/modules/core/tables/SystemRegistry.sol @@ -109,9 +109,19 @@ library SystemRegistry { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((resourceSelector)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bytes32 resourceSelector) internal pure returns (bytes memory) { + return abi.encodePacked(resourceSelector); + } + /** Tightly pack full data using this table's field layout */ function encode(bytes32 resourceSelector) internal pure returns (bytes memory) { - return abi.encodePacked(resourceSelector); + bytes memory _staticData = encodeStatic(resourceSelector); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/Systems.sol b/packages/world/src/modules/core/tables/Systems.sol index fa6622d37e..52a9c0ad04 100644 --- a/packages/world/src/modules/core/tables/Systems.sol +++ b/packages/world/src/modules/core/tables/Systems.sol @@ -166,22 +166,28 @@ library Systems { /** Set the full data using individual values */ function set(bytes32 resourceSelector, address system, bool publicAccess) internal { - bytes memory _data = encode(system, publicAccess); + bytes memory _staticData = encodeStatic(system, publicAccess); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = resourceSelector; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, bytes32 resourceSelector, address system, bool publicAccess) internal { - bytes memory _data = encode(system, publicAccess); + bytes memory _staticData = encodeStatic(system, publicAccess); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = resourceSelector; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Decode the tightly packed blob using this table's field layout */ @@ -191,9 +197,19 @@ library Systems { publicAccess = (_toBool(uint8(Bytes.slice1(_blob, 20)))); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(address system, bool publicAccess) internal pure returns (bytes memory) { + return abi.encodePacked(system, publicAccess); + } + /** Tightly pack full data using this table's field layout */ function encode(address system, bool publicAccess) internal pure returns (bytes memory) { - return abi.encodePacked(system, publicAccess); + bytes memory _staticData = encodeStatic(system, publicAccess); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/keysintable/KeysInTableHook.sol b/packages/world/src/modules/keysintable/KeysInTableHook.sol index 03622ade95..4a9c518988 100644 --- a/packages/world/src/modules/keysintable/KeysInTableHook.sol +++ b/packages/world/src/modules/keysintable/KeysInTableHook.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; import { StoreHook } from "@latticexyz/store/src/StoreHook.sol"; @@ -40,11 +41,25 @@ contract KeysInTableHook is StoreHook { } } - function onBeforeSetRecord(bytes32 tableId, bytes32[] memory keyTuple, bytes memory, FieldLayout) public { + function onBeforeSetRecord( + bytes32 tableId, + bytes32[] memory keyTuple, + bytes memory, + PackedCounter, + bytes memory, + FieldLayout + ) public { handleSet(tableId, keyTuple); } - function onAfterSetRecord(bytes32 tableId, bytes32[] memory keyTuple, bytes memory, FieldLayout) public { + function onAfterSetRecord( + bytes32 tableId, + bytes32[] memory keyTuple, + bytes memory, + PackedCounter, + bytes memory, + FieldLayout + ) public { // NOOP } diff --git a/packages/world/src/modules/keysintable/query.sol b/packages/world/src/modules/keysintable/query.sol index 90946bc2c0..53769a113e 100644 --- a/packages/world/src/modules/keysintable/query.sol +++ b/packages/world/src/modules/keysintable/query.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { getKeysInTable } from "./getKeysInTable.sol"; import { getKeysWithValue } from "../keyswithvalue/getKeysWithValue.sol"; @@ -45,7 +46,13 @@ function passesQueryFragment(bytes32[] memory keyTuple, QueryFragment memory fra if (fragment.queryType == QueryType.HasValue) { // Key must have the given value - return ArrayLib.includes(valuesToTuples(getKeysWithValue(fragment.tableId, fragment.value)), keyTuple); + return + ArrayLib.includes( + valuesToTuples( + getKeysWithValue(fragment.tableId, fragment.value, PackedCounter.wrap(bytes32(0)), new bytes(0)) + ), + keyTuple + ); } if (fragment.queryType == QueryType.Not) { @@ -55,7 +62,13 @@ function passesQueryFragment(bytes32[] memory keyTuple, QueryFragment memory fra if (fragment.queryType == QueryType.NotValue) { // Key must not have the given value - return !ArrayLib.includes(valuesToTuples(getKeysWithValue(fragment.tableId, fragment.value)), keyTuple); + return + !ArrayLib.includes( + valuesToTuples( + getKeysWithValue(fragment.tableId, fragment.value, PackedCounter.wrap(bytes32(0)), new bytes(0)) + ), + keyTuple + ); } return false; @@ -78,7 +91,13 @@ function passesQueryFragment( if (fragment.queryType == QueryType.HasValue) { // Key must be have the given value - return ArrayLib.includes(valuesToTuples(getKeysWithValue(store, fragment.tableId, fragment.value)), keyTuple); + return + ArrayLib.includes( + valuesToTuples( + getKeysWithValue(store, fragment.tableId, fragment.value, PackedCounter.wrap(bytes32(0)), new bytes(0)) + ), + keyTuple + ); } if (fragment.queryType == QueryType.Not) { @@ -88,7 +107,13 @@ function passesQueryFragment( if (fragment.queryType == QueryType.NotValue) { // Key must not have the given value - return !ArrayLib.includes(valuesToTuples(getKeysWithValue(store, fragment.tableId, fragment.value)), keyTuple); + return + !ArrayLib.includes( + valuesToTuples( + getKeysWithValue(store, fragment.tableId, fragment.value, PackedCounter.wrap(bytes32(0)), new bytes(0)) + ), + keyTuple + ); } return false; @@ -108,7 +133,9 @@ function query(QueryFragment[] memory fragments) view returns (bytes32[][] memor // Create the first interim result keyTuples = fragments[0].queryType == QueryType.Has ? getKeysInTable(fragments[0].tableId) - : valuesToTuples(getKeysWithValue(fragments[0].tableId, fragments[0].value)); + : valuesToTuples( + getKeysWithValue(fragments[0].tableId, fragments[0].value, PackedCounter.wrap(bytes32(0)), new bytes(0)) + ); for (uint256 i = 1; i < fragments.length; i++) { bytes32[][] memory result = new bytes32[][](0); @@ -143,7 +170,9 @@ function query(IStore store, QueryFragment[] memory fragments) view returns (byt // Create the first interim result keyTuples = fragments[0].queryType == QueryType.Has ? getKeysInTable(store, fragments[0].tableId) - : valuesToTuples(getKeysWithValue(store, fragments[0].tableId, fragments[0].value)); + : valuesToTuples( + getKeysWithValue(store, fragments[0].tableId, fragments[0].value, PackedCounter.wrap(bytes32(0)), new bytes(0)) + ); for (uint256 i = 1; i < fragments.length; i++) { bytes32[][] memory result = new bytes32[][](0); diff --git a/packages/world/src/modules/keysintable/tables/KeysInTable.sol b/packages/world/src/modules/keysintable/tables/KeysInTable.sol index fe4a0ef4f6..ba91e6d7ae 100644 --- a/packages/world/src/modules/keysintable/tables/KeysInTable.sol +++ b/packages/world/src/modules/keysintable/tables/KeysInTable.sol @@ -897,12 +897,14 @@ library KeysInTable { bytes32[] memory keys3, bytes32[] memory keys4 ) internal { - bytes memory _data = encode(keys0, keys1, keys2, keys3, keys4); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(keys0, keys1, keys2, keys3, keys4); + bytes memory _dynamicData = encodeDynamic(keys0, keys1, keys2, keys3, keys4); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = sourceTable; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ @@ -915,12 +917,14 @@ library KeysInTable { bytes32[] memory keys3, bytes32[] memory keys4 ) internal { - bytes memory _data = encode(keys0, keys1, keys2, keys3, keys4); + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(keys0, keys1, keys2, keys3, keys4); + bytes memory _dynamicData = encodeDynamic(keys0, keys1, keys2, keys3, keys4); bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = sourceTable; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -977,15 +981,14 @@ library KeysInTable { } } - /** Tightly pack full data using this table's field layout */ - function encode( + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths( bytes32[] memory keys0, bytes32[] memory keys1, bytes32[] memory keys2, bytes32[] memory keys3, bytes32[] memory keys4 - ) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + ) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack( @@ -996,10 +999,18 @@ library KeysInTable { keys4.length * 32 ); } + } + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic( + bytes32[] memory keys0, + bytes32[] memory keys1, + bytes32[] memory keys2, + bytes32[] memory keys3, + bytes32[] memory keys4 + ) internal pure returns (bytes memory) { return abi.encodePacked( - _encodedLengths.unwrap(), EncodeArray.encode((keys0)), EncodeArray.encode((keys1)), EncodeArray.encode((keys2)), @@ -1008,6 +1019,21 @@ library KeysInTable { ); } + /** Tightly pack full data using this table's field layout */ + function encode( + bytes32[] memory keys0, + bytes32[] memory keys1, + bytes32[] memory keys2, + bytes32[] memory keys3, + bytes32[] memory keys4 + ) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(keys0, keys1, keys2, keys3, keys4); + bytes memory _dynamicData = encodeDynamic(keys0, keys1, keys2, keys3, keys4); + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + } + /** Encode keys as a bytes32 array using this table's field layout */ function encodeKeyTuple(bytes32 sourceTable) internal pure returns (bytes32[] memory) { bytes32[] memory _keyTuple = new bytes32[](1); diff --git a/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol b/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol index b29e54cab8..ac68814aa7 100644 --- a/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol +++ b/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol @@ -178,24 +178,30 @@ library UsedKeysIndex { /** Set the full data using individual values */ function set(bytes32 sourceTable, bytes32 keysHash, bool has, uint40 index) internal { - bytes memory _data = encode(has, index); + bytes memory _staticData = encodeStatic(has, index); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](2); _keyTuple[0] = sourceTable; _keyTuple[1] = keysHash; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, bytes32 sourceTable, bytes32 keysHash, bool has, uint40 index) internal { - bytes memory _data = encode(has, index); + bytes memory _staticData = encodeStatic(has, index); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](2); _keyTuple[0] = sourceTable; _keyTuple[1] = keysHash; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Decode the tightly packed blob using this table's field layout */ @@ -205,9 +211,19 @@ library UsedKeysIndex { index = (uint40(Bytes.slice5(_blob, 1))); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bool has, uint40 index) internal pure returns (bytes memory) { + return abi.encodePacked(has, index); + } + /** Tightly pack full data using this table's field layout */ function encode(bool has, uint40 index) internal pure returns (bytes memory) { - return abi.encodePacked(has, index); + bytes memory _staticData = encodeStatic(has, index); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol b/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol index f0326407cd..3c3c822c0f 100644 --- a/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol +++ b/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol @@ -5,6 +5,7 @@ import { StoreHook } from "@latticexyz/store/src/StoreHook.sol"; import { Bytes } from "@latticexyz/store/src/Bytes.sol"; import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { IBaseWorld } from "../../interfaces/IBaseWorld.sol"; import { ResourceSelector } from "../../ResourceSelector.sol"; @@ -32,7 +33,9 @@ contract KeysWithValueHook is StoreHook { function onBeforeSetRecord( bytes32 sourceTableId, bytes32[] memory keyTuple, - bytes memory data, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, FieldLayout fieldLayout ) public { bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); @@ -44,13 +47,21 @@ contract KeysWithValueHook is StoreHook { _removeKeyFromList(targetTableId, keyTuple[0], previousValue); // Push the key to the list of keys with the new value + bytes memory data; + if (dynamicData.length > 0) { + data = abi.encodePacked(staticData, encodedLengths, dynamicData); + } else { + data = staticData; + } KeysWithValue.push(targetTableId, keccak256(data), keyTuple[0]); } function onAfterSetRecord( bytes32 sourceTableId, bytes32[] memory keyTuple, - bytes memory data, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData, FieldLayout fieldLayout ) public { // NOOP diff --git a/packages/world/src/modules/keyswithvalue/getKeysWithValue.sol b/packages/world/src/modules/keyswithvalue/getKeysWithValue.sol index f5e8605c63..e47fba0d10 100644 --- a/packages/world/src/modules/keyswithvalue/getKeysWithValue.sol +++ b/packages/world/src/modules/keyswithvalue/getKeysWithValue.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { MODULE_NAMESPACE } from "./constants.sol"; import { KeysWithValue } from "./tables/KeysWithValue.sol"; @@ -13,11 +14,22 @@ import { getTargetTableSelector } from "../utils/getTargetTableSelector.sol"; * Note: this util can only be called within the context of a Store (e.g. from a System or Module). * For usage outside of a Store, use the overload that takes an explicit store argument. */ -function getKeysWithValue(bytes32 tableId, bytes memory value) view returns (bytes32[] memory keysWithValue) { +function getKeysWithValue( + bytes32 tableId, + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData +) view returns (bytes32[] memory keysWithValue) { // Get the corresponding reverse mapping table bytes32 keysWithValueTableId = getTargetTableSelector(MODULE_NAMESPACE, tableId); // Get the keys with the given value + bytes memory value; + if (dynamicData.length > 0) { + value = abi.encodePacked(staticData, encodedLengths, dynamicData); + } else { + value = staticData; + } keysWithValue = KeysWithValue.get(keysWithValueTableId, keccak256(value)); } @@ -27,11 +39,19 @@ function getKeysWithValue(bytes32 tableId, bytes memory value) view returns (byt function getKeysWithValue( IStore store, bytes32 tableId, - bytes memory value + bytes memory staticData, + PackedCounter encodedLengths, + bytes memory dynamicData ) view returns (bytes32[] memory keysWithValue) { // Get the corresponding reverse mapping table bytes32 keysWithValueTableId = getTargetTableSelector(MODULE_NAMESPACE, tableId); // Get the keys with the given value + bytes memory value; + if (dynamicData.length > 0) { + value = abi.encodePacked(staticData, encodedLengths, dynamicData); + } else { + value = staticData; + } keysWithValue = KeysWithValue.get(store, keysWithValueTableId, keccak256(value)); } diff --git a/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol b/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol index 2ae73a6568..a13236775a 100644 --- a/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol +++ b/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol @@ -231,15 +231,26 @@ library KeysWithValue { } } - /** Tightly pack full data using this table's field layout */ - function encode(bytes32[] memory keysWithValue) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(bytes32[] memory keysWithValue) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(keysWithValue.length * 32); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(bytes32[] memory keysWithValue) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((keysWithValue))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(bytes32[] memory keysWithValue) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(keysWithValue); + bytes memory _dynamicData = encodeDynamic(keysWithValue); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((keysWithValue))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol b/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol index a2f0f2519b..070d868fe0 100644 --- a/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol +++ b/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol @@ -151,9 +151,19 @@ library CallboundDelegations { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((availableCalls)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint256 availableCalls) internal pure returns (bytes memory) { + return abi.encodePacked(availableCalls); + } + /** Tightly pack full data using this table's field layout */ function encode(uint256 availableCalls) internal pure returns (bytes memory) { - return abi.encodePacked(availableCalls); + bytes memory _staticData = encodeStatic(availableCalls); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol b/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol index 27a2552404..3e7a015869 100644 --- a/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol +++ b/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol @@ -115,9 +115,19 @@ library TimeboundDelegations { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((maxTimestamp)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint256 maxTimestamp) internal pure returns (bytes memory) { + return abi.encodePacked(maxTimestamp); + } + /** Tightly pack full data using this table's field layout */ function encode(uint256 maxTimestamp) internal pure returns (bytes memory) { - return abi.encodePacked(maxTimestamp); + bytes memory _staticData = encodeStatic(maxTimestamp); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol b/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol index 092bde6646..1b2238a638 100644 --- a/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol +++ b/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol @@ -100,9 +100,19 @@ library UniqueEntity { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint256 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(uint256 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/Delegations.sol b/packages/world/src/tables/Delegations.sol index 5889c445bd..22420ee76b 100644 --- a/packages/world/src/tables/Delegations.sol +++ b/packages/world/src/tables/Delegations.sol @@ -119,9 +119,19 @@ library Delegations { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((delegationControlId)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bytes32 delegationControlId) internal pure returns (bytes memory) { + return abi.encodePacked(delegationControlId); + } + /** Tightly pack full data using this table's field layout */ function encode(bytes32 delegationControlId) internal pure returns (bytes memory) { - return abi.encodePacked(delegationControlId); + bytes memory _staticData = encodeStatic(delegationControlId); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/InstalledModules.sol b/packages/world/src/tables/InstalledModules.sol index a0bbbc61b7..9f7b45859c 100644 --- a/packages/world/src/tables/InstalledModules.sol +++ b/packages/world/src/tables/InstalledModules.sol @@ -115,9 +115,19 @@ library InstalledModules { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((moduleAddress)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(address moduleAddress) internal pure returns (bytes memory) { + return abi.encodePacked(moduleAddress); + } + /** Tightly pack full data using this table's field layout */ function encode(address moduleAddress) internal pure returns (bytes memory) { - return abi.encodePacked(moduleAddress); + bytes memory _staticData = encodeStatic(moduleAddress); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/NamespaceOwner.sol b/packages/world/src/tables/NamespaceOwner.sol index 54033da9dd..85d58a4921 100644 --- a/packages/world/src/tables/NamespaceOwner.sol +++ b/packages/world/src/tables/NamespaceOwner.sol @@ -109,9 +109,19 @@ library NamespaceOwner { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((owner)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(address owner) internal pure returns (bytes memory) { + return abi.encodePacked(owner); + } + /** Tightly pack full data using this table's field layout */ function encode(address owner) internal pure returns (bytes memory) { - return abi.encodePacked(owner); + bytes memory _staticData = encodeStatic(owner); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/ResourceAccess.sol b/packages/world/src/tables/ResourceAccess.sol index f53abae8e7..51a3dba36d 100644 --- a/packages/world/src/tables/ResourceAccess.sol +++ b/packages/world/src/tables/ResourceAccess.sol @@ -115,9 +115,19 @@ library ResourceAccess { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((access)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bool access) internal pure returns (bytes memory) { + return abi.encodePacked(access); + } + /** Tightly pack full data using this table's field layout */ function encode(bool access) internal pure returns (bytes memory) { - return abi.encodePacked(access); + bytes memory _staticData = encodeStatic(access); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/test/KeysInTableModule.t.sol b/packages/world/test/KeysInTableModule.t.sol index e25f7b5427..7789e33740 100644 --- a/packages/world/test/KeysInTableModule.t.sol +++ b/packages/world/test/KeysInTableModule.t.sol @@ -7,6 +7,7 @@ import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; import { FieldLayout, FieldLayoutLib } from "@latticexyz/store/src/FieldLayout.sol"; import { FieldLayoutEncodeHelper } from "@latticexyz/store/test/FieldLayoutEncodeHelper.sol"; import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { SchemaEncodeHelper } from "@latticexyz/store/test/SchemaEncodeHelper.sol"; import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; @@ -103,7 +104,14 @@ contract KeysInTableModuleTest is Test, GasReporter { bytes32[] memory keyTuple = new bytes32[](0); - world.setRecord(singletonTableId, keyTuple, abi.encodePacked(val1), tableFieldLayout); + world.setRecord( + singletonTableId, + keyTuple, + abi.encodePacked(val1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in this target table bytes32[][] memory keysInTable = getKeysInTable(world, singletonTableId); @@ -120,7 +128,14 @@ contract KeysInTableModuleTest is Test, GasReporter { keyTuple[1] = "two"; keyTuple[2] = "three"; - world.setRecord(compositeTableId, keyTuple, abi.encodePacked(val1), tableFieldLayout); + world.setRecord( + compositeTableId, + keyTuple, + abi.encodePacked(val1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in this target table bytes32[][] memory keysInTable = getKeysInTable(world, compositeTableId); @@ -145,7 +160,14 @@ contract KeysInTableModuleTest is Test, GasReporter { _installKeysInTableModule(); // Set a value in the source table startGasReport("set a record on a table with keysInTableModule installed"); - world.setRecord(tableId, keyTuple1, abi.encodePacked(value), tableFieldLayout); + world.setRecord( + tableId, + keyTuple1, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); endGasReport(); // Get the list of keys in this target table @@ -164,7 +186,14 @@ contract KeysInTableModuleTest is Test, GasReporter { // Set a value in the source table startGasReport("set a record on a table with keysInTableModule installed (first)"); - world.setRecord(tableId, keyTuple, abi.encodePacked(value1), tableFieldLayout); + world.setRecord( + tableId, + keyTuple, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); endGasReport(); // Get the list of keys in the first target table @@ -192,7 +221,14 @@ contract KeysInTableModuleTest is Test, GasReporter { // Set a value in the source table startGasReport("set a record on a table with keysInTableModule installed (second)"); - world.setRecord(sourceTableId2, keyTuple, abi.encodePacked(value2), tableFieldLayout); + world.setRecord( + sourceTableId2, + keyTuple, + abi.encodePacked(value2), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); endGasReport(); // Get the list of keys in the second target table @@ -212,7 +248,14 @@ contract KeysInTableModuleTest is Test, GasReporter { _installKeysInTableModule(); // Set a value in the source table - world.setRecord(tableId, keyTuple1, abi.encodePacked(value1), tableFieldLayout); + world.setRecord( + tableId, + keyTuple1, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in the target table bytes32[][] memory keysInTable = getKeysInTable(world, tableId); @@ -222,7 +265,14 @@ contract KeysInTableModuleTest is Test, GasReporter { assertEq(keysInTable[0][0], key1, "2"); // Set another key with the same value - world.setRecord(tableId, keyTuple2, abi.encodePacked(value1), tableFieldLayout); + world.setRecord( + tableId, + keyTuple2, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in the target table keysInTable = getKeysInTable(world, tableId); @@ -234,7 +284,14 @@ contract KeysInTableModuleTest is Test, GasReporter { // Change the value of the first key startGasReport("change a record on a table with keysInTableModule installed"); - world.setRecord(tableId, keyTuple1, abi.encodePacked(value2), tableFieldLayout); + world.setRecord( + tableId, + keyTuple1, + abi.encodePacked(value2), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); endGasReport(); // Get the list of keys in the target table @@ -277,7 +334,14 @@ contract KeysInTableModuleTest is Test, GasReporter { keyTupleB[2] = "charlie"; // Set a value in the source table - world.setRecord(compositeTableId, keyTupleA, abi.encodePacked(value1), tableFieldLayout); + world.setRecord( + compositeTableId, + keyTupleA, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in the target table bytes32[][] memory keysInTable = getKeysInTable(world, compositeTableId); @@ -289,7 +353,14 @@ contract KeysInTableModuleTest is Test, GasReporter { } // Set another key with the same value - world.setRecord(compositeTableId, keyTupleB, abi.encodePacked(value1), tableFieldLayout); + world.setRecord( + compositeTableId, + keyTupleB, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in the target table keysInTable = getKeysInTable(world, compositeTableId); @@ -305,7 +376,14 @@ contract KeysInTableModuleTest is Test, GasReporter { // Change the value of the first key startGasReport("change a composite record on a table with keysInTableModule installed"); - world.setRecord(compositeTableId, keyTupleA, abi.encodePacked(value2), tableFieldLayout); + world.setRecord( + compositeTableId, + keyTupleA, + abi.encodePacked(value2), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); endGasReport(); // Get the list of keys in the target table @@ -367,7 +445,14 @@ contract KeysInTableModuleTest is Test, GasReporter { _installKeysInTableModule(); // Set a value in the source table - world.setRecord(tableId, keyTuple1, abi.encodePacked(value1), tableFieldLayout); + world.setRecord( + tableId, + keyTuple1, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); startGasReport("Get list of keys in a given table"); bytes32[][] memory keysInTable = getKeysInTable(world, tableId); @@ -378,7 +463,14 @@ contract KeysInTableModuleTest is Test, GasReporter { assertEq(keysInTable[0][0], key1); // Set another key with a different value - world.setRecord(tableId, keyTuple2, abi.encodePacked(value2), tableFieldLayout); + world.setRecord( + tableId, + keyTuple2, + abi.encodePacked(value2), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Get the list of keys in the target table keysInTable = getKeysInTable(world, tableId); @@ -393,9 +485,30 @@ contract KeysInTableModuleTest is Test, GasReporter { _installKeysInTableModule(); // Add 3 values - world.setRecord(tableId, keyTuple1, abi.encodePacked(value), tableFieldLayout); - world.setRecord(tableId, keyTuple2, abi.encodePacked(value), tableFieldLayout); - world.setRecord(tableId, keyTuple3, abi.encodePacked(value), tableFieldLayout); + world.setRecord( + tableId, + keyTuple1, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); + world.setRecord( + tableId, + keyTuple2, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); + world.setRecord( + tableId, + keyTuple3, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + tableFieldLayout + ); // Remove 2, starting from the middle // This tests that KeysInTable correctly tracks swaps indexes diff --git a/packages/world/test/KeysWithValueModule.t.sol b/packages/world/test/KeysWithValueModule.t.sol index 50307d969b..fa8e8e83d5 100644 --- a/packages/world/test/KeysWithValueModule.t.sol +++ b/packages/world/test/KeysWithValueModule.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { Test } from "forge-std/Test.sol"; +import { Test, console } from "forge-std/Test.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { SchemaEncodeHelper } from "@latticexyz/store/test/SchemaEncodeHelper.sol"; import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; @@ -26,20 +27,20 @@ import { getTargetTableSelector } from "../src/modules/utils/getTargetTableSelec contract KeysWithValueModuleTest is Test, GasReporter { using ResourceSelector for bytes32; IBaseWorld world; - KeysWithValueModule keysWithValueModule = new KeysWithValueModule(); // Modules can be deployed once and installed multiple times + KeysWithValueModule private keysWithValueModule = new KeysWithValueModule(); // Modules can be deployed once and installed multiple times - bytes16 namespace = ROOT_NAMESPACE; - bytes16 sourceName = bytes16("source"); - bytes32 key1 = keccak256("test"); - bytes32[] keyTuple1; - bytes32 key2 = keccak256("test2"); - bytes32[] keyTuple2; + bytes16 private namespace = ROOT_NAMESPACE; + bytes16 private sourceName = bytes16("source"); + bytes32 private key1 = keccak256("test"); + bytes32[] private keyTuple1; + bytes32 private key2 = keccak256("test2"); + bytes32[] private keyTuple2; - FieldLayout sourceTableFieldLayout; - Schema sourceTableSchema; - Schema sourceTableKeySchema; - bytes32 sourceTableId; - bytes32 targetTableId; + FieldLayout private sourceTableFieldLayout; + Schema private sourceTableSchema; + Schema private sourceTableKeySchema; + bytes32 private sourceTableId; + bytes32 private targetTableId; function setUp() public { sourceTableFieldLayout = FieldLayoutEncodeHelper.encode(32, 0); @@ -80,11 +81,18 @@ contract KeysWithValueModuleTest is Test, GasReporter { uint256 value = 1; startGasReport("set a record on a table with KeysWithValueModule installed"); - world.setRecord(sourceTableId, keyTuple1, abi.encodePacked(value), sourceTableFieldLayout); + world.setRecord( + sourceTableId, + keyTuple1, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + sourceTableFieldLayout + ); endGasReport(); // Get the list of entities with this value from the target table - bytes32[] memory keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encode(value))); + bytes32[] memory keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encodePacked(value))); // Assert that the list is correct assertEq(keysWithValue.length, 1); @@ -97,20 +105,34 @@ contract KeysWithValueModuleTest is Test, GasReporter { // Set a value in the source table uint256 value1 = 1; - world.setRecord(sourceTableId, keyTuple1, abi.encodePacked(value1), sourceTableFieldLayout); + world.setRecord( + sourceTableId, + keyTuple1, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + sourceTableFieldLayout + ); // Get the list of entities with value1 from the target table - bytes32[] memory keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encode(value1))); + bytes32[] memory keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encodePacked(value1))); // Assert that the list is correct assertEq(keysWithValue.length, 1, "1"); assertEq(keysWithValue[0], key1, "2"); // Set a another key with the same value - world.setRecord(sourceTableId, keyTuple2, abi.encodePacked(value1), sourceTableFieldLayout); + world.setRecord( + sourceTableId, + keyTuple2, + abi.encodePacked(value1), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + sourceTableFieldLayout + ); // Get the list of entities with value2 from the target table - keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encode(value1))); + keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encodePacked(value1))); // Assert that the list is correct assertEq(keysWithValue.length, 2); @@ -121,18 +143,25 @@ contract KeysWithValueModuleTest is Test, GasReporter { uint256 value2 = 2; startGasReport("change a record on a table with KeysWithValueModule installed"); - world.setRecord(sourceTableId, keyTuple1, abi.encodePacked(value2), sourceTableFieldLayout); + world.setRecord( + sourceTableId, + keyTuple1, + abi.encodePacked(value2), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + sourceTableFieldLayout + ); endGasReport(); // Get the list of entities with value1 from the target table - keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encode(value1))); + keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encodePacked(value1))); // Assert that the list is correct assertEq(keysWithValue.length, 1, "5"); assertEq(keysWithValue[0], key2, "6"); // Get the list of entities with value2 from the target table - keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encode(value2))); + keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encodePacked(value2))); // Assert that the list is correct assertEq(keysWithValue.length, 1, "7"); @@ -144,7 +173,7 @@ contract KeysWithValueModuleTest is Test, GasReporter { endGasReport(); // Get the list of entities with value2 from the target table - keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encode(value2))); + keysWithValue = KeysWithValue.get(world, targetTableId, keccak256(abi.encodePacked(value2))); // Assert that the list is correct assertEq(keysWithValue.length, 0, "9"); @@ -212,10 +241,23 @@ contract KeysWithValueModuleTest is Test, GasReporter { _installKeysWithValueModule(); // Set a value in the source table - world.setRecord(sourceTableId, keyTuple1, abi.encodePacked(value), sourceTableFieldLayout); + world.setRecord( + sourceTableId, + keyTuple1, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + sourceTableFieldLayout + ); startGasReport("Get list of keys with a given value"); - bytes32[] memory keysWithValue = getKeysWithValue(world, sourceTableId, abi.encode(value)); + bytes32[] memory keysWithValue = getKeysWithValue( + world, + sourceTableId, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0) + ); endGasReport(); // Assert that the list is correct @@ -223,10 +265,23 @@ contract KeysWithValueModuleTest is Test, GasReporter { assertEq(keysWithValue[0], key1); // Set a another key with the same value - world.setRecord(sourceTableId, keyTuple2, abi.encodePacked(value), sourceTableFieldLayout); + world.setRecord( + sourceTableId, + keyTuple2, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + sourceTableFieldLayout + ); // Get the list of keys with value from the target table - keysWithValue = getKeysWithValue(world, sourceTableId, abi.encode(value)); + keysWithValue = getKeysWithValue( + world, + sourceTableId, + abi.encodePacked(value), + PackedCounter.wrap(bytes32(0)), + new bytes(0) + ); // Assert that the list is correct assertEq(keysWithValue.length, 2); diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 1d168ead9a..9d651d3ef2 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -12,6 +12,7 @@ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { FieldLayout, FieldLayoutLib } from "@latticexyz/store/src/FieldLayout.sol"; import { FieldLayoutEncodeHelper } from "@latticexyz/store/test/FieldLayoutEncodeHelper.sol"; import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { SchemaEncodeHelper } from "@latticexyz/store/test/SchemaEncodeHelper.sol"; import { Tables, TablesTableId } from "@latticexyz/store/src/codegen/Tables.sol"; import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; @@ -91,9 +92,23 @@ contract WorldTestSystem is System { FieldLayout fieldLayout = StoreSwitch.getFieldLayout(tableId); if (StoreSwitch.getStoreAddress() == address(this)) { - StoreCore.setRecord(tableId, keyTuple, abi.encodePacked(data), fieldLayout); + StoreCore.setRecord( + tableId, + keyTuple, + abi.encodePacked(data), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + fieldLayout + ); } else { - IBaseWorld(msg.sender).setRecord(tableId, keyTuple, abi.encodePacked(data), fieldLayout); + IBaseWorld(msg.sender).setRecord( + tableId, + keyTuple, + abi.encodePacked(data), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + fieldLayout + ); } } @@ -621,7 +636,14 @@ contract WorldTest is Test, GasReporter { world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](1)); // Write data to the table and expect it to be written - world.setRecord(tableId, singletonKey, abi.encodePacked(true), fieldLayout); + world.setRecord( + tableId, + singletonKey, + abi.encodePacked(true), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + fieldLayout + ); assertTrue(Bool.get(world, tableId)); startGasReport("Delete record"); @@ -632,7 +654,15 @@ contract WorldTest is Test, GasReporter { assertFalse(Bool.get(world, tableId)); // Write data to the table and expect it to be written - world.setRecord(tableId, singletonKey, abi.encodePacked(true), fieldLayout); + world.setRecord( + tableId, + singletonKey, + abi.encodePacked(true), + PackedCounter.wrap(bytes32(0)), + new bytes(0), + fieldLayout + ); + assertTrue(Bool.get(world, tableId)); assertTrue(Bool.get(world, tableId)); // Expect an error when trying to delete from an address that doesn't have access @@ -822,25 +852,29 @@ contract WorldTest is Test, GasReporter { ); // Prepare data to write to the table - bytes memory value = abi.encodePacked(true); + bytes memory staticData = abi.encodePacked(true); // Expect the hook to be notified when a record is written (once before and once after the record is written) vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, value, fieldLayout)); + emit HookCalled( + abi.encode(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout) + ); vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, value, fieldLayout)); + emit HookCalled( + abi.encode(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout) + ); - world.setRecord(tableId, singletonKey, value, fieldLayout); + world.setRecord(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Expect the hook to be notified when a field is written (once before and once after the field is written) vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), value, fieldLayout)); + emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), staticData, fieldLayout)); vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), value, fieldLayout)); + emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), staticData, fieldLayout)); - world.setField(tableId, singletonKey, 0, value, fieldLayout); + world.setField(tableId, singletonKey, 0, staticData, fieldLayout); // Expect the hook to be notified when a record is deleted (once before and once after the field is written) vm.expectEmit(true, true, true, true); @@ -907,15 +941,15 @@ contract WorldTest is Test, GasReporter { ); // Prepare data to write to the table - bytes memory value = abi.encodePacked(true); + bytes memory staticData = abi.encodePacked(true); // Expect a revert when the RevertSubscriber's onBeforeSetRecord hook is called vm.expectRevert(bytes("onBeforeSetRecord")); - world.setRecord(tableId, singletonKey, value, fieldLayout); + world.setRecord(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Expect a revert when the RevertSubscriber's onBeforeSetField hook is called vm.expectRevert(bytes("onBeforeSetField")); - world.setField(tableId, singletonKey, 0, value, fieldLayout); + world.setField(tableId, singletonKey, 0, staticData, fieldLayout); // Expect a revert when the RevertSubscriber's onBeforeDeleteRecord hook is called vm.expectRevert(bytes("onBeforeDeleteRecord")); @@ -926,21 +960,25 @@ contract WorldTest is Test, GasReporter { // Expect the hook to be notified when a record is written (once before and once after the record is written) vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, value, fieldLayout)); + emit HookCalled( + abi.encode(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout) + ); vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, value, fieldLayout)); + emit HookCalled( + abi.encode(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout) + ); - world.setRecord(tableId, singletonKey, value, fieldLayout); + world.setRecord(tableId, singletonKey, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Expect the hook to be notified when a field is written (once before and once after the field is written) vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), value, fieldLayout)); + emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), staticData, fieldLayout)); vm.expectEmit(true, true, true, true); - emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), value, fieldLayout)); + emit HookCalled(abi.encode(tableId, singletonKey, uint8(0), staticData, fieldLayout)); - world.setField(tableId, singletonKey, 0, value, fieldLayout); + world.setField(tableId, singletonKey, 0, staticData, fieldLayout); // Expect the hook to be notified when a record is deleted (once before and once after the field is written) vm.expectEmit(true, true, true, true); diff --git a/packages/world/test/query.t.sol b/packages/world/test/query.t.sol index 0d7b3b63f8..27f8621b9a 100644 --- a/packages/world/test/query.t.sol +++ b/packages/world/test/query.t.sol @@ -8,6 +8,7 @@ import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; import { FieldLayoutEncodeHelper } from "@latticexyz/store/test/FieldLayoutEncodeHelper.sol"; import { Schema } from "@latticexyz/store/src/Schema.sol"; import { SchemaEncodeHelper } from "@latticexyz/store/test/SchemaEncodeHelper.sol"; +import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; import { World } from "../src/World.sol"; @@ -22,29 +23,29 @@ import { query, QueryFragment, QueryType } from "../src/modules/keysintable/quer contract QueryTest is Test, GasReporter { using ResourceSelector for bytes32; - IBaseWorld world; - KeysInTableModule keysInTableModule = new KeysInTableModule(); // Modules can be deployed once and installed multiple times - KeysWithValueModule keysWithValueModule = new KeysWithValueModule(); - - bytes16 namespace = ROOT_NAMESPACE; - bytes16 name1 = bytes16("source1"); - bytes16 name2 = bytes16("source2"); - bytes16 name3 = bytes16("source3"); - - FieldLayout tableFieldLayout; - Schema tableKeySchema; - Schema tableValueSchema; - bytes32 table1 = ResourceSelector.from(namespace, name1); - bytes32 table2 = ResourceSelector.from(namespace, name2); - bytes32 table3 = ResourceSelector.from(namespace, name3); - - uint256 value = 1; - bytes32[] key1 = new bytes32[](1); - bytes32[] key2 = new bytes32[](1); - bytes32[] key3 = new bytes32[](1); - bytes32[] key4 = new bytes32[](1); - QueryFragment[] fragmentsHasHas; - QueryFragment[] fragmentsHasNot; + IBaseWorld private world; + KeysInTableModule private keysInTableModule = new KeysInTableModule(); // Modules can be deployed once and installed multiple times + KeysWithValueModule private keysWithValueModule = new KeysWithValueModule(); + + bytes16 private namespace = ROOT_NAMESPACE; + bytes16 private name1 = bytes16("source1"); + bytes16 private name2 = bytes16("source2"); + bytes16 private name3 = bytes16("source3"); + + FieldLayout private tableFieldLayout; + Schema private tableKeySchema; + Schema private tableValueSchema; + bytes32 private table1 = ResourceSelector.from(namespace, name1); + bytes32 private table2 = ResourceSelector.from(namespace, name2); + bytes32 private table3 = ResourceSelector.from(namespace, name3); + + uint256 private value = 1; + bytes32[] private key1 = new bytes32[](1); + bytes32[] private key2 = new bytes32[](1); + bytes32[] private key3 = new bytes32[](1); + bytes32[] private key4 = new bytes32[](1); + QueryFragment[] private fragmentsHasHas; + QueryFragment[] private fragmentsHasNot; function setUp() public { tableFieldLayout = FieldLayoutEncodeHelper.encode(32, 0); @@ -90,9 +91,9 @@ contract QueryTest is Test, GasReporter { function testHasQuery() public { _installKeysInTableModule(); - world.setRecord(table1, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key1, abi.encode(0), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(0), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all keys in table1 QueryFragment[] memory fragments = new QueryFragment[](1); @@ -111,9 +112,9 @@ contract QueryTest is Test, GasReporter { _installKeysInTableModule(); _installKeysWithValueModule(); - world.setRecord(table1, key1, abi.encode(2), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all keys in table1 with value 1 QueryFragment[] memory fragments = new QueryFragment[](1); fragments[0] = QueryFragment(QueryType.HasValue, table1, abi.encode(1)); @@ -129,12 +130,12 @@ contract QueryTest is Test, GasReporter { function testCombinedHasQuery() public { _installKeysInTableModule(); - world.setRecord(table1, key1, abi.encode(2), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table3, key1, abi.encode(1), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table3, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities that have table1 and table2 QueryFragment[] memory fragments = new QueryFragment[](2); @@ -153,12 +154,12 @@ contract QueryTest is Test, GasReporter { _installKeysInTableModule(); _installKeysWithValueModule(); - world.setRecord(table1, key1, abi.encode(2), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(2), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table3, key1, abi.encode(1), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table3, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities that have table1 and table2 QueryFragment[] memory fragments = new QueryFragment[](2); @@ -176,13 +177,13 @@ contract QueryTest is Test, GasReporter { _installKeysInTableModule(); _installKeysWithValueModule(); - world.setRecord(table1, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key2, abi.encode(2), tableFieldLayout); - world.setRecord(table2, key3, abi.encode(2), tableFieldLayout); - world.setRecord(table2, key4, abi.encode(2), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key2, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key3, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key4, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities that have table1 and table2 QueryFragment[] memory fragments = new QueryFragment[](2); @@ -200,13 +201,13 @@ contract QueryTest is Test, GasReporter { function testCombinedHasNotQuery() public { _installKeysInTableModule(); - world.setRecord(table1, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key2, abi.encode(2), tableFieldLayout); - world.setRecord(table2, key3, abi.encode(2), tableFieldLayout); - world.setRecord(table2, key4, abi.encode(2), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key2, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key3, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key4, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities that have table1 and table2 QueryFragment[] memory fragments = new QueryFragment[](2); @@ -224,13 +225,13 @@ contract QueryTest is Test, GasReporter { _installKeysInTableModule(); _installKeysWithValueModule(); - world.setRecord(table1, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key2, abi.encode(2), tableFieldLayout); - world.setRecord(table2, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key4, abi.encode(1), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key2, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key4, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities that have table1 and table2 QueryFragment[] memory fragments = new QueryFragment[](2); @@ -248,16 +249,16 @@ contract QueryTest is Test, GasReporter { _installKeysInTableModule(); _installKeysWithValueModule(); - world.setRecord(table1, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key1, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key2, abi.encode(2), tableFieldLayout); - world.setRecord(table2, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table2, key4, abi.encode(1), tableFieldLayout); - world.setRecord(table3, key2, abi.encode(1), tableFieldLayout); - world.setRecord(table3, key3, abi.encode(1), tableFieldLayout); - world.setRecord(table3, key4, abi.encode(1), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key2, abi.encode(2), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table2, key4, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table3, key2, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table3, key3, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table3, key4, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities that have table2 and not table1 QueryFragment[] memory fragments = new QueryFragment[](3); @@ -276,9 +277,9 @@ contract QueryTest is Test, GasReporter { _installKeysInTableModule(); _installKeysWithValueModule(); - world.setRecord(table1, key1, abi.encode(4), tableFieldLayout); - world.setRecord(table1, key2, abi.encode(5), tableFieldLayout); - world.setRecord(table1, key3, abi.encode(6), tableFieldLayout); + world.setRecord(table1, key1, abi.encode(4), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key2, abi.encode(5), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); + world.setRecord(table1, key3, abi.encode(6), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all entities with table1 except value 6 QueryFragment[] memory fragments = new QueryFragment[](2); @@ -299,9 +300,9 @@ contract QueryTest is Test, GasReporter { for (uint256 i; i < 100; i++) { bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = bytes32(i); - world.setRecord(table1, keyTuple, abi.encode(1), tableFieldLayout); + world.setRecord(table1, keyTuple, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); } - world.setRecord(table2, key1, abi.encode(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(0), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all keys in table1 QueryFragment[] memory fragments = new QueryFragment[](1); @@ -320,9 +321,9 @@ contract QueryTest is Test, GasReporter { for (uint256 i; i < 1000; i++) { bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = bytes32(i); - world.setRecord(table1, keyTuple, abi.encode(1), tableFieldLayout); + world.setRecord(table1, keyTuple, abi.encode(1), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); } - world.setRecord(table2, key1, abi.encode(0), tableFieldLayout); + world.setRecord(table2, key1, abi.encode(0), PackedCounter.wrap(bytes32(0)), new bytes(0), tableFieldLayout); // Query should return all keys in table1 QueryFragment[] memory fragments = new QueryFragment[](1); diff --git a/packages/world/test/tables/AddressArray.sol b/packages/world/test/tables/AddressArray.sol index 004e97be40..ecd9e930ac 100644 --- a/packages/world/test/tables/AddressArray.sol +++ b/packages/world/test/tables/AddressArray.sol @@ -227,15 +227,26 @@ library AddressArray { } } - /** Tightly pack full data using this table's field layout */ - function encode(address[] memory value) internal pure returns (bytes memory) { - PackedCounter _encodedLengths; + /** Tightly pack dynamic data using this table's schema */ + function encodeLengths(address[] memory value) internal pure returns (PackedCounter _encodedLengths) { // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits unchecked { _encodedLengths = PackedCounterLib.pack(value.length * 20); } + } + + /** Tightly pack dynamic data using this table's schema */ + function encodeDynamic(address[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** Tightly pack full data using this table's field layout */ + function encode(address[] memory value) internal pure returns (bytes memory) { + bytes memory _staticData; + PackedCounter _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_encodedLengths.unwrap(), EncodeArray.encode((value))); + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/test/tables/Bool.sol b/packages/world/test/tables/Bool.sol index 955432d6b9..9d471471ac 100644 --- a/packages/world/test/tables/Bool.sol +++ b/packages/world/test/tables/Bool.sol @@ -100,9 +100,19 @@ library Bool { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(bool value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(bool value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b66e40e5bd..cedd484c9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -782,14 +782,14 @@ importers: specifier: ^0.2.22 version: 0.2.22(typescript@5.1.6) better-sqlite3: - specifier: ^8.4.0 - version: 8.4.0 + specifier: ^8.6.0 + version: 8.6.0 debug: specifier: ^4.3.4 version: 4.3.4(supports-color@8.1.1) drizzle-orm: - specifier: ^0.27.0 - version: 0.27.0(@types/better-sqlite3@7.6.4)(better-sqlite3@8.4.0)(postgres@3.3.5) + specifier: ^0.28.5 + version: 0.28.5(@types/better-sqlite3@7.6.4)(better-sqlite3@8.6.0)(postgres@3.3.5) fastify: specifier: ^4.21.0 version: 4.21.0 @@ -861,11 +861,11 @@ importers: specifier: ^4.3.4 version: 4.3.4(supports-color@8.1.1) drizzle-orm: - specifier: ^0.27.0 - version: 0.27.0(@types/sql.js@1.4.4)(kysely@0.26.1)(postgres@3.3.5)(sql.js@1.8.0) + specifier: ^0.28.5 + version: 0.28.5(@types/sql.js@1.4.4)(kysely@0.26.3)(postgres@3.3.5)(sql.js@1.8.0) kysely: - specifier: ^0.26.1 - version: 0.26.1 + specifier: ^0.26.3 + version: 0.26.3 postgres: specifier: ^3.3.5 version: 3.3.5 @@ -4064,8 +4064,8 @@ packages: is-windows: 1.0.2 dev: true - /better-sqlite3@8.4.0: - resolution: {integrity: sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==} + /better-sqlite3@8.6.0: + resolution: {integrity: sha512-jwAudeiTMTSyby+/SfbHDebShbmC2MCH8mU2+DXi0WJfv13ypEJm47cd3kljmy/H130CazEvkf2Li//ewcMJ1g==} requiresBuild: true dependencies: bindings: 1.5.0 @@ -4953,8 +4953,8 @@ packages: detect-libc: 1.0.3 dev: true - /drizzle-orm@0.27.0(@types/better-sqlite3@7.6.4)(better-sqlite3@8.4.0)(postgres@3.3.5): - resolution: {integrity: sha512-LGiJ0icB+wQwgbSCOvAjONY8Ec6G/EDzQQP5PmUaQYeI9OqgpVKHC2T1fFIbvk5dabWsbokJ5NOciVAxriStig==} + /drizzle-orm@0.28.5(@types/better-sqlite3@7.6.4)(better-sqlite3@8.6.0)(postgres@3.3.5): + resolution: {integrity: sha512-6r6Iw4c38NAmW6TiKH3TUpGUQ1YdlEoLJOQptn8XPx3Z63+vFNKfAiANqrIiYZiMjKR9+NYAL219nFrmo1duXA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' @@ -5016,12 +5016,12 @@ packages: optional: true dependencies: '@types/better-sqlite3': 7.6.4 - better-sqlite3: 8.4.0 + better-sqlite3: 8.6.0 postgres: 3.3.5 dev: false - /drizzle-orm@0.27.0(@types/sql.js@1.4.4)(kysely@0.26.1)(postgres@3.3.5)(sql.js@1.8.0): - resolution: {integrity: sha512-LGiJ0icB+wQwgbSCOvAjONY8Ec6G/EDzQQP5PmUaQYeI9OqgpVKHC2T1fFIbvk5dabWsbokJ5NOciVAxriStig==} + /drizzle-orm@0.28.5(@types/sql.js@1.4.4)(kysely@0.26.3)(postgres@3.3.5)(sql.js@1.8.0): + resolution: {integrity: sha512-6r6Iw4c38NAmW6TiKH3TUpGUQ1YdlEoLJOQptn8XPx3Z63+vFNKfAiANqrIiYZiMjKR9+NYAL219nFrmo1duXA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' @@ -5083,7 +5083,7 @@ packages: optional: true dependencies: '@types/sql.js': 1.4.4 - kysely: 0.26.1 + kysely: 0.26.3 postgres: 3.3.5 sql.js: 1.8.0 dev: false @@ -7630,8 +7630,8 @@ packages: engines: {node: '>=6'} dev: true - /kysely@0.26.1: - resolution: {integrity: sha512-FVRomkdZofBu3O8SiwAOXrwbhPZZr8mBN5ZeUWyprH29jzvy6Inzqbd0IMmGxpd4rcOCL9HyyBNWBa8FBqDAdg==} + /kysely@0.26.3: + resolution: {integrity: sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw==} engines: {node: '>=14.0.0'} dev: false diff --git a/templates/phaser/packages/client/src/mud/setupNetwork.ts b/templates/phaser/packages/client/src/mud/setupNetwork.ts index 66a27a95ae..6fe7d00e2c 100644 --- a/templates/phaser/packages/client/src/mud/setupNetwork.ts +++ b/templates/phaser/packages/client/src/mud/setupNetwork.ts @@ -72,7 +72,7 @@ export async function setupNetwork() { * to the viem publicClient to make RPC calls to fetch MUD * events from the chain. */ - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -115,7 +115,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/templates/phaser/packages/client/src/ui/App.tsx b/templates/phaser/packages/client/src/ui/App.tsx index 47c66ae8f8..a61f2ba87e 100644 --- a/templates/phaser/packages/client/src/ui/App.tsx +++ b/templates/phaser/packages/client/src/ui/App.tsx @@ -21,7 +21,7 @@ export const App = () => { publicClient: networkLayer.network.publicClient, walletClient: networkLayer.network.walletClient, latestBlock$: networkLayer.network.latestBlock$, - blockStorageOperations$: networkLayer.network.blockStorageOperations$, + storedBlockLogs$: networkLayer.network.storedBlockLogs$, worldAddress: networkLayer.network.worldContract.address, worldAbi: networkLayer.network.worldContract.abi, write$: networkLayer.network.write$, diff --git a/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol b/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol index 302b905c36..6b2a3f319b 100644 --- a/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol +++ b/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol @@ -103,9 +103,19 @@ library Counter { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/react/packages/client/src/index.tsx b/templates/react/packages/client/src/index.tsx index 9b3f9121e1..da8d70f020 100644 --- a/templates/react/packages/client/src/index.tsx +++ b/templates/react/packages/client/src/index.tsx @@ -24,7 +24,7 @@ setup().then(async (result) => { publicClient: result.network.publicClient, walletClient: result.network.walletClient, latestBlock$: result.network.latestBlock$, - blockStorageOperations$: result.network.blockStorageOperations$, + storedBlockLogs$: result.network.storedBlockLogs$, worldAddress: result.network.worldContract.address, worldAbi: result.network.worldContract.abi, write$: result.network.write$, diff --git a/templates/react/packages/client/src/mud/setupNetwork.ts b/templates/react/packages/client/src/mud/setupNetwork.ts index de4b2c23dd..15d3555441 100644 --- a/templates/react/packages/client/src/mud/setupNetwork.ts +++ b/templates/react/packages/client/src/mud/setupNetwork.ts @@ -74,7 +74,7 @@ export async function setupNetwork() { * to the viem publicClient to make RPC calls to fetch MUD * events from the chain. */ - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -117,7 +117,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/templates/react/packages/contracts/src/codegen/tables/Counter.sol b/templates/react/packages/contracts/src/codegen/tables/Counter.sol index 302b905c36..6b2a3f319b 100644 --- a/templates/react/packages/contracts/src/codegen/tables/Counter.sol +++ b/templates/react/packages/contracts/src/codegen/tables/Counter.sol @@ -103,9 +103,19 @@ library Counter { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/threejs/packages/client/src/index.tsx b/templates/threejs/packages/client/src/index.tsx index 9b3f9121e1..da8d70f020 100644 --- a/templates/threejs/packages/client/src/index.tsx +++ b/templates/threejs/packages/client/src/index.tsx @@ -24,7 +24,7 @@ setup().then(async (result) => { publicClient: result.network.publicClient, walletClient: result.network.walletClient, latestBlock$: result.network.latestBlock$, - blockStorageOperations$: result.network.blockStorageOperations$, + storedBlockLogs$: result.network.storedBlockLogs$, worldAddress: result.network.worldContract.address, worldAbi: result.network.worldContract.abi, write$: result.network.write$, diff --git a/templates/threejs/packages/client/src/mud/setupNetwork.ts b/templates/threejs/packages/client/src/mud/setupNetwork.ts index ca2dc8d669..eb8a98ae84 100644 --- a/templates/threejs/packages/client/src/mud/setupNetwork.ts +++ b/templates/threejs/packages/client/src/mud/setupNetwork.ts @@ -72,7 +72,7 @@ export async function setupNetwork() { * to the viem publicClient to make RPC calls to fetch MUD * events from the chain. */ - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -115,7 +115,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/templates/threejs/packages/contracts/src/codegen/tables/Position.sol b/templates/threejs/packages/contracts/src/codegen/tables/Position.sol index fa15b967dc..b93c3a63c3 100644 --- a/templates/threejs/packages/contracts/src/codegen/tables/Position.sol +++ b/templates/threejs/packages/contracts/src/codegen/tables/Position.sol @@ -209,22 +209,28 @@ library Position { /** Set the full data using individual values */ function set(bytes32 key, int32 x, int32 y, int32 z) internal { - bytes memory _data = encode(x, y, z); + bytes memory _staticData = encodeStatic(x, y, z); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - StoreSwitch.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using individual values (using the specified store) */ function set(IStore _store, bytes32 key, int32 x, int32 y, int32 z) internal { - bytes memory _data = encode(x, y, z); + bytes memory _staticData = encodeStatic(x, y, z); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - _store.setRecord(_tableId, _keyTuple, _data, getFieldLayout()); + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, getFieldLayout()); } /** Set the full data using the data struct */ @@ -246,9 +252,19 @@ library Position { _table.z = (int32(uint32(Bytes.slice4(_blob, 8)))); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(int32 x, int32 y, int32 z) internal pure returns (bytes memory) { + return abi.encodePacked(x, y, z); + } + /** Tightly pack full data using this table's field layout */ function encode(int32 x, int32 y, int32 z) internal pure returns (bytes memory) { - return abi.encodePacked(x, y, z); + bytes memory _staticData = encodeStatic(x, y, z); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/vanilla/packages/client/src/index.ts b/templates/vanilla/packages/client/src/index.ts index 71b05b1f8d..0c7dda5720 100644 --- a/templates/vanilla/packages/client/src/index.ts +++ b/templates/vanilla/packages/client/src/index.ts @@ -28,7 +28,7 @@ if (import.meta.env.DEV) { publicClient: network.publicClient, walletClient: network.walletClient, latestBlock$: network.latestBlock$, - blockStorageOperations$: network.blockStorageOperations$, + storedBlockLogs$: network.storedBlockLogs$, worldAddress: network.worldContract.address, worldAbi: network.worldContract.abi, write$: network.write$, diff --git a/templates/vanilla/packages/client/src/mud/setupNetwork.ts b/templates/vanilla/packages/client/src/mud/setupNetwork.ts index de4b2c23dd..15d3555441 100644 --- a/templates/vanilla/packages/client/src/mud/setupNetwork.ts +++ b/templates/vanilla/packages/client/src/mud/setupNetwork.ts @@ -74,7 +74,7 @@ export async function setupNetwork() { * to the viem publicClient to make RPC calls to fetch MUD * events from the chain. */ - const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ + const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, @@ -117,7 +117,7 @@ export async function setupNetwork() { publicClient, walletClient: burnerWalletClient, latestBlock$, - blockStorageOperations$, + storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), diff --git a/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol b/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol index 302b905c36..6b2a3f319b 100644 --- a/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol +++ b/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol @@ -103,9 +103,19 @@ library Counter { _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((value)), getFieldLayout()); } + /** Tightly pack static data using this table's schema */ + function encodeStatic(uint32 value) internal pure returns (bytes memory) { + return abi.encodePacked(value); + } + /** Tightly pack full data using this table's field layout */ function encode(uint32 value) internal pure returns (bytes memory) { - return abi.encodePacked(value); + bytes memory _staticData = encodeStatic(value); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/test-data/world-logs.json b/test-data/world-logs.json index 4faea58887..15e99ba751 100644 --- a/test-data/world-logs.json +++ b/test-data/world-logs.json @@ -2,824 +2,824 @@ { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000016d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000003400060030220202000000000000000000000000000000000000000000000000000002001005f000000000000000000000000000000000000000000000000000000006003025f5f5fc4c40000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000a0000000000002c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000077461626c654964000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000b6669656c644c61796f757400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096b6579536368656d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b76616c7565536368656d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012616269456e636f6465644b65794e616d657300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014616269456e636f6465644669656c644e616d6573000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000022000000000a0000000000002c0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000016d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000600060030220202000000000000000000000000000000000000000000000000000002001005f000000000000000000000000000000000000000000000000000000006003025f5f5fc4c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000077461626c654964000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000b6669656c644c61796f757400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096b6579536368656d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b76616c7565536368656d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012616269456e636f6465644b65794e616d657300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014616269456e636f6465644669656c644e616d6573000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x0", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000016d756473746f7265000000000000000053746f7265486f6f6b7300000000000000000000000000000000000000000000000000000000000000000000000001c00000000100000000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000001b600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036b65790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x2", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a000000000000140000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000016d756473746f7265000000000000000053746f7265486f6f6b7300000000000000000000000000000000000000000000000000000000000000000000000000600000000100000000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000001b6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036b65790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1", "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e616d6573706163654f776e6572000000000000000000000000000000000000000000000000000000000000000001c00014010014000000000000000000000000000000000000000000000000000000001001004f000000000000000000000000000000000000000000000000000000001401006100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096e616d657370616365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000056f776e6572000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x3", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e616d6573706163654f776e6572000000000000000000000000000000000000000000000000000000000000000000600014010014000000000000000000000000000000000000000000000000000000001001004f0000000000000000000000000000000000000000000000000000000014010061000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096e616d657370616365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000056f776e6572000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x2", "transactionLogIndex": "0x2", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000042616c616e636573000000000000000000000000000000000000000000000000000000000000000000000000000001c00020010020000000000000000000000000000000000000000000000000000000001001004f000000000000000000000000000000000000000000000000000000002001001f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096e616d6573706163650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000762616c616e636500000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x4", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a000000000000140000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000042616c616e636573000000000000000000000000000000000000000000000000000000000000000000000000000000600020010020000000000000000000000000000000000000000000000000000000001001004f000000000000000000000000000000000000000000000000000000002001001f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096e616d6573706163650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000762616c616e636500000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x3", "transactionLogIndex": "0x3", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e7374616c6c65644d6f64756c657300000000000000000000000000000000000000000000000000000000000002200014010014000000000000000000000000000000000000000000000000000000003002004f5f0000000000000000000000000000000000000000000000000000001401006100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000100000000000001a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000a6d6f64756c654e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d617267756d656e74734861736800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d6d6f64756c654164647265737300000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x5", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a00000000100000000000001a00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000496e7374616c6c65644d6f64756c657300000000000000000000000000000000000000000000000000000000000000600014010014000000000000000000000000000000000000000000000000000000003002004f5f0000000000000000000000000000000000000000000000000000001401006100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000a6d6f64756c654e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d617267756d656e74734861736800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d6d6f64756c654164647265737300000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x4", "transactionLogIndex": "0x4", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000044656c65676174696f6e730000000000000000000000000000000000000000000000000000000000000000000000022000200100200000000000000000000000000000000000000000000000000000000028020061610000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000100000000000001a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000964656c656761746f720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000964656c6567617465650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001364656c65676174696f6e436f6e74726f6c496400000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x6", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a00000000100000000000001a0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000044656c65676174696f6e730000000000000000000000000000000000000000000000000000000000000000000000006000200100200000000000000000000000000000000000000000000000000000000028020061610000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000964656c656761746f720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000964656c6567617465650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001364656c65676174696f6e436f6e74726f6c496400000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x5", "transactionLogIndex": "0x5", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000005265736f75726365416363657373000000000000000000000000000000000000000000000000000000000000000002200001010001000000000000000000000000000000000000000000000000000000003402005f610000000000000000000000000000000000000000000000000000000101006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000100000000000001a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000663616c6c6572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000066163636573730000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x7", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a00000000100000000000001a000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000005265736f75726365416363657373000000000000000000000000000000000000000000000000000000000000000000600001010001000000000000000000000000000000000000000000000000000000003402005f610000000000000000000000000000000000000000000000000000000101006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000663616c6c6572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000066163636573730000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x6", "transactionLogIndex": "0x6", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000053797374656d7300000000000000000000000000000000000000000000000000000000000000000000000000000002200015020014010000000000000000000000000000000000000000000000000000002001005f0000000000000000000000000000000000000000000000000000000015020061600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000a0000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f72000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000673797374656d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c7075626c69634163636573730000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x8", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000010000000000a0000000000001a0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000053797374656d7300000000000000000000000000000000000000000000000000000000000000000000000000000000600015020014010000000000000000000000000000000000000000000000000000002001005f000000000000000000000000000000000000000000000000000000001502006160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f72000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000673797374656d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c7075626c69634163636573730000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x7", "transactionLogIndex": "0x7", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000022000240200200400000000000000000000000000000000000000000000000000000004010043000000000000000000000000000000000000000000000000000000002402005f430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000a0000000000001a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001066756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001673797374656d46756e6374696f6e53656c6563746f7200000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x9", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000010000000000a0000000000001a0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000240200200400000000000000000000000000000000000000000000000000000004010043000000000000000000000000000000000000000000000000000000002402005f43000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001066756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001673797374656d46756e6374696f6e53656c6563746f7200000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x8", "transactionLogIndex": "0x8", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000053797374656d486f6f6b73000000000000000000000000000000000000000000000000000000000000000000000001c00000000100000000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000001b600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0xa", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a000000000000140000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000053797374656d486f6f6b73000000000000000000000000000000000000000000000000000000000000000000000000600000000100000000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000001b6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x9", "transactionLogIndex": "0x9", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000053797374656d5265676973747279000000000000000000000000000000000000000000000000000000000000000001c000200100200000000000000000000000000000000000000000000000000000000014010061000000000000000000000000000000000000000000000000000000002001005f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a000000000000140000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000673797374656d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0xb", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a000000000000140000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000053797374656d52656769737472790000000000000000000000000000000000000000000000000000000000000000006000200100200000000000000000000000000000000000000000000000000000000014010061000000000000000000000000000000000000000000000000000000002001005f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000673797374656d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0xa", "transactionLogIndex": "0xa", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000001c00001010001000000000000000000000000000000000000000000000000000000002001005f000000000000000000000000000000000000000000000000000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c7265736f75726365547970650000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0xc", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000600001010001000000000000000000000000000000000000000000000000000000002001005f0000000000000000000000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000107265736f7572636553656c6563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c7265736f75726365547970650000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0xb", "transactionLogIndex": "0xb", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000004e616d6573706163654f776e657200000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0xd", + "data": "0x000000000000000000000000000000004e616d6573706163654f776e6572000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0xc", "transactionLogIndex": "0xc", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636541636365737300000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0xe", + "data": "0x000000000000000000000000000000005265736f75726365416363657373000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0xd", "transactionLogIndex": "0xd", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0xf", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0xe", "transactionLogIndex": "0xe", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000636f72652e730000000000000000000000000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x10", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000636f72652e730000000000000000000000000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0xf", "transactionLogIndex": "0xf", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000053797374656d73000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000636f72652e73000000000000000000000000000000000000000000000000000000000000000000000000000000000015cafac3dd18ac6c6e92c921884f9e4176737c052c010000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x11", + "data": "0x0000000000000000000000000000000053797374656d7300000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000636f72652e73000000000000000000000000000000000000000000000000000000000000000000000000000000000015cafac3dd18ac6c6e92c921884f9e4176737c052c0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x10", "transactionLogIndex": "0x10", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x0000000000000000000000000000000053797374656d526567697374727900000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000636f72652e7300000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x12", + "data": "0x0000000000000000000000000000000053797374656d5265676973747279000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000636f72652e7300000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x11", "transactionLogIndex": "0x11", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636541636365737300000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c00000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x13", + "data": "0x000000000000000000000000000000005265736f75726365416363657373000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cafac3dd18ac6c6e92c921884f9e4176737c052c00000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x12", "transactionLogIndex": "0x12", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000140554c3a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000040554c3a00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x14", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000140554c3a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000040554c3a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x13", "transactionLogIndex": "0x13", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000018d53b20800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000008d53b20800000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x15", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000018d53b20800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000008d53b208000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x14", "transactionLogIndex": "0x14", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000125f6221000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000025f6221000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x16", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000125f6221000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000025f62210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x15", "transactionLogIndex": "0x15", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000012bfaa27400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000002bfaa27400000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x17", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000012bfaa27400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000002bfaa274000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x16", "transactionLogIndex": "0x16", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000121293ca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000021293ca000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x18", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000121293ca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000021293ca0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x17", "transactionLogIndex": "0x17", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000018818929400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000008818929400000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x19", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000013ea8492200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000003ea84922000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x18", "transactionLogIndex": "0x18", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000018da798da00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000008da798da00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1a", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000018da798da00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000008da798da000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x19", "transactionLogIndex": "0x19", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ba51f4900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000000ba51f4900000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1b", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010ba51f4900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000000ba51f49000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1a", "transactionLogIndex": "0x1a", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001530f4b6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000530f4b6000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1c", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001530f4b6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000530f4b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1b", "transactionLogIndex": "0x1b", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010560912900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000000560912900000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1d", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010560912900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e730000000000000000000005609129000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1c", "transactionLogIndex": "0x1c", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a886545e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000a886545e00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1e", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001a886545e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000a886545e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1d", "transactionLogIndex": "0x1d", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d5f8337f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000d5f8337f00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x1f", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001d5f8337f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000d5f8337f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1e", "transactionLogIndex": "0x1e", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a92813ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000a92813ad00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x20", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001a92813ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000a92813ad000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x1f", "transactionLogIndex": "0x1f", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000013350b6a900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000003350b6a900000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x21", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000013350b6a900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000003350b6a9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x20", "transactionLogIndex": "0x20", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000013c03a51c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000003c03a51c00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x22", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000013c03a51c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000003c03a51c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x21", "transactionLogIndex": "0x21", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001b7a3c75600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000b7a3c75600000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x23", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001b7a3c75600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e7300000000000000000000b7a3c756000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x22", "transactionLogIndex": "0x22", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000011d2257ba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000001d2257ba00000000000000000000000000000000000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x24", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000011d2257ba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000636f72652e73000000000000000000001d2257ba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x23", "transactionLogIndex": "0x23", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x00000000000000000000000000000000496e7374616c6c65644d6f64756c6573000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000002636f72652e6d0000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000014e7f1725e7734ce288f8367e1bb143e90bb3f0512000000000000000000000000", - "blockHash": "0xfe43f23153c69149ebe20b5941cccf8c55f4bf3acd82b33445ace71a2c5f8a9c", - "blockNumber": "0x2", - "transactionHash": "0xae82b15a8c62db1e23d9c523e730d9b861eed20529b8cff4d3452f86ae3149df", - "transactionIndex": "0x7", - "logIndex": "0x25", + "data": "0x00000000000000000000000000000000496e7374616c6c65644d6f64756c657300000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002636f72652e6d0000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000014e7f1725e7734ce288f8367e1bb143e90bb3f0512000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x73080a3dddb1f96bc5a4d0b9b7a7f00555b7c11e68683d796252c9a71de6fdfc", + "transactionIndex": "0x0", + "logIndex": "0x24", "transactionLogIndex": "0x24", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c69737400000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x7bca8d7dabf72bfa9ff127d2d2fac95a747b5cbd45e44d820789f14a6d340d2f", - "transactionIndex": "0x0", - "logIndex": "0x0", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265720000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x607b94e53ff6d14c8af149f742239040a26773006b3916fca7403ab56bc1addf", + "transactionIndex": "0x1", + "logIndex": "0x25", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c697374000000000000000000000000000000000000000000000000000000000000000000000000016000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000040000000000000e000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x7bca8d7dabf72bfa9ff127d2d2fac95a747b5cbd45e44d820789f14a6d340d2f", - "transactionIndex": "0x0", - "logIndex": "0x1", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d626572000000000000000000000000000000000000000000000000000000000000000000000000000000000060000401000400000000000000000000000000000000000000000000000000000000040100030000000000000000000000000000000000000000000000000000000004010003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036b65790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x607b94e53ff6d14c8af149f742239040a26773006b3916fca7403ab56bc1addf", + "transactionIndex": "0x1", + "logIndex": "0x26", "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000437573746f6d4572726f72735379737400000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xb5f9defc11305940c4b08a6f34cdb4a671fde33905f288048b70c85b1d0875eb", - "transactionIndex": "0x1", - "logIndex": "0x2", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000566563746f720000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x5a88a5686556db275ec64403ada8ee92a761403bfd712f21b6746aabb0029f12", + "transactionIndex": "0x2", + "logIndex": "0x27", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000053797374656d73000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000437573746f6d4572726f72735379737400000000000000000000000000000000000000000000000000000000000000155fc8d32690cc91d4c39d9d3abcbd16989f875707010000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xb5f9defc11305940c4b08a6f34cdb4a671fde33905f288048b70c85b1d0875eb", - "transactionIndex": "0x1", - "logIndex": "0x3", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000010000000000a0000000000001a00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000566563746f7200000000000000000000000000000000000000000000000000000000000000000000000000000000006000080200040400000000000000000000000000000000000000000000000000000004010003000000000000000000000000000000000000000000000000000000000802002323000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036b6579000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017900000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x5a88a5686556db275ec64403ada8ee92a761403bfd712f21b6746aabb0029f12", + "transactionIndex": "0x2", + "logIndex": "0x28", "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x0000000000000000000000000000000053797374656d526567697374727900000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000437573746f6d4572726f727353797374", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xb5f9defc11305940c4b08a6f34cdb4a671fde33905f288048b70c85b1d0875eb", - "transactionIndex": "0x1", - "logIndex": "0x4", - "transactionLogIndex": "0x2", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c69737400000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xaed15d9b279865d827e86039036fcc9d86c5467e36441834ba1f78991785229d", + "transactionIndex": "0x3", + "logIndex": "0x29", + "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x000000000000000000000000000000005265736f7572636541636365737300000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f87570700000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xb5f9defc11305940c4b08a6f34cdb4a671fde33905f288048b70c85b1d0875eb", - "transactionIndex": "0x1", - "logIndex": "0x5", - "transactionLogIndex": "0x3", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000a00000000040000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c697374000000000000000000000000000000000000000000000000000000000000000000000000006000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xaed15d9b279865d827e86039036fcc9d86c5467e36441834ba1f78991785229d", + "transactionIndex": "0x3", + "logIndex": "0x2a", + "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c69737453797374656d00000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x5e56042175cee9139c0439cc0df13d0efbdb1a57e93e5353f8fc713ea061bfce", - "transactionIndex": "0x2", - "logIndex": "0x6", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004d756c7469000000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x126a9b9fbea2e94295dfd3b380c2f43f4fc96fe1ee732e4d3df29b2433ac5dc1", + "transactionIndex": "0x4", + "logIndex": "0x2b", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000053797374656d73000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c69737453797374656d00000000000000000000000000000000000000000000000000000000000000150165878a594ca255338adfa4d48449f69242eb8f010000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x5e56042175cee9139c0439cc0df13d0efbdb1a57e93e5353f8fc713ea061bfce", - "transactionIndex": "0x2", - "logIndex": "0x7", + "data": "0x6d756473746f726500000000000000005461626c65730000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000010000000001c0000000000002c000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004d756c74690000000000000000000000000000000000000000000000000000000000000000000000000000000000006000210200200100000000000000000000000000000000000000000000000000000034040003601f2e000000000000000000000000000000000000000000000000002102003f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000162000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000036e756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x126a9b9fbea2e94295dfd3b380c2f43f4fc96fe1ee732e4d3df29b2433ac5dc1", + "transactionIndex": "0x4", + "logIndex": "0x2c", "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x0000000000000000000000000000000053797374656d526567697374727900000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000004e756d6265724c69737453797374656d", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x5e56042175cee9139c0439cc0df13d0efbdb1a57e93e5353f8fc713ea061bfce", - "transactionIndex": "0x2", - "logIndex": "0x8", - "transactionLogIndex": "0x2", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000437573746f6d4572726f72735379737400000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xfb5bf0734dca864b8b13107223cb02facd6803ef1a03f39b325cd96a5ab4fd02", + "transactionIndex": "0x5", + "logIndex": "0x2d", + "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x000000000000000000000000000000005265736f7572636541636365737300000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f00000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x5e56042175cee9139c0439cc0df13d0efbdb1a57e93e5353f8fc713ea061bfce", - "transactionIndex": "0x2", - "logIndex": "0x9", - "transactionLogIndex": "0x3", + "data": "0x0000000000000000000000000000000053797374656d7300000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000437573746f6d4572726f72735379737400000000000000000000000000000000000000000000000000000000000000155fc8d32690cc91d4c39d9d3abcbd16989f8757070100000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xfb5bf0734dca864b8b13107223cb02facd6803ef1a03f39b325cd96a5ab4fd02", + "transactionIndex": "0x5", + "logIndex": "0x2e", + "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000566563746f720000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xb3febd63ed51098bd7062390e4e859508e55a1bbfa5ee8ec197a4df93586f632", - "transactionIndex": "0x3", - "logIndex": "0xa", - "transactionLogIndex": "0x0", + "data": "0x0000000000000000000000000000000053797374656d5265676973747279000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000437573746f6d4572726f727353797374", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xfb5bf0734dca864b8b13107223cb02facd6803ef1a03f39b325cd96a5ab4fd02", + "transactionIndex": "0x5", + "logIndex": "0x2f", + "transactionLogIndex": "0x2", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000566563746f72000000000000000000000000000000000000000000000000000000000000000000000000000000000220000802000404000000000000000000000000000000000000000000000000000000040100030000000000000000000000000000000000000000000000000000000008020023230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000a0000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036b6579000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017900000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xb3febd63ed51098bd7062390e4e859508e55a1bbfa5ee8ec197a4df93586f632", - "transactionIndex": "0x3", - "logIndex": "0xb", - "transactionLogIndex": "0x1", + "data": "0x000000000000000000000000000000005265736f75726365416363657373000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f87570700000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xfb5bf0734dca864b8b13107223cb02facd6803ef1a03f39b325cd96a5ab4fd02", + "transactionIndex": "0x5", + "logIndex": "0x30", + "transactionLogIndex": "0x3", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265720000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xd9aea844188d36cd599b3d9ddd523b1269131ec10ebf5b107a50bffe8a03985e", - "transactionIndex": "0x4", - "logIndex": "0xc", + "data": "0x000000000000000000000000000000005265736f75726365547970650000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c69737453797374656d00000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x3a45c7867bdaafbebbfe02dc2eb9c7dedd4b6fd27f2540febb2fb49c8caa6ea5", + "transactionIndex": "0x6", + "logIndex": "0x31", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265720000000000000000000000000000000000000000000000000000000000000000000000000000000001c000040100040000000000000000000000000000000000000000000000000000000004010003000000000000000000000000000000000000000000000000000000000401000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000a00000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036b65790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xd9aea844188d36cd599b3d9ddd523b1269131ec10ebf5b107a50bffe8a03985e", - "transactionIndex": "0x4", - "logIndex": "0xd", + "data": "0x0000000000000000000000000000000053797374656d7300000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004e756d6265724c69737453797374656d00000000000000000000000000000000000000000000000000000000000000150165878a594ca255338adfa4d48449f69242eb8f0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x3a45c7867bdaafbebbfe02dc2eb9c7dedd4b6fd27f2540febb2fb49c8caa6ea5", + "transactionIndex": "0x6", + "logIndex": "0x32", "transactionLogIndex": "0x1", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x000000000000000000000000000000005265736f7572636554797065000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004d756c7469000000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xa50c88a503c25f17dc6c8e4cc779f15341f6f8242da501989a64ec87a8f5a85b", - "transactionIndex": "0x5", - "logIndex": "0xe", - "transactionLogIndex": "0x0", + "data": "0x0000000000000000000000000000000053797374656d5265676973747279000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000004e756d6265724c69737453797374656d", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x3a45c7867bdaafbebbfe02dc2eb9c7dedd4b6fd27f2540febb2fb49c8caa6ea5", + "transactionIndex": "0x6", + "logIndex": "0x33", + "transactionLogIndex": "0x2", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xe7b614a59a30dff5ea0536e50c4019312e856b636126e1b2d19a7c2872798735" ], - "data": "0x6d756473746f726500000000000000005461626c657300000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000004d756c74690000000000000000000000000000000000000000000000000000000000000000000000000000000000034000210200200100000000000000000000000000000000000000000000000000000034040003601f2e000000000000000000000000000000000000000000000000002102003f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000001c0000000000002c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000162000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000036e756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xa50c88a503c25f17dc6c8e4cc779f15341f6f8242da501989a64ec87a8f5a85b", - "transactionIndex": "0x5", - "logIndex": "0xf", - "transactionLogIndex": "0x1", + "data": "0x000000000000000000000000000000005265736f75726365416363657373000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f00000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x3a45c7867bdaafbebbfe02dc2eb9c7dedd4b6fd27f2540febb2fb49c8caa6ea5", + "transactionIndex": "0x6", + "logIndex": "0x34", + "transactionLogIndex": "0x3", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000015f644e3c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000437573746f6d4572726f7273537973745f644e3c00000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xff27259bf2a15d32d629b5f2dce0f7b248400c760cd4a4f367670e87b4cf81fb", - "transactionIndex": "0x6", - "logIndex": "0x10", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000015f644e3c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000437573746f6d4572726f7273537973745f644e3c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x99183c2ecbd4300f807d9903e3a0048eb27d80386fd655ff4b3bdf4aea5d5208", + "transactionIndex": "0x7", + "logIndex": "0x35", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001b8a44c7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656db8a44c7c00000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x704d3a95376db47cabae3ba83743e46e1eb751b82a9b622af5a555e109d59a66", - "transactionIndex": "0x7", - "logIndex": "0x11", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001a4ece52c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656da4ece52c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xa96d498c47174e6315617f6ef8e0fd6e93974a63c77152d173803f070f8023a6", + "transactionIndex": "0x8", + "logIndex": "0x36", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a4ece52c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656da4ece52c00000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x9d7756b56cd32ba0a3305788a5c412eae328b0a28c4ff2e46f5db939a1190a77", - "transactionIndex": "0x8", - "logIndex": "0x12", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001f7f0e440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656df7f0e440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x77b03de8eef7f084dea0958f5f69c881d3ff3ae5df88d32110e9095536640b66", + "transactionIndex": "0x9", + "logIndex": "0x37", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001f7f0e440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656df7f0e44000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0x8a7d4a27b5a121f77b0011d084850b4502db7cc7e30f5a22e1fdf61247eac74c", - "transactionIndex": "0x9", - "logIndex": "0x13", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001306d61a5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656d306d61a5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0x6be14198e38f5635f834446da7a3594826ce7a404ae5e238b24c5a9cac751a74", + "transactionIndex": "0xa", + "logIndex": "0x38", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0x912af873e852235aae78a1d25ae9bb28b616a67c36898c53a14fd8184504ee32" + "0xc851f86da4b2f7d69c86fc8dc136b05aa9805e49c225918a8856af392597a34f" ], - "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001306d61a5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656d306d61a500000000000000000000000000000000000000000000000000000000", - "blockHash": "0x40b6d1b700f1f3dc080ebe2d54c3553e6419da61cfeb5a5e2e2272ebe9840b9a", - "blockNumber": "0x3", - "transactionHash": "0xf97efde38e1aaee38023ff6669c33b26ac5230f364af583b6159dfec83099c02", - "transactionIndex": "0xa", - "logIndex": "0x14", + "data": "0x0000000000000000000000000000000046756e6374696f6e53656c6563746f7200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001b8a44c7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000004e756d6265724c69737453797374656db8a44c7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xc67f4cbf009a2ede672e0fff1e8bcc4b1e46db4145886d6942308d0d3b985941", + "blockNumber": "0x5", + "transactionHash": "0xbe213a215bd71f17f867b8129a24630241f970e1cf5231f8a3bb2ba412b68a70", + "transactionIndex": "0xb", + "logIndex": "0x39", "transactionLogIndex": "0x0", "removed": false }, { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0x5599f2052ade9808af7738adc73853c23894a2f1e944c827f7f6b8e948c4e885" ], - "data": "0x000000000000000000000000000000004e756d6265724c6973740000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000001a400000000000000000000000000000000000000000000000000000000", - "blockHash": "0xcc9c86d971dbe128cf77e6c94d85c369dc31f6a9b0b75324cc5feb5215a4b045", - "blockNumber": "0x4", - "transactionHash": "0x6b11c4b4d9257da889cd755a9aef98068339dea0770f3c43def5293c47977876", + "data": "0x000000000000000000000000000000004e756d6265724c69737400000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000040000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000001a400000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbca51e861d5bfa7243f5c4b4dd5bf4e38874df40b4489bd87e14e0dca7fb434d", + "blockNumber": "0x6", + "transactionHash": "0x913934c3603cedb30d6ec5131ca347ce5f3906f6146f7fdfe39ad6008e6e0a94", "transactionIndex": "0x0", "logIndex": "0x0", "transactionLogIndex": "0x0", @@ -828,12 +828,12 @@ { "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "topics": [ - "0xd01f9f1368f831528fc9fe6442366b2b7d957fbfff3bcf7c24d9ab5fe51f8c46" + "0x5599f2052ade9808af7738adc73853c23894a2f1e944c827f7f6b8e948c4e885" ], - "data": "0x000000000000000000000000000000004e756d6265724c6973740000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000001a400000045000000000000000000000000000000000000000000000000", - "blockHash": "0xcc9c86d971dbe128cf77e6c94d85c369dc31f6a9b0b75324cc5feb5215a4b045", - "blockNumber": "0x4", - "transactionHash": "0x2453e912dfab0eed54e6140d2f5c829034fe51fa844767991510eb71ecff8a3b", + "data": "0x000000000000000000000000000000004e756d6265724c69737400000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000800000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000004500000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbca51e861d5bfa7243f5c4b4dd5bf4e38874df40b4489bd87e14e0dca7fb434d", + "blockNumber": "0x6", + "transactionHash": "0xa5e0a4df59ae785eb7f2abc61ab104cbd97db921a61ad3a5a34b7a959d543ca6", "transactionIndex": "0x1", "logIndex": "0x1", "transactionLogIndex": "0x0",