From 514c3b74459847b744fa46ac780c53734a052f67 Mon Sep 17 00:00:00 2001 From: vdrg Date: Tue, 3 Dec 2024 13:15:39 -0300 Subject: [PATCH 01/25] wip crosschain module --- packages/world-module-crosschain/.gitignore | 2 + .../world-module-crosschain/.solhint.json | 8 + packages/world-module-crosschain/CHANGELOG.md | 146 ++++++ packages/world-module-crosschain/README.md | 1 + packages/world-module-crosschain/foundry.toml | 15 + .../world-module-crosschain/mud.config.ts | 18 + packages/world-module-crosschain/package.json | 63 +++ .../world-module-crosschain/remappings.txt | 4 + .../src/CrosschainModule.sol | 45 ++ .../src/CrosschainSystem.sol | 63 +++ .../src/codegen/index.sol | 6 + .../tables/CrosschainRecordMetadata.sol | 422 ++++++++++++++++++ .../src/codegen/world/ICrosschainSystem.sol | 21 + .../src/codegen/world/IWorld.sol | 16 + packages/world-module-crosschain/ts/build.ts | 18 + .../world-module-crosschain/tsconfig.json | 7 + .../world-module-crosschain/tsup.config.ts | 18 + pnpm-lock.yaml | 53 ++- 18 files changed, 923 insertions(+), 3 deletions(-) create mode 100644 packages/world-module-crosschain/.gitignore create mode 100644 packages/world-module-crosschain/.solhint.json create mode 100644 packages/world-module-crosschain/CHANGELOG.md create mode 100644 packages/world-module-crosschain/README.md create mode 100644 packages/world-module-crosschain/foundry.toml create mode 100644 packages/world-module-crosschain/mud.config.ts create mode 100644 packages/world-module-crosschain/package.json create mode 100644 packages/world-module-crosschain/remappings.txt create mode 100644 packages/world-module-crosschain/src/CrosschainModule.sol create mode 100644 packages/world-module-crosschain/src/CrosschainSystem.sol create mode 100644 packages/world-module-crosschain/src/codegen/index.sol create mode 100644 packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol create mode 100644 packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol create mode 100644 packages/world-module-crosschain/src/codegen/world/IWorld.sol create mode 100644 packages/world-module-crosschain/ts/build.ts create mode 100644 packages/world-module-crosschain/tsconfig.json create mode 100644 packages/world-module-crosschain/tsup.config.ts diff --git a/packages/world-module-crosschain/.gitignore b/packages/world-module-crosschain/.gitignore new file mode 100644 index 0000000000..1e4ded714a --- /dev/null +++ b/packages/world-module-crosschain/.gitignore @@ -0,0 +1,2 @@ +cache +out diff --git a/packages/world-module-crosschain/.solhint.json b/packages/world-module-crosschain/.solhint.json new file mode 100644 index 0000000000..4e2baa8be7 --- /dev/null +++ b/packages/world-module-crosschain/.solhint.json @@ -0,0 +1,8 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["error", ">=0.8.0"], + "avoid-low-level-calls": "off", + "func-visibility": ["warn", { "ignoreConstructors": true }] + } +} diff --git a/packages/world-module-crosschain/CHANGELOG.md b/packages/world-module-crosschain/CHANGELOG.md new file mode 100644 index 0000000000..2dbdd1d7ab --- /dev/null +++ b/packages/world-module-crosschain/CHANGELOG.md @@ -0,0 +1,146 @@ +# @latticexyz/world-module-metadata + +## 2.2.14 + +### Patch Changes + +- @latticexyz/schema-type@2.2.14 +- @latticexyz/store@2.2.14 +- @latticexyz/world@2.2.14 + +## 2.2.13 + +### Patch Changes + +- @latticexyz/schema-type@2.2.13 +- @latticexyz/store@2.2.13 +- @latticexyz/world@2.2.13 + +## 2.2.12 + +### Patch Changes + +- Updated dependencies [ea18f27] + - @latticexyz/schema-type@2.2.12 + - @latticexyz/store@2.2.12 + - @latticexyz/world@2.2.12 + +## 2.2.11 + +### Patch Changes + +- Updated dependencies [7ddcf64] +- Updated dependencies [13e5689] + - @latticexyz/store@2.2.11 + - @latticexyz/world@2.2.11 + - @latticexyz/schema-type@2.2.11 + +## 2.2.10 + +### Patch Changes + +- Updated dependencies [9d7fc85] + - @latticexyz/world@2.2.10 + - @latticexyz/schema-type@2.2.10 + - @latticexyz/store@2.2.10 + +## 2.2.9 + +### Patch Changes + +- @latticexyz/schema-type@2.2.9 +- @latticexyz/store@2.2.9 +- @latticexyz/world@2.2.9 + +## 2.2.8 + +### Patch Changes + +- @latticexyz/store@2.2.8 +- @latticexyz/world@2.2.8 +- @latticexyz/schema-type@2.2.8 + +## 2.2.7 + +### Patch Changes + +- Updated dependencies [a08ba5e] + - @latticexyz/store@2.2.7 + - @latticexyz/world@2.2.7 + - @latticexyz/schema-type@2.2.7 + +## 2.2.6 + +### Patch Changes + +- @latticexyz/schema-type@2.2.6 +- @latticexyz/store@2.2.6 +- @latticexyz/world@2.2.6 + +## 2.2.5 + +### Patch Changes + +- @latticexyz/schema-type@2.2.5 +- @latticexyz/store@2.2.5 +- @latticexyz/world@2.2.5 + +## 2.2.4 + +### Patch Changes + +- Updated dependencies [50010fb] +- Updated dependencies [1f24978] + - @latticexyz/schema-type@2.2.4 + - @latticexyz/store@2.2.4 + - @latticexyz/world@2.2.4 + +## 2.2.3 + +### Patch Changes + +- Updated dependencies [8546452] + - @latticexyz/world@2.2.3 + - @latticexyz/schema-type@2.2.3 + - @latticexyz/store@2.2.3 + +## 2.2.2 + +### Patch Changes + +- @latticexyz/schema-type@2.2.2 +- @latticexyz/store@2.2.2 +- @latticexyz/world@2.2.2 + +## 2.2.1 + +### Patch Changes + +- @latticexyz/store@2.2.1 +- @latticexyz/world@2.2.1 +- @latticexyz/schema-type@2.2.1 + +## 2.2.0 + +### Patch Changes + +- Updated dependencies [04c675c] +- Updated dependencies [04c675c] + - @latticexyz/store@2.2.0 + - @latticexyz/world@2.2.0 + - @latticexyz/schema-type@2.2.0 + +## 2.1.1 + +### Patch Changes + +- 6a66f57: Refactored `AccessControl` library exported from `@latticexyz/world` to be usable outside of the world package and updated module packages to use it. +- fad4e85: Added metadata module to be automatically installed during world deploy. This module allows for tagging any resource with arbitrary metadata. Internally, we'll use this to tag resources with labels onchain so that we can use labels to create a MUD project from an existing world. +- Updated dependencies [9e21e42] +- Updated dependencies [6a66f57] +- Updated dependencies [86a8104] +- Updated dependencies [542ea54] +- Updated dependencies [57bf8c3] + - @latticexyz/schema-type@2.1.1 + - @latticexyz/store@2.1.1 + - @latticexyz/world@2.1.1 diff --git a/packages/world-module-crosschain/README.md b/packages/world-module-crosschain/README.md new file mode 100644 index 0000000000..bb63585de0 --- /dev/null +++ b/packages/world-module-crosschain/README.md @@ -0,0 +1 @@ +# Metadata world module diff --git a/packages/world-module-crosschain/foundry.toml b/packages/world-module-crosschain/foundry.toml new file mode 100644 index 0000000000..f0e017f5a0 --- /dev/null +++ b/packages/world-module-crosschain/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +solc = "0.8.24" +ffi = false +fuzz_runs = 256 +optimizer = true +optimizer_runs = 3000 +verbosity = 2 +allow_paths = ["../../node_modules", "../"] +src = "src" +out = "out" +bytecode_hash = "none" +extra_output_files = [ + "abi", + "evm.bytecode" +] diff --git a/packages/world-module-crosschain/mud.config.ts b/packages/world-module-crosschain/mud.config.ts new file mode 100644 index 0000000000..756302ae59 --- /dev/null +++ b/packages/world-module-crosschain/mud.config.ts @@ -0,0 +1,18 @@ +import { defineWorld } from "@latticexyz/world"; + +export default defineWorld({ + namespace: "root", + tables: { + CrosschainRecordMetadata: { + schema: { + // keccak256(abi.encodePacked(tableId, keyTuple)) + recordId: "bytes32", + blockNumber: "uint256", + logIndex: "uint256", + timestamp: "uint256", + chainId: "uint256", + }, + key: ["recordId"], + }, + }, +}); diff --git a/packages/world-module-crosschain/package.json b/packages/world-module-crosschain/package.json new file mode 100644 index 0000000000..f504ee2386 --- /dev/null +++ b/packages/world-module-crosschain/package.json @@ -0,0 +1,63 @@ +{ + "name": "@latticexyz/world-module-crosschain", + "version": "2.2.14", + "description": "Crosschain world module", + "repository": { + "type": "git", + "url": "https://github.com/latticexyz/mud.git", + "directory": "packages/world-module-crosschain" + }, + "license": "MIT", + "type": "module", + "exports": { + "./mud.config": "./dist/mud.config.js", + "./out/*": "./out/*" + }, + "typesVersions": { + "*": { + "mud.config": [ + "./dist/mud.config.d.ts" + ] + } + }, + "files": [ + "dist", + "out", + "src" + ], + "scripts": { + "build": "pnpm run build:mud && pnpm run build:abi && pnpm run build:abi-ts && pnpm run build:js", + "build:abi": "forge build", + "build:abi-ts": "abi-ts", + "build:js": "tsup", + "build:mud": "tsx ./ts/build.ts", + "clean": "pnpm run clean:abi && pnpm run clean:js && pnpm run clean:mud", + "clean:abi": "forge clean", + "clean:js": "shx rm -rf dist", + "clean:mud": "shx rm -rf src/**/codegen", + "dev": "tsup --watch", + "gas-report": "gas-report --save gas-report.json", + "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", + "test": "forge test", + "test:ci": "pnpm run test" + }, + "dependencies": { + "@latticexyz/schema-type": "workspace:*", + "@latticexyz/store": "workspace:*", + "@latticexyz/world": "workspace:*", + "optimism": "https://github.com/ethereum-optimism/optimism" + }, + "devDependencies": { + "@latticexyz/abi-ts": "workspace:*", + "@latticexyz/gas-report": "workspace:*", + "@types/node": "^18.15.11", + "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", + "forge-std": "https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1", + "solhint": "^3.3.7", + "tsup": "^6.7.0", + "vitest": "0.34.6" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/world-module-crosschain/remappings.txt b/packages/world-module-crosschain/remappings.txt new file mode 100644 index 0000000000..e7777afad9 --- /dev/null +++ b/packages/world-module-crosschain/remappings.txt @@ -0,0 +1,4 @@ +ds-test/=node_modules/ds-test/src/ +forge-std/=node_modules/forge-std/src/ +@latticexyz/=node_modules/@latticexyz/ +@contracts-bedrock/=node_modules/optimism/packages/contracts-bedrock/src/ diff --git a/packages/world-module-crosschain/src/CrosschainModule.sol b/packages/world-module-crosschain/src/CrosschainModule.sol new file mode 100644 index 0000000000..3864dd4bd0 --- /dev/null +++ b/packages/world-module-crosschain/src/CrosschainModule.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; +import { Module } from "@latticexyz/world/src/Module.sol"; +import { AccessControl } from "@latticexyz/world/src/AccessControl.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; +import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; +import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; + +import { CrosschainSystem } from "./CrosschainSystem.sol"; +import { CrosschainRecordMetadata } from "./codegen/tables/CrosschainRecordMetadata.sol"; + +contract CrosschainModule is Module { + using WorldResourceIdInstance for ResourceId; + + CrosschainSystem private immutable crosschainSystem = new CrosschainSystem(); + + function installRoot(bytes memory) public { + IBaseWorld world = IBaseWorld(_world()); + + if (!ResourceIds.getExists(CrosschainRecordMetadata._tableId)) { + CrosschainRecordMetadata.register(); + } + + ResourceId crosschainSystemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, ROOT_NAMESPACE, "CrosschainSystem"); + if (!ResourceIds.getExists(crosschainSystemId)) { + (bool registrationSuccess, bytes memory registrationReturnData) = address(world).delegatecall( + abi.encodeCall(world.registerSystem, (crosschainSystemId, crosschainSystem, true)) + ); + if (!registrationSuccess) revertWithBytes(registrationReturnData); + // world.registerFunctionSelector(crosschainSystemId, "crosschainRead(bytes32,bytes32[])"); + // world.registerFunctionSelector( + // crosschainSystemId, + // "crosschainWrite((address,uint256,uint256,uint256,uint256),bytes)" + // ); + } + } + + function install(bytes memory) public pure { + revert Module_NonRootInstallNotSupported(); + } +} diff --git a/packages/world-module-crosschain/src/CrosschainSystem.sol b/packages/world-module-crosschain/src/CrosschainSystem.sol new file mode 100644 index 0000000000..b3fa4719c5 --- /dev/null +++ b/packages/world-module-crosschain/src/CrosschainSystem.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { EncodedLengths } from "@latticexyz/store/src/EncodedLengths.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; + +import { System } from "@latticexyz/world/src/System.sol"; +import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; +import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + +import { Identifier, ICrossL2Inbox } from "@contracts-bedrock/L2/interfaces/ICrossL2Inbox.sol"; +import { Predeploys } from "@contracts-bedrock/libraries/Predeploys.sol"; +import { CrosschainRecordMetadata, CrosschainRecordMetadataData } from "./codegen/tables/CrosschainRecordMetadata.sol"; + +contract CrosschainSystem is System { + // TODO: rename errors and events + error WrongWorld(); + error NotCrosschainRead(); + + event World_CrosschainRead( + ResourceId indexed tableId, + bytes32[] keyTuple, + bytes staticData, + EncodedLengths encodedLengths, + bytes dynamicData + ); + + function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external { + (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( + tableId, + keyTuple + ); + + emit World_CrosschainRead(tableId, keyTuple, staticData, encodedLengths, dynamicData); + } + + function crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external { + if (identifier.origin != address(this)) revert WrongWorld(); + + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(identifier, keccak256(_crosschainRead)); + + (bytes32 selector, ResourceId tableId) = abi.decode(_crosschainRead[:64], (bytes32, ResourceId)); + if (selector != World_CrosschainRead.selector) revert NotCrosschainRead(); + // TODO: check tableId resource type? + + (bytes32[] memory keyTuple, bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = abi + .decode(_crosschainRead[64:], (bytes32[], bytes, EncodedLengths, bytes)); + + bytes32 recordId = keccak256(abi.encode(tableId, keyTuple)); + CrosschainRecordMetadata.set( + recordId, + CrosschainRecordMetadataData({ + blockNumber: identifier.blockNumber, + logIndex: identifier.logIndex, + timestamp: identifier.timestamp, + chainId: identifier.chainId + }) + ); + + StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData); + } +} diff --git a/packages/world-module-crosschain/src/codegen/index.sol b/packages/world-module-crosschain/src/codegen/index.sol new file mode 100644 index 0000000000..131b21b9ac --- /dev/null +++ b/packages/world-module-crosschain/src/codegen/index.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { CrosschainRecordMetadata, CrosschainRecordMetadataData } from "./tables/CrosschainRecordMetadata.sol"; diff --git a/packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol b/packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol new file mode 100644 index 0000000000..6591c6270c --- /dev/null +++ b/packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +struct CrosschainRecordMetadataData { + uint256 blockNumber; + uint256 logIndex; + uint256 timestamp; + uint256 chainId; +} + +library CrosschainRecordMetadata { + // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "root", name: "CrosschainRecord", typeId: RESOURCE_TABLE });` + ResourceId constant _tableId = ResourceId.wrap(0x7462726f6f740000000000000000000043726f7373636861696e5265636f7264); + + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0080040020202020000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (bytes32) + Schema constant _keySchema = Schema.wrap(0x002001005f000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256, uint256, uint256, uint256) + Schema constant _valueSchema = Schema.wrap(0x008004001f1f1f1f000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](1); + keyNames[0] = "recordId"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](4); + fieldNames[0] = "blockNumber"; + fieldNames[1] = "logIndex"; + fieldNames[2] = "timestamp"; + fieldNames[3] = "chainId"; + } + + /** + * @notice Register the table with its config. + */ + function register() internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register() internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get blockNumber. + */ + function getBlockNumber(bytes32 recordId) internal view returns (uint256 blockNumber) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get blockNumber. + */ + function _getBlockNumber(bytes32 recordId) internal view returns (uint256 blockNumber) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set blockNumber. + */ + function setBlockNumber(bytes32 recordId, uint256 blockNumber) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); + } + + /** + * @notice Set blockNumber. + */ + function _setBlockNumber(bytes32 recordId, uint256 blockNumber) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); + } + + /** + * @notice Get logIndex. + */ + function getLogIndex(bytes32 recordId) internal view returns (uint256 logIndex) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get logIndex. + */ + function _getLogIndex(bytes32 recordId) internal view returns (uint256 logIndex) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set logIndex. + */ + function setLogIndex(bytes32 recordId, uint256 logIndex) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((logIndex)), _fieldLayout); + } + + /** + * @notice Set logIndex. + */ + function _setLogIndex(bytes32 recordId, uint256 logIndex) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((logIndex)), _fieldLayout); + } + + /** + * @notice Get timestamp. + */ + function getTimestamp(bytes32 recordId) internal view returns (uint256 timestamp) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get timestamp. + */ + function _getTimestamp(bytes32 recordId) internal view returns (uint256 timestamp) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set timestamp. + */ + function setTimestamp(bytes32 recordId, uint256 timestamp) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 2, abi.encodePacked((timestamp)), _fieldLayout); + } + + /** + * @notice Set timestamp. + */ + function _setTimestamp(bytes32 recordId, uint256 timestamp) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.setStaticField(_tableId, _keyTuple, 2, abi.encodePacked((timestamp)), _fieldLayout); + } + + /** + * @notice Get chainId. + */ + function getChainId(bytes32 recordId) internal view returns (uint256 chainId) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get chainId. + */ + function _getChainId(bytes32 recordId) internal view returns (uint256 chainId) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set chainId. + */ + function setChainId(bytes32 recordId, uint256 chainId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 3, abi.encodePacked((chainId)), _fieldLayout); + } + + /** + * @notice Set chainId. + */ + function _setChainId(bytes32 recordId, uint256 chainId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.setStaticField(_tableId, _keyTuple, 3, abi.encodePacked((chainId)), _fieldLayout); + } + + /** + * @notice Get the full data. + */ + function get(bytes32 recordId) internal view returns (CrosschainRecordMetadataData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Get the full data. + */ + function _get(bytes32 recordId) internal view returns (CrosschainRecordMetadataData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function set(bytes32 recordId, uint256 blockNumber, uint256 logIndex, uint256 timestamp, uint256 chainId) internal { + bytes memory _staticData = encodeStatic(blockNumber, logIndex, timestamp, chainId); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function _set(bytes32 recordId, uint256 blockNumber, uint256 logIndex, uint256 timestamp, uint256 chainId) internal { + bytes memory _staticData = encodeStatic(blockNumber, logIndex, timestamp, chainId); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Set the full data using the data struct. + */ + function set(bytes32 recordId, CrosschainRecordMetadataData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.blockNumber, _table.logIndex, _table.timestamp, _table.chainId); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using the data struct. + */ + function _set(bytes32 recordId, CrosschainRecordMetadataData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.blockNumber, _table.logIndex, _table.timestamp, _table.chainId); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Decode the tightly packed blob of static data using this table's field layout. + */ + function decodeStatic( + bytes memory _blob + ) internal pure returns (uint256 blockNumber, uint256 logIndex, uint256 timestamp, uint256 chainId) { + blockNumber = (uint256(Bytes.getBytes32(_blob, 0))); + + logIndex = (uint256(Bytes.getBytes32(_blob, 32))); + + timestamp = (uint256(Bytes.getBytes32(_blob, 64))); + + chainId = (uint256(Bytes.getBytes32(_blob, 96))); + } + + /** + * @notice Decode the tightly packed blobs using this table's field layout. + * @param _staticData Tightly packed static fields. + * + * + */ + function decode( + bytes memory _staticData, + EncodedLengths, + bytes memory + ) internal pure returns (CrosschainRecordMetadataData memory _table) { + (_table.blockNumber, _table.logIndex, _table.timestamp, _table.chainId) = decodeStatic(_staticData); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(bytes32 recordId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(bytes32 recordId) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic( + uint256 blockNumber, + uint256 logIndex, + uint256 timestamp, + uint256 chainId + ) internal pure returns (bytes memory) { + return abi.encodePacked(blockNumber, logIndex, timestamp, chainId); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode( + uint256 blockNumber, + uint256 logIndex, + uint256 timestamp, + uint256 chainId + ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(blockNumber, logIndex, timestamp, chainId); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(bytes32 recordId) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = recordId; + + return _keyTuple; + } +} diff --git a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol new file mode 100644 index 0000000000..97fb59effc --- /dev/null +++ b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { Identifier } from "@contracts-bedrock/L2/interfaces/ICrossL2Inbox.sol"; + +/** + * @title ICrosschainSystem + * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) + * @dev This interface is automatically generated from the corresponding system contract. Do not edit manually. + */ +interface ICrosschainSystem { + error WrongWorld(); + error NotCrosschainRead(); + + function root__crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external; + + function root__crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external; +} diff --git a/packages/world-module-crosschain/src/codegen/world/IWorld.sol b/packages/world-module-crosschain/src/codegen/world/IWorld.sol new file mode 100644 index 0000000000..03eb47ea8a --- /dev/null +++ b/packages/world-module-crosschain/src/codegen/world/IWorld.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { ICrosschainSystem } from "./ICrosschainSystem.sol"; + +/** + * @title IWorld + * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) + * @notice This interface integrates all systems and associated function selectors + * that are dynamically registered in the World during deployment. + * @dev This is an autogenerated file; do not edit manually. + */ +interface IWorld is IBaseWorld, ICrosschainSystem {} diff --git a/packages/world-module-crosschain/ts/build.ts b/packages/world-module-crosschain/ts/build.ts new file mode 100644 index 0000000000..9f24536f54 --- /dev/null +++ b/packages/world-module-crosschain/ts/build.ts @@ -0,0 +1,18 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { tablegen } from "@latticexyz/store/codegen"; +import { worldgen } from "@latticexyz/world/node"; + +/** + * To avoid circular dependencies, we run a very similar `build` step as `cli` package here. + */ + +// TODO: move tablegen/worldgen to CLI commands from store/world we can run in package.json instead of a custom script +// (https://github.com/latticexyz/mud/issues/3030) + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const configPath = "../mud.config"; + +const { default: config } = await import(configPath); +const rootDir = path.dirname(path.join(__dirname, configPath)); +await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]); diff --git a/packages/world-module-crosschain/tsconfig.json b/packages/world-module-crosschain/tsconfig.json new file mode 100644 index 0000000000..9b0bf57752 --- /dev/null +++ b/packages/world-module-crosschain/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["mud.config.ts", "ts"] +} diff --git a/packages/world-module-crosschain/tsup.config.ts b/packages/world-module-crosschain/tsup.config.ts new file mode 100644 index 0000000000..1636dcc6c3 --- /dev/null +++ b/packages/world-module-crosschain/tsup.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "tsup"; + +export default defineConfig((opts) => ({ + entry: { + "mud.config": "mud.config.ts", + }, + target: "esnext", + format: ["esm"], + sourcemap: true, + minify: true, + // don't generate DTS during watch mode because it's slow + // we're likely using TS source in this mode anyway + dts: !opts.watch, + // don't clean during watch mode to avoid removing + // previously-built DTS files, which other build tasks + // depend on + clean: !opts.watch, +})); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3db6ff8f2..0b9f7fd2ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1333,6 +1333,46 @@ importers: specifier: 0.34.6 version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) + packages/world-module-crosschain: + dependencies: + '@latticexyz/schema-type': + specifier: workspace:* + version: link:../schema-type + '@latticexyz/store': + specifier: workspace:* + version: link:../store + '@latticexyz/world': + specifier: workspace:* + version: link:../world + optimism: + specifier: https://github.com/ethereum-optimism/optimism + version: https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592 + devDependencies: + '@latticexyz/abi-ts': + specifier: workspace:* + version: link:../abi-ts + '@latticexyz/gas-report': + specifier: workspace:* + version: link:../gas-report + '@types/node': + specifier: ^18.15.11 + version: 18.19.50 + ds-test: + specifier: https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0 + version: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0 + forge-std: + specifier: https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1 + version: https://codeload.github.com/foundry-rs/forge-std/tar.gz/74cfb77e308dd188d2f58864aaf44963ae6b88b1 + solhint: + specifier: ^3.3.7 + version: 3.3.7 + tsup: + specifier: ^6.7.0 + version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) + vitest: + specifier: 0.34.6 + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) + packages/world-module-erc20: dependencies: '@latticexyz/schema-type': @@ -6895,6 +6935,7 @@ packages: eciesjs@0.3.20: resolution: {integrity: sha512-Rz5AB8v9+xmMdS/R7RzWPe/R8DP5QfyrkA6ce4umJopoB5su2H2aDy/GcgIfwhmCwxnBkqGf/PbGzmKcGtIgGA==} + deprecated: Please upgrade to v0.4+ ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -9314,6 +9355,10 @@ packages: openurl@1.1.1: resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==} + optimism@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592: + resolution: {tarball: https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592} + version: 0.0.0 + optionator@0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} engines: {node: '>= 0.8.0'} @@ -18907,7 +18952,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -18919,7 +18964,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -18940,7 +18985,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -21796,6 +21841,8 @@ snapshots: openurl@1.1.1: {} + optimism@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592: {} + optionator@0.8.3: dependencies: deep-is: 0.1.4 From 3a1e11085d501ffbc28d207d52013e5a36a60074 Mon Sep 17 00:00:00 2001 From: vdrg Date: Tue, 3 Dec 2024 13:16:23 -0300 Subject: [PATCH 02/25] refactor module --- .../src/CrosschainModule.sol | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/world-module-crosschain/src/CrosschainModule.sol b/packages/world-module-crosschain/src/CrosschainModule.sol index 3864dd4bd0..a56823e129 100644 --- a/packages/world-module-crosschain/src/CrosschainModule.sol +++ b/packages/world-module-crosschain/src/CrosschainModule.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.24; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { System } from "@latticexyz/world/src/System.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { Module } from "@latticexyz/world/src/Module.sol"; import { AccessControl } from "@latticexyz/world/src/AccessControl.sol"; @@ -19,27 +20,39 @@ contract CrosschainModule is Module { CrosschainSystem private immutable crosschainSystem = new CrosschainSystem(); function installRoot(bytes memory) public { - IBaseWorld world = IBaseWorld(_world()); - if (!ResourceIds.getExists(CrosschainRecordMetadata._tableId)) { CrosschainRecordMetadata.register(); } ResourceId crosschainSystemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, ROOT_NAMESPACE, "CrosschainSystem"); if (!ResourceIds.getExists(crosschainSystemId)) { - (bool registrationSuccess, bytes memory registrationReturnData) = address(world).delegatecall( - abi.encodeCall(world.registerSystem, (crosschainSystemId, crosschainSystem, true)) + _registerSystem(crosschainSystemId, crosschainSystem); + _registerRootFunctionSelector(crosschainSystemId, "crosschainRead(bytes32,bytes32[])"); + _registerRootFunctionSelector( + crosschainSystemId, + "crosschainWrite((address,uint256,uint256,uint256,uint256),bytes)" ); - if (!registrationSuccess) revertWithBytes(registrationReturnData); - // world.registerFunctionSelector(crosschainSystemId, "crosschainRead(bytes32,bytes32[])"); - // world.registerFunctionSelector( - // crosschainSystemId, - // "crosschainWrite((address,uint256,uint256,uint256,uint256),bytes)" - // ); } } function install(bytes memory) public pure { revert Module_NonRootInstallNotSupported(); } + + function _registerSystem(ResourceId systemId, System system) internal { + address world = _world(); + (bool success, bytes memory returnData) = world.delegatecall( + abi.encodeCall(IBaseWorld(world).registerSystem, (systemId, system, true)) + ); + if (!success) revertWithBytes(returnData); + } + + function _registerRootFunctionSelector(ResourceId systemId, string memory functionSignature) internal { + address world = _world(); + (bool success, bytes memory returnData) = world.delegatecall( + abi.encodeCall(IBaseWorld(world).registerRootFunctionSelector, (systemId, functionSignature, functionSignature)) + ); + + if (!success) revertWithBytes(returnData); + } } From 3c667859683f46fcf8ca71a031ce27464ed1ae92 Mon Sep 17 00:00:00 2001 From: vdrg Date: Tue, 3 Dec 2024 16:46:33 -0300 Subject: [PATCH 03/25] Add timestamp validation --- .../world-module-crosschain/src/CrosschainSystem.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/world-module-crosschain/src/CrosschainSystem.sol b/packages/world-module-crosschain/src/CrosschainSystem.sol index b3fa4719c5..f88bafbba9 100644 --- a/packages/world-module-crosschain/src/CrosschainSystem.sol +++ b/packages/world-module-crosschain/src/CrosschainSystem.sol @@ -17,6 +17,7 @@ contract CrosschainSystem is System { // TODO: rename errors and events error WrongWorld(); error NotCrosschainRead(); + error MoreRecentRecordExists(); event World_CrosschainRead( ResourceId indexed tableId, @@ -42,12 +43,21 @@ contract CrosschainSystem is System { (bytes32 selector, ResourceId tableId) = abi.decode(_crosschainRead[:64], (bytes32, ResourceId)); if (selector != World_CrosschainRead.selector) revert NotCrosschainRead(); + // TODO: check tableId resource type? (bytes32[] memory keyTuple, bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = abi .decode(_crosschainRead[64:], (bytes32[], bytes, EncodedLengths, bytes)); bytes32 recordId = keccak256(abi.encode(tableId, keyTuple)); + + uint256 timestamp = CrosschainRecordMetadata.getTimestamp(recordId); + + // TODO: improve validation, maybe split metadata by chainId? + if (identifier.timestamp < timestamp) { + revert MoreRecentRecordExists(); + } + CrosschainRecordMetadata.set( recordId, CrosschainRecordMetadataData({ From 6a6dba337f5eb19cfd21c3eb5d4d6bdbe0e670e6 Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 4 Dec 2024 10:09:26 -0300 Subject: [PATCH 04/25] Add op chains to explorer --- .../app/(explorer)/utils/indexerForChainId.ts | 2 +- packages/explorer/src/common.ts | 35 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts b/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts index e7f23ed1ae..c137833b6c 100644 --- a/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts +++ b/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts @@ -5,7 +5,7 @@ import { chainIdToName, supportedChains, validateChainId } from "../../../common export function indexerForChainId(chainId: number): { type: "sqlite" | "hosted"; url: string } { validateChainId(chainId); - if (chainId === anvil.id) { + if (chainId === anvil.id || chainId === 901 || chainId === 902) { return { type: "sqlite", url: "/api/sqlite-indexer" }; } diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index da18b9bc32..7cfed1bc87 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -1,9 +1,42 @@ +import { defineChain } from "viem"; import { anvil } from "viem/chains"; import { garnet, redstone, rhodolite } from "@latticexyz/common/chains"; +export const OPChainA = defineChain({ + id: 901, + name: "OPChainA", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { + http: ["http://127.0.0.1:9545"], + webSocket: ["ws://127.0.0.1:9545"], + }, + }, +}); + +export const OPChainB = defineChain({ + id: 902, + name: "OPChainB", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { + http: ["http://127.0.0.1:9546"], + webSocket: ["ws://127.0.0.1:9546"], + }, + }, +}); + export const internalNamespaces = ["world", "store", "metadata", "puppet", "erc20-puppet", "erc721-puppet"]; -export const supportedChains = { anvil, rhodolite, garnet, redstone } as const; +export const supportedChains = { anvil, rhodolite, garnet, redstone, OPChainA, OPChainB } as const; export type supportedChains = typeof supportedChains; export type supportedChainName = keyof supportedChains; From c71d8b7163327aae5dc221eaea3059029d0db7c7 Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 4 Dec 2024 16:45:12 -0300 Subject: [PATCH 05/25] Refactor crosschain module to support bridging --- .../world-module-crosschain/mud.config.ts | 36 +- .../src/CrosschainModule.sol | 24 +- .../src/CrosschainSystem.sol | 73 --- .../src/codegen/index.sol | 6 - .../tables/CrosschainRecordMetadata.sol | 422 ------------------ .../src/codegen/world/ICrosschainSystem.sol | 14 +- .../namespaces/crosschain/codegen/index.sol | 6 + .../codegen/tables/CrosschainRecord.sol | 388 ++++++++++++++++ .../src/namespaces/root/CrosschainSystem.sol | 128 ++++++ .../codegen/systems/CrosschainSystemLib.sol | 180 ++++++++ 10 files changed, 750 insertions(+), 527 deletions(-) delete mode 100644 packages/world-module-crosschain/src/CrosschainSystem.sol delete mode 100644 packages/world-module-crosschain/src/codegen/index.sol delete mode 100644 packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol create mode 100644 packages/world-module-crosschain/src/namespaces/crosschain/codegen/index.sol create mode 100644 packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol create mode 100644 packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol create mode 100644 packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol diff --git a/packages/world-module-crosschain/mud.config.ts b/packages/world-module-crosschain/mud.config.ts index 756302ae59..143de1c5bd 100644 --- a/packages/world-module-crosschain/mud.config.ts +++ b/packages/world-module-crosschain/mud.config.ts @@ -1,18 +1,32 @@ import { defineWorld } from "@latticexyz/world"; export default defineWorld({ - namespace: "root", - tables: { - CrosschainRecordMetadata: { - schema: { - // keccak256(abi.encodePacked(tableId, keyTuple)) - recordId: "bytes32", - blockNumber: "uint256", - logIndex: "uint256", - timestamp: "uint256", - chainId: "uint256", + userTypes: { + ResourceId: { + type: "bytes32", + filePath: "@latticexyz/store/src/ResourceId.sol", + }, + }, + namespaces: { + root: { + namespace: "", + }, + crosschain: { + tables: { + CrosschainRecord: { + schema: { + chainId: "uint256", + tableId: "ResourceId", + keyHash: "bytes32", + blockNumber: "uint256", + timestamp: "uint256", + }, + key: ["chainId", "tableId", "keyHash"], + }, }, - key: ["recordId"], }, }, + codegen: { + generateSystemLibraries: true, + }, }); diff --git a/packages/world-module-crosschain/src/CrosschainModule.sol b/packages/world-module-crosschain/src/CrosschainModule.sol index a56823e129..e07c967a61 100644 --- a/packages/world-module-crosschain/src/CrosschainModule.sol +++ b/packages/world-module-crosschain/src/CrosschainModule.sol @@ -1,18 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.24; -import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { IWorldRegistrationSystem } from "@latticexyz/world/src/codegen/interfaces/IWorldRegistrationSystem.sol"; import { System } from "@latticexyz/world/src/System.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { Module } from "@latticexyz/world/src/Module.sol"; -import { AccessControl } from "@latticexyz/world/src/AccessControl.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; -import { CrosschainSystem } from "./CrosschainSystem.sol"; -import { CrosschainRecordMetadata } from "./codegen/tables/CrosschainRecordMetadata.sol"; +import { CrosschainSystem } from "./namespaces/root/CrosschainSystem.sol"; +import { CrosschainRecord } from "./namespaces/crosschain/codegen/tables/CrosschainRecord.sol"; contract CrosschainModule is Module { using WorldResourceIdInstance for ResourceId; @@ -20,8 +19,8 @@ contract CrosschainModule is Module { CrosschainSystem private immutable crosschainSystem = new CrosschainSystem(); function installRoot(bytes memory) public { - if (!ResourceIds.getExists(CrosschainRecordMetadata._tableId)) { - CrosschainRecordMetadata.register(); + if (!ResourceIds.getExists(CrosschainRecord._tableId)) { + CrosschainRecord.register(); } ResourceId crosschainSystemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, ROOT_NAMESPACE, "CrosschainSystem"); @@ -40,17 +39,18 @@ contract CrosschainModule is Module { } function _registerSystem(ResourceId systemId, System system) internal { - address world = _world(); - (bool success, bytes memory returnData) = world.delegatecall( - abi.encodeCall(IBaseWorld(world).registerSystem, (systemId, system, true)) + (bool success, bytes memory returnData) = _world().delegatecall( + abi.encodeCall(IWorldRegistrationSystem.registerSystem, (systemId, system, true)) ); if (!success) revertWithBytes(returnData); } function _registerRootFunctionSelector(ResourceId systemId, string memory functionSignature) internal { - address world = _world(); - (bool success, bytes memory returnData) = world.delegatecall( - abi.encodeCall(IBaseWorld(world).registerRootFunctionSelector, (systemId, functionSignature, functionSignature)) + (bool success, bytes memory returnData) = _world().delegatecall( + abi.encodeCall( + IWorldRegistrationSystem.registerRootFunctionSelector, + (systemId, functionSignature, functionSignature) + ) ); if (!success) revertWithBytes(returnData); diff --git a/packages/world-module-crosschain/src/CrosschainSystem.sol b/packages/world-module-crosschain/src/CrosschainSystem.sol deleted file mode 100644 index f88bafbba9..0000000000 --- a/packages/world-module-crosschain/src/CrosschainSystem.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.24; - -import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -import { EncodedLengths } from "@latticexyz/store/src/EncodedLengths.sol"; -import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; - -import { System } from "@latticexyz/world/src/System.sol"; -import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; -import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; - -import { Identifier, ICrossL2Inbox } from "@contracts-bedrock/L2/interfaces/ICrossL2Inbox.sol"; -import { Predeploys } from "@contracts-bedrock/libraries/Predeploys.sol"; -import { CrosschainRecordMetadata, CrosschainRecordMetadataData } from "./codegen/tables/CrosschainRecordMetadata.sol"; - -contract CrosschainSystem is System { - // TODO: rename errors and events - error WrongWorld(); - error NotCrosschainRead(); - error MoreRecentRecordExists(); - - event World_CrosschainRead( - ResourceId indexed tableId, - bytes32[] keyTuple, - bytes staticData, - EncodedLengths encodedLengths, - bytes dynamicData - ); - - function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external { - (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( - tableId, - keyTuple - ); - - emit World_CrosschainRead(tableId, keyTuple, staticData, encodedLengths, dynamicData); - } - - function crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external { - if (identifier.origin != address(this)) revert WrongWorld(); - - ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(identifier, keccak256(_crosschainRead)); - - (bytes32 selector, ResourceId tableId) = abi.decode(_crosschainRead[:64], (bytes32, ResourceId)); - if (selector != World_CrosschainRead.selector) revert NotCrosschainRead(); - - // TODO: check tableId resource type? - - (bytes32[] memory keyTuple, bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = abi - .decode(_crosschainRead[64:], (bytes32[], bytes, EncodedLengths, bytes)); - - bytes32 recordId = keccak256(abi.encode(tableId, keyTuple)); - - uint256 timestamp = CrosschainRecordMetadata.getTimestamp(recordId); - - // TODO: improve validation, maybe split metadata by chainId? - if (identifier.timestamp < timestamp) { - revert MoreRecentRecordExists(); - } - - CrosschainRecordMetadata.set( - recordId, - CrosschainRecordMetadataData({ - blockNumber: identifier.blockNumber, - logIndex: identifier.logIndex, - timestamp: identifier.timestamp, - chainId: identifier.chainId - }) - ); - - StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData); - } -} diff --git a/packages/world-module-crosschain/src/codegen/index.sol b/packages/world-module-crosschain/src/codegen/index.sol deleted file mode 100644 index 131b21b9ac..0000000000 --- a/packages/world-module-crosschain/src/codegen/index.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.24; - -/* Autogenerated file. Do not edit manually. */ - -import { CrosschainRecordMetadata, CrosschainRecordMetadataData } from "./tables/CrosschainRecordMetadata.sol"; diff --git a/packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol b/packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol deleted file mode 100644 index 6591c6270c..0000000000 --- a/packages/world-module-crosschain/src/codegen/tables/CrosschainRecordMetadata.sol +++ /dev/null @@ -1,422 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.24; - -/* Autogenerated file. Do not edit manually. */ - -// Import store internals -import { IStore } from "@latticexyz/store/src/IStore.sol"; -import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; -import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; -import { Bytes } from "@latticexyz/store/src/Bytes.sol"; -import { Memory } from "@latticexyz/store/src/Memory.sol"; -import { SliceLib } from "@latticexyz/store/src/Slice.sol"; -import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; -import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; -import { Schema } from "@latticexyz/store/src/Schema.sol"; -import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; -import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; - -struct CrosschainRecordMetadataData { - uint256 blockNumber; - uint256 logIndex; - uint256 timestamp; - uint256 chainId; -} - -library CrosschainRecordMetadata { - // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "root", name: "CrosschainRecord", typeId: RESOURCE_TABLE });` - ResourceId constant _tableId = ResourceId.wrap(0x7462726f6f740000000000000000000043726f7373636861696e5265636f7264); - - FieldLayout constant _fieldLayout = - FieldLayout.wrap(0x0080040020202020000000000000000000000000000000000000000000000000); - - // Hex-encoded key schema of (bytes32) - Schema constant _keySchema = Schema.wrap(0x002001005f000000000000000000000000000000000000000000000000000000); - // Hex-encoded value schema of (uint256, uint256, uint256, uint256) - Schema constant _valueSchema = Schema.wrap(0x008004001f1f1f1f000000000000000000000000000000000000000000000000); - - /** - * @notice Get the table's key field names. - * @return keyNames An array of strings with the names of key fields. - */ - function getKeyNames() internal pure returns (string[] memory keyNames) { - keyNames = new string[](1); - keyNames[0] = "recordId"; - } - - /** - * @notice Get the table's value field names. - * @return fieldNames An array of strings with the names of value fields. - */ - function getFieldNames() internal pure returns (string[] memory fieldNames) { - fieldNames = new string[](4); - fieldNames[0] = "blockNumber"; - fieldNames[1] = "logIndex"; - fieldNames[2] = "timestamp"; - fieldNames[3] = "chainId"; - } - - /** - * @notice Register the table with its config. - */ - function register() internal { - StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); - } - - /** - * @notice Register the table with its config. - */ - function _register() internal { - StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); - } - - /** - * @notice Get blockNumber. - */ - function getBlockNumber(bytes32 recordId) internal view returns (uint256 blockNumber) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Get blockNumber. - */ - function _getBlockNumber(bytes32 recordId) internal view returns (uint256 blockNumber) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Set blockNumber. - */ - function setBlockNumber(bytes32 recordId, uint256 blockNumber) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); - } - - /** - * @notice Set blockNumber. - */ - function _setBlockNumber(bytes32 recordId, uint256 blockNumber) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); - } - - /** - * @notice Get logIndex. - */ - function getLogIndex(bytes32 recordId) internal view returns (uint256 logIndex) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Get logIndex. - */ - function _getLogIndex(bytes32 recordId) internal view returns (uint256 logIndex) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Set logIndex. - */ - function setLogIndex(bytes32 recordId, uint256 logIndex) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((logIndex)), _fieldLayout); - } - - /** - * @notice Set logIndex. - */ - function _setLogIndex(bytes32 recordId, uint256 logIndex) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((logIndex)), _fieldLayout); - } - - /** - * @notice Get timestamp. - */ - function getTimestamp(bytes32 recordId) internal view returns (uint256 timestamp) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Get timestamp. - */ - function _getTimestamp(bytes32 recordId) internal view returns (uint256 timestamp) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Set timestamp. - */ - function setTimestamp(bytes32 recordId, uint256 timestamp) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.setStaticField(_tableId, _keyTuple, 2, abi.encodePacked((timestamp)), _fieldLayout); - } - - /** - * @notice Set timestamp. - */ - function _setTimestamp(bytes32 recordId, uint256 timestamp) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.setStaticField(_tableId, _keyTuple, 2, abi.encodePacked((timestamp)), _fieldLayout); - } - - /** - * @notice Get chainId. - */ - function getChainId(bytes32 recordId) internal view returns (uint256 chainId) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Get chainId. - */ - function _getChainId(bytes32 recordId) internal view returns (uint256 chainId) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); - return (uint256(bytes32(_blob))); - } - - /** - * @notice Set chainId. - */ - function setChainId(bytes32 recordId, uint256 chainId) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.setStaticField(_tableId, _keyTuple, 3, abi.encodePacked((chainId)), _fieldLayout); - } - - /** - * @notice Set chainId. - */ - function _setChainId(bytes32 recordId, uint256 chainId) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.setStaticField(_tableId, _keyTuple, 3, abi.encodePacked((chainId)), _fieldLayout); - } - - /** - * @notice Get the full data. - */ - function get(bytes32 recordId) internal view returns (CrosschainRecordMetadataData memory _table) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( - _tableId, - _keyTuple, - _fieldLayout - ); - return decode(_staticData, _encodedLengths, _dynamicData); - } - - /** - * @notice Get the full data. - */ - function _get(bytes32 recordId) internal view returns (CrosschainRecordMetadataData memory _table) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( - _tableId, - _keyTuple, - _fieldLayout - ); - return decode(_staticData, _encodedLengths, _dynamicData); - } - - /** - * @notice Set the full data using individual values. - */ - function set(bytes32 recordId, uint256 blockNumber, uint256 logIndex, uint256 timestamp, uint256 chainId) internal { - bytes memory _staticData = encodeStatic(blockNumber, logIndex, timestamp, chainId); - - EncodedLengths _encodedLengths; - bytes memory _dynamicData; - - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); - } - - /** - * @notice Set the full data using individual values. - */ - function _set(bytes32 recordId, uint256 blockNumber, uint256 logIndex, uint256 timestamp, uint256 chainId) internal { - bytes memory _staticData = encodeStatic(blockNumber, logIndex, timestamp, chainId); - - EncodedLengths _encodedLengths; - bytes memory _dynamicData; - - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); - } - - /** - * @notice Set the full data using the data struct. - */ - function set(bytes32 recordId, CrosschainRecordMetadataData memory _table) internal { - bytes memory _staticData = encodeStatic(_table.blockNumber, _table.logIndex, _table.timestamp, _table.chainId); - - EncodedLengths _encodedLengths; - bytes memory _dynamicData; - - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); - } - - /** - * @notice Set the full data using the data struct. - */ - function _set(bytes32 recordId, CrosschainRecordMetadataData memory _table) internal { - bytes memory _staticData = encodeStatic(_table.blockNumber, _table.logIndex, _table.timestamp, _table.chainId); - - EncodedLengths _encodedLengths; - bytes memory _dynamicData; - - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); - } - - /** - * @notice Decode the tightly packed blob of static data using this table's field layout. - */ - function decodeStatic( - bytes memory _blob - ) internal pure returns (uint256 blockNumber, uint256 logIndex, uint256 timestamp, uint256 chainId) { - blockNumber = (uint256(Bytes.getBytes32(_blob, 0))); - - logIndex = (uint256(Bytes.getBytes32(_blob, 32))); - - timestamp = (uint256(Bytes.getBytes32(_blob, 64))); - - chainId = (uint256(Bytes.getBytes32(_blob, 96))); - } - - /** - * @notice Decode the tightly packed blobs using this table's field layout. - * @param _staticData Tightly packed static fields. - * - * - */ - function decode( - bytes memory _staticData, - EncodedLengths, - bytes memory - ) internal pure returns (CrosschainRecordMetadataData memory _table) { - (_table.blockNumber, _table.logIndex, _table.timestamp, _table.chainId) = decodeStatic(_staticData); - } - - /** - * @notice Delete all data for given keys. - */ - function deleteRecord(bytes32 recordId) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreSwitch.deleteRecord(_tableId, _keyTuple); - } - - /** - * @notice Delete all data for given keys. - */ - function _deleteRecord(bytes32 recordId) internal { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); - } - - /** - * @notice Tightly pack static (fixed length) data using this table's schema. - * @return The static data, encoded into a sequence of bytes. - */ - function encodeStatic( - uint256 blockNumber, - uint256 logIndex, - uint256 timestamp, - uint256 chainId - ) internal pure returns (bytes memory) { - return abi.encodePacked(blockNumber, logIndex, timestamp, chainId); - } - - /** - * @notice Encode all of a record's fields. - * @return The static (fixed length) data, encoded into a sequence of bytes. - * @return The lengths of the dynamic fields (packed into a single bytes32 value). - * @return The dynamic (variable length) data, encoded into a sequence of bytes. - */ - function encode( - uint256 blockNumber, - uint256 logIndex, - uint256 timestamp, - uint256 chainId - ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { - bytes memory _staticData = encodeStatic(blockNumber, logIndex, timestamp, chainId); - - EncodedLengths _encodedLengths; - bytes memory _dynamicData; - - return (_staticData, _encodedLengths, _dynamicData); - } - - /** - * @notice Encode keys as a bytes32 array using this table's field layout. - */ - function encodeKeyTuple(bytes32 recordId) internal pure returns (bytes32[] memory) { - bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = recordId; - - return _keyTuple; - } -} diff --git a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol index 97fb59effc..69e49ba1c5 100644 --- a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol +++ b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol @@ -13,9 +13,17 @@ import { Identifier } from "@contracts-bedrock/L2/interfaces/ICrossL2Inbox.sol"; */ interface ICrosschainSystem { error WrongWorld(); - error NotCrosschainRead(); + error NotCrosschainRecord(); + error MoreRecentRecordExists(); + error RecordDoesNotExist(); + error RecordAlreadyExists(); + error RecordBridgedToADifferentChain(); - function root__crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external; + function create(ResourceId tableId, bytes32[] memory keyTuple) external; - function root__crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external; + function bridge(ResourceId tableId, bytes32[] memory keyTuple, uint256 targetChain) external; + + function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external; + + function crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external; } diff --git a/packages/world-module-crosschain/src/namespaces/crosschain/codegen/index.sol b/packages/world-module-crosschain/src/namespaces/crosschain/codegen/index.sol new file mode 100644 index 0000000000..62a82d4d01 --- /dev/null +++ b/packages/world-module-crosschain/src/namespaces/crosschain/codegen/index.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { CrosschainRecord, CrosschainRecordData } from "./tables/CrosschainRecord.sol"; diff --git a/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol b/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol new file mode 100644 index 0000000000..4830cda863 --- /dev/null +++ b/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +// Import user types +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +struct CrosschainRecordData { + uint256 blockNumber; + uint256 timestamp; +} + +library CrosschainRecord { + // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "crosschain", name: "CrosschainRecord", typeId: RESOURCE_TABLE });` + ResourceId constant _tableId = ResourceId.wrap(0x746263726f7373636861696e0000000043726f7373636861696e5265636f7264); + + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0040020020200000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (uint256, bytes32, bytes32) + Schema constant _keySchema = Schema.wrap(0x006003001f5f5f00000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256, uint256) + Schema constant _valueSchema = Schema.wrap(0x004002001f1f0000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](3); + keyNames[0] = "chainId"; + keyNames[1] = "tableId"; + keyNames[2] = "keyHash"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](2); + fieldNames[0] = "blockNumber"; + fieldNames[1] = "timestamp"; + } + + /** + * @notice Register the table with its config. + */ + function register() internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register() internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get blockNumber. + */ + function getBlockNumber( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal view returns (uint256 blockNumber) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get blockNumber. + */ + function _getBlockNumber( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal view returns (uint256 blockNumber) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set blockNumber. + */ + function setBlockNumber(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); + } + + /** + * @notice Set blockNumber. + */ + function _setBlockNumber(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); + } + + /** + * @notice Get timestamp. + */ + function getTimestamp( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal view returns (uint256 timestamp) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get timestamp. + */ + function _getTimestamp( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal view returns (uint256 timestamp) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set timestamp. + */ + function setTimestamp(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 timestamp) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((timestamp)), _fieldLayout); + } + + /** + * @notice Set timestamp. + */ + function _setTimestamp(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 timestamp) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreCore.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((timestamp)), _fieldLayout); + } + + /** + * @notice Get the full data. + */ + function get( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal view returns (CrosschainRecordData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Get the full data. + */ + function _get( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal view returns (CrosschainRecordData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function set(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber, uint256 timestamp) internal { + bytes memory _staticData = encodeStatic(blockNumber, timestamp); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function _set(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber, uint256 timestamp) internal { + bytes memory _staticData = encodeStatic(blockNumber, timestamp); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Set the full data using the data struct. + */ + function set(uint256 chainId, ResourceId tableId, bytes32 keyHash, CrosschainRecordData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.blockNumber, _table.timestamp); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using the data struct. + */ + function _set(uint256 chainId, ResourceId tableId, bytes32 keyHash, CrosschainRecordData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.blockNumber, _table.timestamp); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Decode the tightly packed blob of static data using this table's field layout. + */ + function decodeStatic(bytes memory _blob) internal pure returns (uint256 blockNumber, uint256 timestamp) { + blockNumber = (uint256(Bytes.getBytes32(_blob, 0))); + + timestamp = (uint256(Bytes.getBytes32(_blob, 32))); + } + + /** + * @notice Decode the tightly packed blobs using this table's field layout. + * @param _staticData Tightly packed static fields. + * + * + */ + function decode( + bytes memory _staticData, + EncodedLengths, + bytes memory + ) internal pure returns (CrosschainRecordData memory _table) { + (_table.blockNumber, _table.timestamp) = decodeStatic(_staticData); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(uint256 chainId, ResourceId tableId, bytes32 keyHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(uint256 chainId, ResourceId tableId, bytes32 keyHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 blockNumber, uint256 timestamp) internal pure returns (bytes memory) { + return abi.encodePacked(blockNumber, timestamp); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode( + uint256 blockNumber, + uint256 timestamp + ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(blockNumber, timestamp); + + EncodedLengths _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple( + uint256 chainId, + ResourceId tableId, + bytes32 keyHash + ) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(chainId)); + _keyTuple[1] = ResourceId.unwrap(tableId); + _keyTuple[2] = keyHash; + + return _keyTuple; + } +} diff --git a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol new file mode 100644 index 0000000000..eee53b3db7 --- /dev/null +++ b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { EncodedLengths } from "@latticexyz/store/src/EncodedLengths.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; + +import { System } from "@latticexyz/world/src/System.sol"; +import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; +import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { AccessControl } from "@latticexyz/world/src/AccessControl.sol"; + +import { Identifier, ICrossL2Inbox } from "@contracts-bedrock/L2/interfaces/ICrossL2Inbox.sol"; +import { Predeploys } from "@contracts-bedrock/libraries/Predeploys.sol"; + +import { CrosschainRecord, CrosschainRecordData } from "../crosschain/codegen/tables/CrosschainRecord.sol"; + +contract CrosschainSystem is System { + using WorldResourceIdInstance for ResourceId; + + // TODO: rename errors and events + error WrongWorld(); + error NotCrosschainRecord(); + error MoreRecentRecordExists(); + error RecordDoesNotExist(); + error RecordAlreadyExists(); + error RecordBridgedToADifferentChain(); + + event World_CrosschainRecord( + ResourceId indexed tableId, + bytes32[] keyTuple, + bytes staticData, + EncodedLengths encodedLengths, + bytes dynamicData, + // 0 means broadcast so it can be consumed by any world + uint256 toChainId + ); + + function create(ResourceId tableId, bytes32[] memory keyTuple) external { + AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); + + bytes32 keyHash = keccak256(abi.encode(keyTuple)); + uint256 blockNumber = CrosschainRecord.getBlockNumber(block.chainid, tableId, keyHash); + if (blockNumber != 0) { + revert RecordAlreadyExists(); + } + + CrosschainRecordData memory data = CrosschainRecordData({ blockNumber: block.number, timestamp: block.timestamp }); + + CrosschainRecord.set(block.chainid, tableId, keyHash, data); + } + + function bridge(ResourceId tableId, bytes32[] memory keyTuple, uint256 targetChain) external { + AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); + + bytes32 keyHash = keccak256(abi.encode(keyTuple)); + uint256 blockNumber = CrosschainRecord.getBlockNumber(block.chainid, tableId, keyHash); + if (blockNumber == 0) { + revert RecordDoesNotExist(); + } + + // We don't own this record anymore + CrosschainRecord.deleteRecord(block.chainid, tableId, keyHash); + + (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( + tableId, + keyTuple + ); + + emit World_CrosschainRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, targetChain); + } + + // Anyone can call this method so other chains can consume the record data + function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external { + (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( + tableId, + keyTuple + ); + + emit World_CrosschainRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, 0); + } + + // Anyone can call this to verify a crosschain record and store it + function crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external { + if (identifier.origin != address(this)) revert WrongWorld(); + + (bytes32 selector, ResourceId tableId) = abi.decode(_crosschainRead[:64], (bytes32, ResourceId)); + if (selector != World_CrosschainRecord.selector) revert NotCrosschainRecord(); + + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(identifier, keccak256(_crosschainRead)); + + // TODO: check tableId resource type? + + ( + bytes32[] memory keyTuple, + bytes memory staticData, + EncodedLengths encodedLengths, + bytes memory dynamicData, + uint256 toChainId + ) = abi.decode(_crosschainRead[64:], (bytes32[], bytes, EncodedLengths, bytes, uint256)); + + bytes32 keyHash = keccak256(abi.encode(keyTuple)); + + // If we own the record, then writes are not allowed as it would override it + uint256 ownedTimestamp = CrosschainRecord.getTimestamp(block.chainid, tableId, keyHash); + if (ownedTimestamp > 0) { + revert RecordAlreadyExists(); + } + + // If toChainId == 0 it means it was broadcasted and not bridged + if (toChainId == 0) { + if (identifier.timestamp < CrosschainRecord.getTimestamp(0, tableId, keyHash)) { + revert MoreRecentRecordExists(); + } + } else if (toChainId != block.chainid) { + revert RecordBridgedToADifferentChain(); + } + + CrosschainRecordData memory data = CrosschainRecordData({ + blockNumber: identifier.blockNumber, + timestamp: identifier.timestamp + }); + + CrosschainRecord.set(toChainId, tableId, keyHash, data); + + StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData); + } +} diff --git a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol new file mode 100644 index 0000000000..71dcf7d671 --- /dev/null +++ b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { CrosschainSystem } from "../../CrosschainSystem.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { Identifier } from "@contracts-bedrock/L2/interfaces/ICrossL2Inbox.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; +import { IWorldCall } from "@latticexyz/world/src/IWorldKernel.sol"; +import { SystemCall } from "@latticexyz/world/src/SystemCall.sol"; +import { Systems } from "@latticexyz/world/src/codegen/tables/Systems.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; + +type CrosschainSystemType is bytes32; + +// equivalent to WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "", name: "CrosschainSystem" })) +CrosschainSystemType constant crosschainSystem = CrosschainSystemType.wrap( + 0x7379000000000000000000000000000043726f7373636861696e53797374656d +); + +struct CallWrapper { + ResourceId systemId; + address from; +} + +struct RootCallWrapper { + ResourceId systemId; + address from; +} + +/** + * @title CrosschainSystemLib + * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) + * @dev This library is automatically generated from the corresponding system contract. Do not edit manually. + */ +library CrosschainSystemLib { + error CrosschainSystemLib_CallingFromRootSystem(); + error WrongWorld(); + error NotCrosschainRecord(); + error MoreRecentRecordExists(); + error RecordDoesNotExist(); + error RecordAlreadyExists(); + error RecordBridgedToADifferentChain(); + + function create(CrosschainSystemType self, ResourceId tableId, bytes32[] memory keyTuple) internal { + return CallWrapper(self.toResourceId(), address(0)).create(tableId, keyTuple); + } + + function bridge( + CrosschainSystemType self, + ResourceId tableId, + bytes32[] memory keyTuple, + uint256 targetChain + ) internal { + return CallWrapper(self.toResourceId(), address(0)).bridge(tableId, keyTuple, targetChain); + } + + function crosschainRead(CrosschainSystemType self, ResourceId tableId, bytes32[] calldata keyTuple) internal { + return CallWrapper(self.toResourceId(), address(0)).crosschainRead(tableId, keyTuple); + } + + function crosschainWrite( + CrosschainSystemType self, + Identifier calldata identifier, + bytes calldata _crosschainRead + ) internal { + return CallWrapper(self.toResourceId(), address(0)).crosschainWrite(identifier, _crosschainRead); + } + + function create(CallWrapper memory self, ResourceId tableId, bytes32[] memory keyTuple) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert CrosschainSystemLib_CallingFromRootSystem(); + + bytes memory systemCall = abi.encodeCall(CrosschainSystem.create, (tableId, keyTuple)); + self.from == address(0) + ? _world().call(self.systemId, systemCall) + : _world().callFrom(self.from, self.systemId, systemCall); + } + + function bridge( + CallWrapper memory self, + ResourceId tableId, + bytes32[] memory keyTuple, + uint256 targetChain + ) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert CrosschainSystemLib_CallingFromRootSystem(); + + bytes memory systemCall = abi.encodeCall(CrosschainSystem.bridge, (tableId, keyTuple, targetChain)); + self.from == address(0) + ? _world().call(self.systemId, systemCall) + : _world().callFrom(self.from, self.systemId, systemCall); + } + + function crosschainRead(CallWrapper memory self, ResourceId tableId, bytes32[] calldata keyTuple) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert CrosschainSystemLib_CallingFromRootSystem(); + + bytes memory systemCall = abi.encodeCall(CrosschainSystem.crosschainRead, (tableId, keyTuple)); + self.from == address(0) + ? _world().call(self.systemId, systemCall) + : _world().callFrom(self.from, self.systemId, systemCall); + } + + function crosschainWrite( + CallWrapper memory self, + Identifier calldata identifier, + bytes calldata _crosschainRead + ) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert CrosschainSystemLib_CallingFromRootSystem(); + + bytes memory systemCall = abi.encodeCall(CrosschainSystem.crosschainWrite, (identifier, _crosschainRead)); + self.from == address(0) + ? _world().call(self.systemId, systemCall) + : _world().callFrom(self.from, self.systemId, systemCall); + } + + function create(RootCallWrapper memory self, ResourceId tableId, bytes32[] memory keyTuple) internal { + bytes memory systemCall = abi.encodeCall(CrosschainSystem.create, (tableId, keyTuple)); + SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); + } + + function bridge( + RootCallWrapper memory self, + ResourceId tableId, + bytes32[] memory keyTuple, + uint256 targetChain + ) internal { + bytes memory systemCall = abi.encodeCall(CrosschainSystem.bridge, (tableId, keyTuple, targetChain)); + SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); + } + + function crosschainRead(RootCallWrapper memory self, ResourceId tableId, bytes32[] calldata keyTuple) internal { + bytes memory systemCall = abi.encodeCall(CrosschainSystem.crosschainRead, (tableId, keyTuple)); + SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); + } + + function crosschainWrite( + RootCallWrapper memory self, + Identifier calldata identifier, + bytes calldata _crosschainRead + ) internal { + bytes memory systemCall = abi.encodeCall(CrosschainSystem.crosschainWrite, (identifier, _crosschainRead)); + SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); + } + + function callFrom(CrosschainSystemType self, address from) internal pure returns (CallWrapper memory) { + return CallWrapper(self.toResourceId(), from); + } + + function callAsRoot(CrosschainSystemType self) internal view returns (RootCallWrapper memory) { + return RootCallWrapper(self.toResourceId(), msg.sender); + } + + function callAsRootFrom(CrosschainSystemType self, address from) internal pure returns (RootCallWrapper memory) { + return RootCallWrapper(self.toResourceId(), from); + } + + function toResourceId(CrosschainSystemType self) internal pure returns (ResourceId) { + return ResourceId.wrap(CrosschainSystemType.unwrap(self)); + } + + function fromResourceId(ResourceId resourceId) internal pure returns (CrosschainSystemType) { + return CrosschainSystemType.wrap(resourceId.unwrap()); + } + + function getAddress(CrosschainSystemType self) internal view returns (address) { + return Systems.getSystem(self.toResourceId()); + } + + function _world() private view returns (IWorldCall) { + return IWorldCall(StoreSwitch.getStoreAddress()); + } +} + +using CrosschainSystemLib for CrosschainSystemType global; +using CrosschainSystemLib for CallWrapper global; +using CrosschainSystemLib for RootCallWrapper global; From be5510dbd581315e637e5206194e5391b4b279ee Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 4 Dec 2024 18:13:15 -0300 Subject: [PATCH 06/25] Refactor api and CrosschainRecord table --- .../world-module-crosschain/mud.config.ts | 4 +- .../src/codegen/world/ICrosschainSystem.sol | 2 +- .../codegen/tables/CrosschainRecord.sol | 283 ++++++++++-------- .../src/namespaces/root/CrosschainSystem.sol | 66 ++-- .../codegen/systems/CrosschainSystemLib.sol | 2 +- 5 files changed, 192 insertions(+), 165 deletions(-) diff --git a/packages/world-module-crosschain/mud.config.ts b/packages/world-module-crosschain/mud.config.ts index 143de1c5bd..a20e76757b 100644 --- a/packages/world-module-crosschain/mud.config.ts +++ b/packages/world-module-crosschain/mud.config.ts @@ -15,13 +15,13 @@ export default defineWorld({ tables: { CrosschainRecord: { schema: { - chainId: "uint256", tableId: "ResourceId", keyHash: "bytes32", blockNumber: "uint256", timestamp: "uint256", + owned: "bool", }, - key: ["chainId", "tableId", "keyHash"], + key: ["tableId", "keyHash"], }, }, }, diff --git a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol index 69e49ba1c5..f1c102d770 100644 --- a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol +++ b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol @@ -15,7 +15,7 @@ interface ICrosschainSystem { error WrongWorld(); error NotCrosschainRecord(); error MoreRecentRecordExists(); - error RecordDoesNotExist(); + error RecordNotOwned(); error RecordAlreadyExists(); error RecordBridgedToADifferentChain(); diff --git a/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol b/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol index 4830cda863..b2ff4fde96 100644 --- a/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol +++ b/packages/world-module-crosschain/src/namespaces/crosschain/codegen/tables/CrosschainRecord.sol @@ -22,6 +22,7 @@ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; struct CrosschainRecordData { uint256 blockNumber; uint256 timestamp; + bool owned; } library CrosschainRecord { @@ -29,22 +30,21 @@ library CrosschainRecord { ResourceId constant _tableId = ResourceId.wrap(0x746263726f7373636861696e0000000043726f7373636861696e5265636f7264); FieldLayout constant _fieldLayout = - FieldLayout.wrap(0x0040020020200000000000000000000000000000000000000000000000000000); + FieldLayout.wrap(0x0041030020200100000000000000000000000000000000000000000000000000); - // Hex-encoded key schema of (uint256, bytes32, bytes32) - Schema constant _keySchema = Schema.wrap(0x006003001f5f5f00000000000000000000000000000000000000000000000000); - // Hex-encoded value schema of (uint256, uint256) - Schema constant _valueSchema = Schema.wrap(0x004002001f1f0000000000000000000000000000000000000000000000000000); + // Hex-encoded key schema of (bytes32, bytes32) + Schema constant _keySchema = Schema.wrap(0x004002005f5f0000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256, uint256, bool) + Schema constant _valueSchema = Schema.wrap(0x004103001f1f6000000000000000000000000000000000000000000000000000); /** * @notice Get the table's key field names. * @return keyNames An array of strings with the names of key fields. */ function getKeyNames() internal pure returns (string[] memory keyNames) { - keyNames = new string[](3); - keyNames[0] = "chainId"; - keyNames[1] = "tableId"; - keyNames[2] = "keyHash"; + keyNames = new string[](2); + keyNames[0] = "tableId"; + keyNames[1] = "keyHash"; } /** @@ -52,9 +52,10 @@ library CrosschainRecord { * @return fieldNames An array of strings with the names of value fields. */ function getFieldNames() internal pure returns (string[] memory fieldNames) { - fieldNames = new string[](2); + fieldNames = new string[](3); fieldNames[0] = "blockNumber"; fieldNames[1] = "timestamp"; + fieldNames[2] = "owned"; } /** @@ -74,15 +75,10 @@ library CrosschainRecord { /** * @notice Get blockNumber. */ - function getBlockNumber( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal view returns (uint256 blockNumber) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function getBlockNumber(ResourceId tableId, bytes32 keyHash) internal view returns (uint256 blockNumber) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); return (uint256(bytes32(_blob))); @@ -91,15 +87,10 @@ library CrosschainRecord { /** * @notice Get blockNumber. */ - function _getBlockNumber( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal view returns (uint256 blockNumber) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function _getBlockNumber(ResourceId tableId, bytes32 keyHash) internal view returns (uint256 blockNumber) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); return (uint256(bytes32(_blob))); @@ -108,11 +99,10 @@ library CrosschainRecord { /** * @notice Set blockNumber. */ - function setBlockNumber(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber) internal { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function setBlockNumber(ResourceId tableId, bytes32 keyHash, uint256 blockNumber) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); } @@ -120,11 +110,10 @@ library CrosschainRecord { /** * @notice Set blockNumber. */ - function _setBlockNumber(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber) internal { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function _setBlockNumber(ResourceId tableId, bytes32 keyHash, uint256 blockNumber) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((blockNumber)), _fieldLayout); } @@ -132,15 +121,10 @@ library CrosschainRecord { /** * @notice Get timestamp. */ - function getTimestamp( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal view returns (uint256 timestamp) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function getTimestamp(ResourceId tableId, bytes32 keyHash) internal view returns (uint256 timestamp) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); return (uint256(bytes32(_blob))); @@ -149,15 +133,10 @@ library CrosschainRecord { /** * @notice Get timestamp. */ - function _getTimestamp( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal view returns (uint256 timestamp) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function _getTimestamp(ResourceId tableId, bytes32 keyHash) internal view returns (uint256 timestamp) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); return (uint256(bytes32(_blob))); @@ -166,11 +145,10 @@ library CrosschainRecord { /** * @notice Set timestamp. */ - function setTimestamp(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 timestamp) internal { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function setTimestamp(ResourceId tableId, bytes32 keyHash, uint256 timestamp) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreSwitch.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((timestamp)), _fieldLayout); } @@ -178,27 +156,67 @@ library CrosschainRecord { /** * @notice Set timestamp. */ - function _setTimestamp(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 timestamp) internal { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function _setTimestamp(ResourceId tableId, bytes32 keyHash, uint256 timestamp) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreCore.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked((timestamp)), _fieldLayout); } + /** + * @notice Get owned. + */ + function getOwned(ResourceId tableId, bytes32 keyHash) internal view returns (bool owned) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Get owned. + */ + function _getOwned(ResourceId tableId, bytes32 keyHash) internal view returns (bool owned) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return (_toBool(uint8(bytes1(_blob)))); + } + + /** + * @notice Set owned. + */ + function setOwned(ResourceId tableId, bytes32 keyHash, bool owned) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; + + StoreSwitch.setStaticField(_tableId, _keyTuple, 2, abi.encodePacked((owned)), _fieldLayout); + } + + /** + * @notice Set owned. + */ + function _setOwned(ResourceId tableId, bytes32 keyHash, bool owned) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; + + StoreCore.setStaticField(_tableId, _keyTuple, 2, abi.encodePacked((owned)), _fieldLayout); + } + /** * @notice Get the full data. */ - function get( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal view returns (CrosschainRecordData memory _table) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function get(ResourceId tableId, bytes32 keyHash) internal view returns (CrosschainRecordData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( _tableId, @@ -211,15 +229,10 @@ library CrosschainRecord { /** * @notice Get the full data. */ - function _get( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal view returns (CrosschainRecordData memory _table) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function _get(ResourceId tableId, bytes32 keyHash) internal view returns (CrosschainRecordData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( _tableId, @@ -232,16 +245,15 @@ library CrosschainRecord { /** * @notice Set the full data using individual values. */ - function set(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber, uint256 timestamp) internal { - bytes memory _staticData = encodeStatic(blockNumber, timestamp); + function set(ResourceId tableId, bytes32 keyHash, uint256 blockNumber, uint256 timestamp, bool owned) internal { + bytes memory _staticData = encodeStatic(blockNumber, timestamp, owned); EncodedLengths _encodedLengths; bytes memory _dynamicData; - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); } @@ -249,16 +261,15 @@ library CrosschainRecord { /** * @notice Set the full data using individual values. */ - function _set(uint256 chainId, ResourceId tableId, bytes32 keyHash, uint256 blockNumber, uint256 timestamp) internal { - bytes memory _staticData = encodeStatic(blockNumber, timestamp); + function _set(ResourceId tableId, bytes32 keyHash, uint256 blockNumber, uint256 timestamp, bool owned) internal { + bytes memory _staticData = encodeStatic(blockNumber, timestamp, owned); EncodedLengths _encodedLengths; bytes memory _dynamicData; - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } @@ -266,16 +277,15 @@ library CrosschainRecord { /** * @notice Set the full data using the data struct. */ - function set(uint256 chainId, ResourceId tableId, bytes32 keyHash, CrosschainRecordData memory _table) internal { - bytes memory _staticData = encodeStatic(_table.blockNumber, _table.timestamp); + function set(ResourceId tableId, bytes32 keyHash, CrosschainRecordData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.blockNumber, _table.timestamp, _table.owned); EncodedLengths _encodedLengths; bytes memory _dynamicData; - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); } @@ -283,16 +293,15 @@ library CrosschainRecord { /** * @notice Set the full data using the data struct. */ - function _set(uint256 chainId, ResourceId tableId, bytes32 keyHash, CrosschainRecordData memory _table) internal { - bytes memory _staticData = encodeStatic(_table.blockNumber, _table.timestamp); + function _set(ResourceId tableId, bytes32 keyHash, CrosschainRecordData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.blockNumber, _table.timestamp, _table.owned); EncodedLengths _encodedLengths; bytes memory _dynamicData; - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } @@ -300,10 +309,12 @@ library CrosschainRecord { /** * @notice Decode the tightly packed blob of static data using this table's field layout. */ - function decodeStatic(bytes memory _blob) internal pure returns (uint256 blockNumber, uint256 timestamp) { + function decodeStatic(bytes memory _blob) internal pure returns (uint256 blockNumber, uint256 timestamp, bool owned) { blockNumber = (uint256(Bytes.getBytes32(_blob, 0))); timestamp = (uint256(Bytes.getBytes32(_blob, 32))); + + owned = (_toBool(uint8(Bytes.getBytes1(_blob, 64)))); } /** @@ -317,17 +328,16 @@ library CrosschainRecord { EncodedLengths, bytes memory ) internal pure returns (CrosschainRecordData memory _table) { - (_table.blockNumber, _table.timestamp) = decodeStatic(_staticData); + (_table.blockNumber, _table.timestamp, _table.owned) = decodeStatic(_staticData); } /** * @notice Delete all data for given keys. */ - function deleteRecord(uint256 chainId, ResourceId tableId, bytes32 keyHash) internal { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function deleteRecord(ResourceId tableId, bytes32 keyHash) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreSwitch.deleteRecord(_tableId, _keyTuple); } @@ -335,11 +345,10 @@ library CrosschainRecord { /** * @notice Delete all data for given keys. */ - function _deleteRecord(uint256 chainId, ResourceId tableId, bytes32 keyHash) internal { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function _deleteRecord(ResourceId tableId, bytes32 keyHash) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); } @@ -348,8 +357,8 @@ library CrosschainRecord { * @notice Tightly pack static (fixed length) data using this table's schema. * @return The static data, encoded into a sequence of bytes. */ - function encodeStatic(uint256 blockNumber, uint256 timestamp) internal pure returns (bytes memory) { - return abi.encodePacked(blockNumber, timestamp); + function encodeStatic(uint256 blockNumber, uint256 timestamp, bool owned) internal pure returns (bytes memory) { + return abi.encodePacked(blockNumber, timestamp, owned); } /** @@ -360,9 +369,10 @@ library CrosschainRecord { */ function encode( uint256 blockNumber, - uint256 timestamp + uint256 timestamp, + bool owned ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { - bytes memory _staticData = encodeStatic(blockNumber, timestamp); + bytes memory _staticData = encodeStatic(blockNumber, timestamp, owned); EncodedLengths _encodedLengths; bytes memory _dynamicData; @@ -373,16 +383,23 @@ library CrosschainRecord { /** * @notice Encode keys as a bytes32 array using this table's field layout. */ - function encodeKeyTuple( - uint256 chainId, - ResourceId tableId, - bytes32 keyHash - ) internal pure returns (bytes32[] memory) { - bytes32[] memory _keyTuple = new bytes32[](3); - _keyTuple[0] = bytes32(uint256(chainId)); - _keyTuple[1] = ResourceId.unwrap(tableId); - _keyTuple[2] = keyHash; + function encodeKeyTuple(ResourceId tableId, bytes32 keyHash) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = ResourceId.unwrap(tableId); + _keyTuple[1] = keyHash; return _keyTuple; } } + +/** + * @notice Cast a value to a bool. + * @dev Boolean values are encoded as uint8 (1 = true, 0 = false), but Solidity doesn't allow casting between uint8 and bool. + * @param value The uint8 value to convert. + * @return result The boolean value. + */ +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +} diff --git a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol index eee53b3db7..9775434ad3 100644 --- a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol +++ b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol @@ -22,7 +22,7 @@ contract CrosschainSystem is System { error WrongWorld(); error NotCrosschainRecord(); error MoreRecentRecordExists(); - error RecordDoesNotExist(); + error RecordNotOwned(); error RecordAlreadyExists(); error RecordBridgedToADifferentChain(); @@ -32,7 +32,6 @@ contract CrosschainSystem is System { bytes staticData, EncodedLengths encodedLengths, bytes dynamicData, - // 0 means broadcast so it can be consumed by any world uint256 toChainId ); @@ -40,27 +39,37 @@ contract CrosschainSystem is System { AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); bytes32 keyHash = keccak256(abi.encode(keyTuple)); - uint256 blockNumber = CrosschainRecord.getBlockNumber(block.chainid, tableId, keyHash); - if (blockNumber != 0) { + + CrosschainRecordData memory data = CrosschainRecord.get(tableId, keyHash); + if (data.blockNumber > 0) { revert RecordAlreadyExists(); } - CrosschainRecordData memory data = CrosschainRecordData({ blockNumber: block.number, timestamp: block.timestamp }); + data.blockNumber = block.number; + data.timestamp = block.timestamp; + data.owned = true; - CrosschainRecord.set(block.chainid, tableId, keyHash, data); + CrosschainRecord.set(tableId, keyHash, data); } function bridge(ResourceId tableId, bytes32[] memory keyTuple, uint256 targetChain) external { AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); bytes32 keyHash = keccak256(abi.encode(keyTuple)); - uint256 blockNumber = CrosschainRecord.getBlockNumber(block.chainid, tableId, keyHash); - if (blockNumber == 0) { - revert RecordDoesNotExist(); + CrosschainRecordData memory data = CrosschainRecord.get(tableId, keyHash); + + // We can only bridge records we own + if (!data.owned) { + revert RecordNotOwned(); } + data.blockNumber = block.number; + data.timestamp = block.timestamp; + // We don't own this record anymore - CrosschainRecord.deleteRecord(block.chainid, tableId, keyHash); + data.owned = false; + + CrosschainRecord.set(tableId, keyHash, data); (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( tableId, @@ -70,27 +79,30 @@ contract CrosschainSystem is System { emit World_CrosschainRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, targetChain); } - // Anyone can call this method so other chains can consume the record data + /** + * @dev Anyone can call this method so other chains can consume the record data. + * Can only be called for records owned by this world + */ function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external { (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( tableId, keyTuple ); - emit World_CrosschainRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, 0); + // using toChainId == block.chainid means that other chains can't own this record + emit World_CrosschainRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, block.chainid); } // Anyone can call this to verify a crosschain record and store it function crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external { if (identifier.origin != address(this)) revert WrongWorld(); + // TODO: check tableId resource type? (bytes32 selector, ResourceId tableId) = abi.decode(_crosschainRead[:64], (bytes32, ResourceId)); if (selector != World_CrosschainRecord.selector) revert NotCrosschainRecord(); ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(identifier, keccak256(_crosschainRead)); - // TODO: check tableId resource type? - ( bytes32[] memory keyTuple, bytes memory staticData, @@ -102,26 +114,24 @@ contract CrosschainSystem is System { bytes32 keyHash = keccak256(abi.encode(keyTuple)); // If we own the record, then writes are not allowed as it would override it - uint256 ownedTimestamp = CrosschainRecord.getTimestamp(block.chainid, tableId, keyHash); - if (ownedTimestamp > 0) { + CrosschainRecordData memory data = CrosschainRecord.get(tableId, keyHash); + if (data.owned) { revert RecordAlreadyExists(); } - // If toChainId == 0 it means it was broadcasted and not bridged - if (toChainId == 0) { - if (identifier.timestamp < CrosschainRecord.getTimestamp(0, tableId, keyHash)) { - revert MoreRecentRecordExists(); - } - } else if (toChainId != block.chainid) { - revert RecordBridgedToADifferentChain(); + // If toChainId == block.chainId it means it was bridged to this world + // If toChainId != block.chainid it means it we can consume but we don't own it + if (toChainId == block.chainid) { + data.owned = true; + // TODO: should we also store and check against originating chain id? + } else if (identifier.timestamp < data.timestamp) { + revert MoreRecentRecordExists(); } - CrosschainRecordData memory data = CrosschainRecordData({ - blockNumber: identifier.blockNumber, - timestamp: identifier.timestamp - }); + data.blockNumber = identifier.blockNumber; + data.timestamp = identifier.timestamp; - CrosschainRecord.set(toChainId, tableId, keyHash, data); + CrosschainRecord.set(tableId, keyHash, data); StoreCore.setRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData); } diff --git a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol index 71dcf7d671..b9493a98db 100644 --- a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol +++ b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol @@ -39,7 +39,7 @@ library CrosschainSystemLib { error WrongWorld(); error NotCrosschainRecord(); error MoreRecentRecordExists(); - error RecordDoesNotExist(); + error RecordNotOwned(); error RecordAlreadyExists(); error RecordBridgedToADifferentChain(); From c4397dc2837ceca8cae1c1c1bedc7688f606b559 Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 4 Dec 2024 18:15:23 -0300 Subject: [PATCH 07/25] Remove copied changelog --- packages/world-module-crosschain/CHANGELOG.md | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 packages/world-module-crosschain/CHANGELOG.md diff --git a/packages/world-module-crosschain/CHANGELOG.md b/packages/world-module-crosschain/CHANGELOG.md deleted file mode 100644 index 2dbdd1d7ab..0000000000 --- a/packages/world-module-crosschain/CHANGELOG.md +++ /dev/null @@ -1,146 +0,0 @@ -# @latticexyz/world-module-metadata - -## 2.2.14 - -### Patch Changes - -- @latticexyz/schema-type@2.2.14 -- @latticexyz/store@2.2.14 -- @latticexyz/world@2.2.14 - -## 2.2.13 - -### Patch Changes - -- @latticexyz/schema-type@2.2.13 -- @latticexyz/store@2.2.13 -- @latticexyz/world@2.2.13 - -## 2.2.12 - -### Patch Changes - -- Updated dependencies [ea18f27] - - @latticexyz/schema-type@2.2.12 - - @latticexyz/store@2.2.12 - - @latticexyz/world@2.2.12 - -## 2.2.11 - -### Patch Changes - -- Updated dependencies [7ddcf64] -- Updated dependencies [13e5689] - - @latticexyz/store@2.2.11 - - @latticexyz/world@2.2.11 - - @latticexyz/schema-type@2.2.11 - -## 2.2.10 - -### Patch Changes - -- Updated dependencies [9d7fc85] - - @latticexyz/world@2.2.10 - - @latticexyz/schema-type@2.2.10 - - @latticexyz/store@2.2.10 - -## 2.2.9 - -### Patch Changes - -- @latticexyz/schema-type@2.2.9 -- @latticexyz/store@2.2.9 -- @latticexyz/world@2.2.9 - -## 2.2.8 - -### Patch Changes - -- @latticexyz/store@2.2.8 -- @latticexyz/world@2.2.8 -- @latticexyz/schema-type@2.2.8 - -## 2.2.7 - -### Patch Changes - -- Updated dependencies [a08ba5e] - - @latticexyz/store@2.2.7 - - @latticexyz/world@2.2.7 - - @latticexyz/schema-type@2.2.7 - -## 2.2.6 - -### Patch Changes - -- @latticexyz/schema-type@2.2.6 -- @latticexyz/store@2.2.6 -- @latticexyz/world@2.2.6 - -## 2.2.5 - -### Patch Changes - -- @latticexyz/schema-type@2.2.5 -- @latticexyz/store@2.2.5 -- @latticexyz/world@2.2.5 - -## 2.2.4 - -### Patch Changes - -- Updated dependencies [50010fb] -- Updated dependencies [1f24978] - - @latticexyz/schema-type@2.2.4 - - @latticexyz/store@2.2.4 - - @latticexyz/world@2.2.4 - -## 2.2.3 - -### Patch Changes - -- Updated dependencies [8546452] - - @latticexyz/world@2.2.3 - - @latticexyz/schema-type@2.2.3 - - @latticexyz/store@2.2.3 - -## 2.2.2 - -### Patch Changes - -- @latticexyz/schema-type@2.2.2 -- @latticexyz/store@2.2.2 -- @latticexyz/world@2.2.2 - -## 2.2.1 - -### Patch Changes - -- @latticexyz/store@2.2.1 -- @latticexyz/world@2.2.1 -- @latticexyz/schema-type@2.2.1 - -## 2.2.0 - -### Patch Changes - -- Updated dependencies [04c675c] -- Updated dependencies [04c675c] - - @latticexyz/store@2.2.0 - - @latticexyz/world@2.2.0 - - @latticexyz/schema-type@2.2.0 - -## 2.1.1 - -### Patch Changes - -- 6a66f57: Refactored `AccessControl` library exported from `@latticexyz/world` to be usable outside of the world package and updated module packages to use it. -- fad4e85: Added metadata module to be automatically installed during world deploy. This module allows for tagging any resource with arbitrary metadata. Internally, we'll use this to tag resources with labels onchain so that we can use labels to create a MUD project from an existing world. -- Updated dependencies [9e21e42] -- Updated dependencies [6a66f57] -- Updated dependencies [86a8104] -- Updated dependencies [542ea54] -- Updated dependencies [57bf8c3] - - @latticexyz/schema-type@2.1.1 - - @latticexyz/store@2.1.1 - - @latticexyz/world@2.1.1 From d980b1d8a3c8b97226e66bb1b59e3df533ec0417 Mon Sep 17 00:00:00 2001 From: vdrg Date: Thu, 5 Dec 2024 17:48:49 -0300 Subject: [PATCH 08/25] Add remove (wip) --- .../world-module-crosschain/mud.config.ts | 1 + .../src/codegen/world/ICrosschainSystem.sol | 2 ++ .../src/namespaces/root/CrosschainSystem.sol | 30 +++++++++++++++++++ .../codegen/systems/CrosschainSystemLib.sol | 19 ++++++++++++ 4 files changed, 52 insertions(+) diff --git a/packages/world-module-crosschain/mud.config.ts b/packages/world-module-crosschain/mud.config.ts index a20e76757b..0ca377848e 100644 --- a/packages/world-module-crosschain/mud.config.ts +++ b/packages/world-module-crosschain/mud.config.ts @@ -19,6 +19,7 @@ export default defineWorld({ keyHash: "bytes32", blockNumber: "uint256", timestamp: "uint256", + // chainId: "uint256", owned: "bool", }, key: ["tableId", "keyHash"], diff --git a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol index f1c102d770..1b28894c5a 100644 --- a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol +++ b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol @@ -21,6 +21,8 @@ interface ICrosschainSystem { function create(ResourceId tableId, bytes32[] memory keyTuple) external; + function remove(ResourceId tableId, bytes32[] memory keyTuple) external; + function bridge(ResourceId tableId, bytes32[] memory keyTuple, uint256 targetChain) external; function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external; diff --git a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol index 9775434ad3..971cb8d31e 100644 --- a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol +++ b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol @@ -35,6 +35,8 @@ contract CrosschainSystem is System { uint256 toChainId ); + event World_CrosschainRecordRemoved(ResourceId indexed tableId, bytes32[] keyTuple); + function create(ResourceId tableId, bytes32[] memory keyTuple) external { AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); @@ -52,10 +54,26 @@ contract CrosschainSystem is System { CrosschainRecord.set(tableId, keyHash, data); } + function remove(ResourceId tableId, bytes32[] memory keyTuple) external { + AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); + + bytes32 keyHash = keccak256(abi.encode(keyTuple)); + + CrosschainRecordData memory data = CrosschainRecord.get(tableId, keyHash); + if (!data.owned) { + revert RecordNotOwned(); + } + + CrosschainRecord.deleteRecord(tableId, keyHash); + + emit World_CrosschainRecordRemoved(tableId, keyTuple); + } + function bridge(ResourceId tableId, bytes32[] memory keyTuple, uint256 targetChain) external { AccessControl._requireAccess(tableId.getNamespaceId(), _msgSender()); bytes32 keyHash = keccak256(abi.encode(keyTuple)); + CrosschainRecordData memory data = CrosschainRecord.get(tableId, keyHash); // We can only bridge records we own @@ -84,6 +102,16 @@ contract CrosschainSystem is System { * Can only be called for records owned by this world */ function crosschainRead(ResourceId tableId, bytes32[] calldata keyTuple) external { + bytes32 keyHash = keccak256(abi.encode(keyTuple)); + CrosschainRecordData memory data = CrosschainRecord.get(tableId, keyHash); + + // We can only bridge records we own + if (!data.owned) { + revert RecordNotOwned(); + } + + // TODO: should we update metadata? + (bytes memory staticData, EncodedLengths encodedLengths, bytes memory dynamicData) = StoreCore.getRecord( tableId, keyTuple @@ -93,6 +121,8 @@ contract CrosschainSystem is System { emit World_CrosschainRecord(tableId, keyTuple, staticData, encodedLengths, dynamicData, block.chainid); } + // TODO: add crosschainRemove or add that logic to crosschainWrite (depending on the selector) + // Anyone can call this to verify a crosschain record and store it function crosschainWrite(Identifier calldata identifier, bytes calldata _crosschainRead) external { if (identifier.origin != address(this)) revert WrongWorld(); diff --git a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol index b9493a98db..66429d65df 100644 --- a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol +++ b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol @@ -47,6 +47,10 @@ library CrosschainSystemLib { return CallWrapper(self.toResourceId(), address(0)).create(tableId, keyTuple); } + function remove(CrosschainSystemType self, ResourceId tableId, bytes32[] memory keyTuple) internal { + return CallWrapper(self.toResourceId(), address(0)).remove(tableId, keyTuple); + } + function bridge( CrosschainSystemType self, ResourceId tableId, @@ -78,6 +82,16 @@ library CrosschainSystemLib { : _world().callFrom(self.from, self.systemId, systemCall); } + function remove(CallWrapper memory self, ResourceId tableId, bytes32[] memory keyTuple) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert CrosschainSystemLib_CallingFromRootSystem(); + + bytes memory systemCall = abi.encodeCall(CrosschainSystem.remove, (tableId, keyTuple)); + self.from == address(0) + ? _world().call(self.systemId, systemCall) + : _world().callFrom(self.from, self.systemId, systemCall); + } + function bridge( CallWrapper memory self, ResourceId tableId, @@ -122,6 +136,11 @@ library CrosschainSystemLib { SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); } + function remove(RootCallWrapper memory self, ResourceId tableId, bytes32[] memory keyTuple) internal { + bytes memory systemCall = abi.encodeCall(CrosschainSystem.remove, (tableId, keyTuple)); + SystemCall.callWithHooksOrRevert(self.from, self.systemId, systemCall, msg.value); + } + function bridge( RootCallWrapper memory self, ResourceId tableId, From 121048b7f8d4afd16e234cadaaf04640ac6bae25 Mon Sep 17 00:00:00 2001 From: vdrg Date: Thu, 5 Dec 2024 21:45:46 -0300 Subject: [PATCH 09/25] Improve crosschain record validation --- packages/world-module-crosschain/mud.config.ts | 1 - .../src/codegen/world/ICrosschainSystem.sol | 1 + .../src/namespaces/root/CrosschainSystem.sol | 12 ++++++++++-- .../root/codegen/systems/CrosschainSystemLib.sol | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/world-module-crosschain/mud.config.ts b/packages/world-module-crosschain/mud.config.ts index 0ca377848e..a20e76757b 100644 --- a/packages/world-module-crosschain/mud.config.ts +++ b/packages/world-module-crosschain/mud.config.ts @@ -19,7 +19,6 @@ export default defineWorld({ keyHash: "bytes32", blockNumber: "uint256", timestamp: "uint256", - // chainId: "uint256", owned: "bool", }, key: ["tableId", "keyHash"], diff --git a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol index 1b28894c5a..0ac1542057 100644 --- a/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol +++ b/packages/world-module-crosschain/src/codegen/world/ICrosschainSystem.sol @@ -18,6 +18,7 @@ interface ICrosschainSystem { error RecordNotOwned(); error RecordAlreadyExists(); error RecordBridgedToADifferentChain(); + error InvalidRecordTimestamp(); function create(ResourceId tableId, bytes32[] memory keyTuple) external; diff --git a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol index 971cb8d31e..741f4c2080 100644 --- a/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol +++ b/packages/world-module-crosschain/src/namespaces/root/CrosschainSystem.sol @@ -25,6 +25,7 @@ contract CrosschainSystem is System { error RecordNotOwned(); error RecordAlreadyExists(); error RecordBridgedToADifferentChain(); + error InvalidRecordTimestamp(); event World_CrosschainRecord( ResourceId indexed tableId, @@ -153,8 +154,15 @@ contract CrosschainSystem is System { // If toChainId != block.chainid it means it we can consume but we don't own it if (toChainId == block.chainid) { data.owned = true; - // TODO: should we also store and check against originating chain id? - } else if (identifier.timestamp < data.timestamp) { + } + + // If timestamp is in the future, revert + // This also creates a total ordering across chains + if (block.timestamp < identifier.timestamp) { + revert InvalidRecordTimestamp(); + } + + if (identifier.timestamp < data.timestamp) { revert MoreRecentRecordExists(); } diff --git a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol index 66429d65df..0817d2dcda 100644 --- a/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol +++ b/packages/world-module-crosschain/src/namespaces/root/codegen/systems/CrosschainSystemLib.sol @@ -42,6 +42,7 @@ library CrosschainSystemLib { error RecordNotOwned(); error RecordAlreadyExists(); error RecordBridgedToADifferentChain(); + error InvalidRecordTimestamp(); function create(CrosschainSystemType self, ResourceId tableId, bytes32[] memory keyTuple) internal { return CallWrapper(self.toResourceId(), address(0)).create(tableId, keyTuple); From 1b05ade39c40f2b851518b57d94967e660e764e2 Mon Sep 17 00:00:00 2001 From: vdrg Date: Mon, 9 Dec 2024 10:44:19 -0300 Subject: [PATCH 10/25] Fix optimism dependency --- packages/world-module-crosschain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/world-module-crosschain/package.json b/packages/world-module-crosschain/package.json index f504ee2386..f3f0777087 100644 --- a/packages/world-module-crosschain/package.json +++ b/packages/world-module-crosschain/package.json @@ -45,7 +45,7 @@ "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", "@latticexyz/world": "workspace:*", - "optimism": "https://github.com/ethereum-optimism/optimism" + "optimism": "https://github.com/ethereum-optimism/optimism#ecdb788a18022d4b78d6288c1f6ec94c152d02f8" }, "devDependencies": { "@latticexyz/abi-ts": "workspace:*", From 08179949754e0b748e52ad2a51e7a4aa2547795e Mon Sep 17 00:00:00 2001 From: vdrg Date: Mon, 9 Dec 2024 12:07:55 -0300 Subject: [PATCH 11/25] pnpm-lock --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b9f7fd2ec..2cd5736d06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1345,8 +1345,8 @@ importers: specifier: workspace:* version: link:../world optimism: - specifier: https://github.com/ethereum-optimism/optimism - version: https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592 + specifier: https://github.com/ethereum-optimism/optimism#ecdb788a18022d4b78d6288c1f6ec94c152d02f8 + version: optimism#ecdb788a18022d4b78d6288c1f6ec94c152d02f8@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/ecdb788a18022d4b78d6288c1f6ec94c152d02f8 devDependencies: '@latticexyz/abi-ts': specifier: workspace:* @@ -9355,8 +9355,8 @@ packages: openurl@1.1.1: resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==} - optimism@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592: - resolution: {tarball: https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592} + optimism#ecdb788a18022d4b78d6288c1f6ec94c152d02f8@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/ecdb788a18022d4b78d6288c1f6ec94c152d02f8: + resolution: {tarball: https://codeload.github.com/ethereum-optimism/optimism/tar.gz/ecdb788a18022d4b78d6288c1f6ec94c152d02f8} version: 0.0.0 optionator@0.8.3: @@ -21841,7 +21841,7 @@ snapshots: openurl@1.1.1: {} - optimism@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/3f43f039a9e68b777045d7e2446947acbd9b0592: {} + optimism#ecdb788a18022d4b78d6288c1f6ec94c152d02f8@https://codeload.github.com/ethereum-optimism/optimism/tar.gz/ecdb788a18022d4b78d6288c1f6ec94c152d02f8: {} optionator@0.8.3: dependencies: From 610c894c52d356365c22b25af7e05959cc2ba722 Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 11 Dec 2024 10:25:26 +0200 Subject: [PATCH 12/25] poc for custom chain configs --- examples/local-explorer/mprocs.yaml | 20 +-- packages/explorer/package.json | 2 + .../(explorer)/[chainName]/worlds/page.tsx | 15 ++- .../explorer/src/app/(explorer)/layout.tsx | 14 ++ .../app/(explorer)/utils/indexerForChainId.ts | 17 ++- packages/explorer/src/bin/explorer.ts | 51 +++++-- packages/explorer/src/common.ts | 124 ++++++++++++------ pnpm-lock.yaml | 30 ++++- 8 files changed, 200 insertions(+), 73 deletions(-) diff --git a/examples/local-explorer/mprocs.yaml b/examples/local-explorer/mprocs.yaml index c3a8d4d594..256ecacbf7 100644 --- a/examples/local-explorer/mprocs.yaml +++ b/examples/local-explorer/mprocs.yaml @@ -1,13 +1,13 @@ procs: - client: - cwd: packages/client - shell: pnpm run dev - contracts: - cwd: packages/contracts - shell: pnpm mud dev-contracts --rpc http://127.0.0.1:8545 - anvil: - cwd: packages/contracts - shell: anvil --base-fee 0 --block-time 2 + # client: + # cwd: packages/client + # shell: pnpm run dev + # contracts: + # cwd: packages/contracts + # shell: pnpm mud dev-contracts --rpc http://127.0.0.1:8545 + # anvil: + # cwd: packages/contracts + # shell: anvil --base-fee 0 --block-time 2 explorer: cwd: packages/contracts - shell: pnpm explorer --dev + shell: pnpm explorer --dev --chainId 901 --rpc http://127.0.0.1:8545 diff --git a/packages/explorer/package.json b/packages/explorer/package.json index d92e80c83d..191ae82904 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -46,6 +46,7 @@ "@latticexyz/store-sync": "workspace:*", "@latticexyz/world": "workspace:*", "@monaco-editor/react": "^4.6.0", + "@next/env": "^15.1.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-label": "^2.1.0", @@ -65,6 +66,7 @@ "lucide-react": "^0.408.0", "monaco-editor": "^0.52.0", "next": "14.2.5", + "next-runtime-env": "^3.2.2", "node-sql-parser": "^5.3.3", "nuqs": "^1.19.2", "query-string": "^9.1.0", diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx index 04fcbedb5c..169a3c6c4a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx @@ -52,9 +52,14 @@ type Props = { }; export default async function WorldsPage({ params: { chainName } }: Props) { - const worlds = await fetchWorlds(chainName); - if (worlds.length === 1) { - return redirect(`/${chainName}/worlds/${worlds[0]}`); - } - return ; + // TODO: validate chain here as well + + // const worlds = await fetchWorlds(chainName); + // if (worlds.length === 1) { + // return redirect(`/${chainName}/worlds/${worlds[0]}`); + // } + // return ; + + // return
Hello
; + return ; // TODO: adjust } diff --git a/packages/explorer/src/app/(explorer)/layout.tsx b/packages/explorer/src/app/(explorer)/layout.tsx index 8c630fc884..3aa0bd2142 100644 --- a/packages/explorer/src/app/(explorer)/layout.tsx +++ b/packages/explorer/src/app/(explorer)/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { PublicEnvScript } from "next-runtime-env"; import { Inter, JetBrains_Mono } from "next/font/google"; import { Toaster } from "sonner"; import { Theme } from "@radix-ui/themes"; @@ -30,8 +31,21 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { + // const chainId = process.env.CHAIN_ID; + // const chainName = process.env.CHAIN_NAME; + // const rpcHttpUrl = process.env.RPC_HTTP_URL; + // const rpcWsUrl = process.env.RPC_WS_URL; + + // console.log("chainId", chainId); + // console.log("chainName", chainName); + // console.log("rpcHttpUrl", rpcHttpUrl); + // console.log("rpcWsUrl", rpcWsUrl); + return ( + + +
{children}
diff --git a/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts b/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts index c137833b6c..e909e6d0cd 100644 --- a/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts +++ b/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts @@ -3,13 +3,16 @@ import { MUDChain } from "@latticexyz/common/chains"; import { chainIdToName, supportedChains, validateChainId } from "../../../common"; export function indexerForChainId(chainId: number): { type: "sqlite" | "hosted"; url: string } { - validateChainId(chainId); + // validateChainId(chainId); - if (chainId === anvil.id || chainId === 901 || chainId === 902) { - return { type: "sqlite", url: "/api/sqlite-indexer" }; - } + // TODO: || chainId === 901 || chainId === 902 + return { type: "sqlite", url: "/api/sqlite-indexer" }; - const chainName = chainIdToName[chainId]; - const chain = supportedChains[chainName] as MUDChain; - return { type: "hosted", url: new URL("/q", chain.indexerUrl).toString() }; + // if (chainId === anvil.id) { + // return { type: "sqlite", url: "/api/sqlite-indexer" }; + // } + + // const chainName = chainIdToName[chainId]; + // const chain = supportedChains[chainName] as MUDChain; + // return { type: "hosted", url: new URL("/q", chain.indexerUrl).toString() }; } diff --git a/packages/explorer/src/bin/explorer.ts b/packages/explorer/src/bin/explorer.ts index 218018aaba..6bac33efc1 100755 --- a/packages/explorer/src/bin/explorer.ts +++ b/packages/explorer/src/bin/explorer.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { rm } from "fs/promises"; +import { rm, writeFile } from "fs/promises"; import path from "path"; import process from "process"; import { fileURLToPath } from "url"; @@ -31,6 +31,24 @@ const argv = yargs(process.argv.slice(2)) type: "number", default: process.env.CHAIN_ID || 31337, }, + chainName: { + alias: "n", + description: "Chain name", + type: "string", + default: process.env.CHAIN_NAME || "custom", + }, + rpcHttpUrl: { + alias: ["rpc", "rpcUrl", "rpcHttpUrl"], + description: "RPC HTTP URL", + type: "string", + default: process.env.RPC_HTTP_URL || "http://127.0.0.1:8545", + }, + rpcWsUrl: { + alias: ["rpcWs", "rpcWsUrl"], + description: "RPC WebSocket URL", + type: "string", + default: process.env.RPC_WS_URL || "ws://127.0.0.1:8545", + }, indexerDatabase: { alias: "i", description: "Path to the indexer database", @@ -45,12 +63,12 @@ const argv = yargs(process.argv.slice(2)) }, }) .check((argv) => { - validateChainId(Number(argv.chainId)); + // validateChainId(Number(argv.chainId)); // TODO: skip validation if RPC provided return true; }) .parseSync(); -const { port, hostname, chainId, indexerDatabase, dev } = argv; +const { port, hostname, chainId, chainName, rpcHttpUrl, rpcWsUrl, indexerDatabase, dev } = argv; const indexerDatabasePath = path.join(packageRoot, indexerDatabase); let explorerProcess: ChildProcess; @@ -60,7 +78,20 @@ async function startExplorer() { const env = { ...process.env, CHAIN_ID: chainId.toString(), + CHAIN_NAME: chainName, + RPC_HTTP_URL: rpcHttpUrl, + RPC_WS_URL: rpcWsUrl, INDEXER_DATABASE: indexerDatabasePath, + + NEXT_PUBLIC_CHAIN_ID: chainId.toString(), + NEXT_PUBLIC_CHAIN_NAME: chainName, + NEXT_PUBLIC_RPC_HTTP_URL: rpcHttpUrl, + NEXT_PUBLIC_RPC_WS_URL: rpcWsUrl, + + // NEXT_PUBLIC_CHAIN_ID: chainId.toString(), + // NEXT_PUBLIC_CHAIN_NAME: chainName, + // NEXT_PUBLIC_RPC_HTTP_URL: rpcHttpUrl, + // NEXT_PUBLIC_RPC_WS_URL: rpcWsUrl, }; if (dev) { @@ -87,20 +118,22 @@ async function startExplorer() { } async function startStoreIndexer() { - if (chainId !== anvil.id) { - console.log("Skipping SQLite indexer for non-anvil chain ID", chainId); - return; - } + // TODO: make conditional skipping + // if (chainId !== anvil.id) { + // console.log("Skipping SQLite indexer for non-anvil chain ID", chainId); + // return; + // } await rm(indexerDatabasePath, { recursive: true, force: true }); - console.log("Running SQLite indexer for anvil..."); + console.log("Running SQLite indexer for anvil..."); // TODO: running for custom chain indexerProcess = spawn("sh", ["node_modules/.bin/sqlite-indexer"], { cwd: packageRoot, stdio: "inherit", env: { DEBUG: "mud:*", - RPC_HTTP_URL: "http://127.0.0.1:8545", + RPC_HTTP_URL: rpcHttpUrl, + RPC_WS_URL: rpcWsUrl, FOLLOW_BLOCK_TAG: "latest", SQLITE_FILENAME: indexerDatabase, ...process.env, diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index 7cfed1bc87..8418e2ffd4 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -1,42 +1,86 @@ +// import { defineChain } from "viem"; +import { env } from "next-runtime-env"; import { defineChain } from "viem"; import { anvil } from "viem/chains"; import { garnet, redstone, rhodolite } from "@latticexyz/common/chains"; -export const OPChainA = defineChain({ - id: 901, - name: "OPChainA", - nativeCurrency: { - decimals: 18, - name: "Ether", - symbol: "ETH", - }, - rpcUrls: { - default: { - http: ["http://127.0.0.1:9545"], - webSocket: ["ws://127.0.0.1:9545"], +console.log("env", env("NEXT_PUBLIC_CHAIN_NAME")); + +// export const OPChainA = defineChain({ +// id: 901, +// name: "OPChainA", +// nativeCurrency: { +// decimals: 18, +// name: "Ether", +// symbol: "ETH", +// }, +// rpcUrls: { +// default: { +// http: ["http://127.0.0.1:9545"], +// webSocket: ["ws://127.0.0.1:9545"], +// }, +// }, +// }); + +// export const OPChainB = defineChain({ +// id: 902, +// name: "OPChainB", +// nativeCurrency: { +// decimals: 18, +// name: "Ether", +// symbol: "ETH", +// }, +// rpcUrls: { +// default: { +// http: ["http://127.0.0.1:9546"], +// webSocket: ["ws://127.0.0.1:9546"], +// }, +// }, +// }); + +// export const customDefinedChain = { +// id: pro +// } + +// console.log(process.env, process.env.NEXT_PUBLIC_ANALYTICS_ID); + +export const constructCustomDefinedChain = () => { + // console.log("process.env", process.env.CHAIN_ID, process.env.CHAIN_NAME, process.env.NEXT_PUBLIC_RPC_HTTP_URL); + + const chainId = env("NEXT_PUBLIC_CHAIN_ID"); + const chainName = env("NEXT_PUBLIC_CHAIN_NAME"); + const rpcHttpUrl = env("NEXT_PUBLIC_RPC_HTTP_URL"); + + console.log("chainId", chainId); + console.log("chainName", chainName); + console.log("rpcHttpUrl", rpcHttpUrl); + + if (!chainId || !chainName || !rpcHttpUrl) { + throw new Error("Missing environment variables"); + } + + return defineChain({ + id: Number(chainId), + name: chainName, + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", }, - }, -}); - -export const OPChainB = defineChain({ - id: 902, - name: "OPChainB", - nativeCurrency: { - decimals: 18, - name: "Ether", - symbol: "ETH", - }, - rpcUrls: { - default: { - http: ["http://127.0.0.1:9546"], - webSocket: ["ws://127.0.0.1:9546"], + rpcUrls: { + default: { + http: [rpcHttpUrl], + // webSocket: [process.env.RPC_WS_URL], // TODO: add later + }, }, - }, -}); + }); +}; + +const customDefinedChain = constructCustomDefinedChain(); export const internalNamespaces = ["world", "store", "metadata", "puppet", "erc20-puppet", "erc721-puppet"]; -export const supportedChains = { anvil, rhodolite, garnet, redstone, OPChainA, OPChainB } as const; +export const supportedChains = { anvil, rhodolite, garnet, redstone, customDefinedChain } as const; export type supportedChains = typeof supportedChains; export type supportedChainName = keyof supportedChains; @@ -47,21 +91,25 @@ export const chainIdToName = Object.fromEntries( ) as Record; export function isValidChainId(chainId: unknown): chainId is supportedChainId { - return typeof chainId === "number" && chainId in chainIdToName; + // return typeof chainId === "number" && chainId in chainIdToName; + return true; } export function isValidChainName(name: unknown): name is supportedChainName { - return typeof name === "string" && name in supportedChains; + // return typeof name === "string" && name in supportedChains; + return true; } export function validateChainId(chainId: unknown): asserts chainId is supportedChainId { - if (!isValidChainId(chainId)) { - throw new Error(`Invalid chain ID. Supported chains are: ${Object.keys(chainIdToName).join(", ")}.`); - } + // TODO: make conditional skipping + // if (!isValidChainId(chainId)) { + // throw new Error(`Invalid chain ID. Supported chains are: ${Object.keys(chainIdToName).join(", ")}.`); + // } } export function validateChainName(name: unknown): asserts name is supportedChainName { - if (!isValidChainName(name)) { - throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChains).join(", ")}.`); - } + // TODO: make conditional skipping + // if (!isValidChainName(name)) { + // throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChains).join(", ")}.`); + // } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cd5736d06..ee71d71ae9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -498,6 +498,9 @@ importers: '@monaco-editor/react': specifier: ^4.6.0 version: 4.6.0(monaco-editor@0.52.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@next/env': + specifier: ^15.1.0 + version: 15.1.0 '@radix-ui/react-checkbox': specifier: ^1.1.1 version: 1.1.1(@types/react-dom@18.2.7)(@types/react@18.2.22)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -555,6 +558,9 @@ importers: next: specifier: 14.2.5 version: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next-runtime-env: + specifier: ^3.2.2 + version: 3.2.2(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) node-sql-parser: specifier: ^5.3.3 version: 5.3.3 @@ -833,10 +839,10 @@ importers: version: 8.3.4 jest: specifier: ^29.3.1 - version: 29.5.0(@types/node@18.19.50) + version: 29.5.0(@types/node@20.12.12) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.19.50))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) @@ -1252,10 +1258,10 @@ importers: version: 27.4.1 jest: specifier: ^29.3.1 - version: 29.5.0(@types/node@20.12.12) + version: 29.5.0(@types/node@18.19.50) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.25.2))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.19.50))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) @@ -3716,6 +3722,9 @@ packages: '@next/env@14.2.5': resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + '@next/env@15.1.0': + resolution: {integrity: sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w==} + '@next/eslint-plugin-next@14.2.3': resolution: {integrity: sha512-L3oDricIIjgj1AVnRdRor21gI7mShlSwU/1ZGHmqM3LzHhXXhdkrfeNY5zif25Bi5Dd7fiJHsbhoZCHfXYvlAw==} @@ -9089,6 +9098,12 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next-runtime-env@3.2.2: + resolution: {integrity: sha512-S5S6NxIf3XeaVc9fLBN2L5Jzu+6dLYCXeOaPQa1RzKRYlG2BBayxXOj6A4VsciocyNkJMazW1VAibtbb1/ZjAw==} + peerDependencies: + next: ^14 + react: ^18 + next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} @@ -14571,6 +14586,8 @@ snapshots: '@next/env@14.2.5': {} + '@next/env@15.1.0': {} + '@next/eslint-plugin-next@14.2.3': dependencies: glob: 10.3.10 @@ -21571,6 +21588,11 @@ snapshots: neo-async@2.6.2: {} + next-runtime-env@3.2.2(next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0): + dependencies: + next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + next-tick@1.1.0: {} next@14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): From 54c836dca82147b243d32e44a0f456b2b22b7e79 Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 11 Dec 2024 12:35:58 +0200 Subject: [PATCH 13/25] add custom chain name --- examples/local-explorer/mprocs.yaml | 2 +- packages/explorer/src/bin/explorer.ts | 1 - packages/explorer/src/common.ts | 8 +++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/local-explorer/mprocs.yaml b/examples/local-explorer/mprocs.yaml index 256ecacbf7..2318f1151c 100644 --- a/examples/local-explorer/mprocs.yaml +++ b/examples/local-explorer/mprocs.yaml @@ -10,4 +10,4 @@ procs: # shell: anvil --base-fee 0 --block-time 2 explorer: cwd: packages/contracts - shell: pnpm explorer --dev --chainId 901 --rpc http://127.0.0.1:8545 + shell: pnpm explorer --dev --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 diff --git a/packages/explorer/src/bin/explorer.ts b/packages/explorer/src/bin/explorer.ts index 6bac33efc1..6459e39c0b 100755 --- a/packages/explorer/src/bin/explorer.ts +++ b/packages/explorer/src/bin/explorer.ts @@ -133,7 +133,6 @@ async function startStoreIndexer() { env: { DEBUG: "mud:*", RPC_HTTP_URL: rpcHttpUrl, - RPC_WS_URL: rpcWsUrl, FOLLOW_BLOCK_TAG: "latest", SQLITE_FILENAME: indexerDatabase, ...process.env, diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index 8418e2ffd4..cc7e5e4311 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -80,7 +80,13 @@ const customDefinedChain = constructCustomDefinedChain(); export const internalNamespaces = ["world", "store", "metadata", "puppet", "erc20-puppet", "erc721-puppet"]; -export const supportedChains = { anvil, rhodolite, garnet, redstone, customDefinedChain } as const; +export const supportedChains = { + anvil, + rhodolite, + garnet, + redstone, + [customDefinedChain.name]: customDefinedChain, +} as const; export type supportedChains = typeof supportedChains; export type supportedChainName = keyof supportedChains; From 77d13fa9a5a5622359dd3e2e75fe5ac7ac7683f7 Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 11 Dec 2024 19:29:21 +0200 Subject: [PATCH 14/25] type custom chain wip --- .../(explorer)/[chainName]/worlds/page.tsx | 16 ++- .../src/app/(explorer)/api/world-abi/route.ts | 4 +- .../src/app/(explorer)/hooks/useChain.ts | 5 +- .../explorer/src/app/(explorer)/layout.tsx | 10 -- .../(explorer)/queries/useWorldAbiQuery.ts | 4 +- .../utils/blockExplorerTransactionUrl.ts | 2 +- .../app/(explorer)/utils/indexerForChainId.ts | 20 ++-- packages/explorer/src/bin/explorer.ts | 15 ++- packages/explorer/src/common.ts | 101 ++++++------------ 9 files changed, 62 insertions(+), 115 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx index 169a3c6c4a..37726eb02a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx @@ -52,14 +52,10 @@ type Props = { }; export default async function WorldsPage({ params: { chainName } }: Props) { - // TODO: validate chain here as well - - // const worlds = await fetchWorlds(chainName); - // if (worlds.length === 1) { - // return redirect(`/${chainName}/worlds/${worlds[0]}`); - // } - // return ; - - // return
Hello
; - return ; // TODO: adjust + // TODO: make sure it works with custom chain + const worlds = await fetchWorlds(chainName); + if (worlds.length === 1) { + return redirect(`/${chainName}/worlds/${worlds[0]}`); + } + return ; } diff --git a/packages/explorer/src/app/(explorer)/api/world-abi/route.ts b/packages/explorer/src/app/(explorer)/api/world-abi/route.ts index 81458f6d74..c8ff66429a 100644 --- a/packages/explorer/src/app/(explorer)/api/world-abi/route.ts +++ b/packages/explorer/src/app/(explorer)/api/world-abi/route.ts @@ -10,7 +10,7 @@ import { chainIdToName, supportedChainId, supportedChains, validateChainId } fro export const dynamic = "force-dynamic"; async function getClient(chainId: supportedChainId) { - const chain = supportedChains[chainIdToName[chainId]]; + const chain = supportedChains[chainIdToName[chainId]]; // TODO: fix types const client = createClient({ chain, transport: http(), @@ -20,7 +20,7 @@ async function getClient(chainId: supportedChainId) { } function getIndexerUrl(chainId: supportedChainId) { - const chain = supportedChains[chainIdToName[chainId]]; + const chain = supportedChains[chainIdToName[chainId]!]; // TODO: fix types return "indexerUrl" in chain ? chain.indexerUrl : undefined; } diff --git a/packages/explorer/src/app/(explorer)/hooks/useChain.ts b/packages/explorer/src/app/(explorer)/hooks/useChain.ts index e6a1b0a54d..1ce9d96838 100644 --- a/packages/explorer/src/app/(explorer)/hooks/useChain.ts +++ b/packages/explorer/src/app/(explorer)/hooks/useChain.ts @@ -1,11 +1,10 @@ import { useParams } from "next/navigation"; import { Chain } from "viem"; -import { supportedChains, validateChainName } from "../../../common"; +import { getChain, validateChainName } from "../../../common"; export function useChain(): Chain { const { chainName } = useParams(); validateChainName(chainName); - const chain = supportedChains[chainName]; - return chain; + return getChain(chainName); } diff --git a/packages/explorer/src/app/(explorer)/layout.tsx b/packages/explorer/src/app/(explorer)/layout.tsx index 3aa0bd2142..935b754613 100644 --- a/packages/explorer/src/app/(explorer)/layout.tsx +++ b/packages/explorer/src/app/(explorer)/layout.tsx @@ -31,16 +31,6 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - // const chainId = process.env.CHAIN_ID; - // const chainName = process.env.CHAIN_NAME; - // const rpcHttpUrl = process.env.RPC_HTTP_URL; - // const rpcWsUrl = process.env.RPC_WS_URL; - - // console.log("chainId", chainId); - // console.log("chainName", chainName); - // console.log("rpcHttpUrl", rpcHttpUrl); - // console.log("rpcWsUrl", rpcWsUrl); - return ( diff --git a/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts b/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts index 083d820412..96a2daace9 100644 --- a/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts +++ b/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts @@ -1,7 +1,7 @@ import { useParams } from "next/navigation"; import { AbiFunction, Hex } from "viem"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; -import { supportedChains, validateChainName } from "../../../common"; +import { getChain, validateChainName } from "../../../common"; type AbiQueryResult = { abi: AbiFunction[]; @@ -11,7 +11,7 @@ type AbiQueryResult = { export function useWorldAbiQuery(): UseQueryResult { const { chainName, worldAddress } = useParams(); validateChainName(chainName); - const { id: chainId } = supportedChains[chainName]; + const { id: chainId } = getChain(chainName); return useQuery({ queryKey: ["worldAbi", chainName, worldAddress], diff --git a/packages/explorer/src/app/(explorer)/utils/blockExplorerTransactionUrl.ts b/packages/explorer/src/app/(explorer)/utils/blockExplorerTransactionUrl.ts index aa8a43a0b0..dd31407000 100644 --- a/packages/explorer/src/app/(explorer)/utils/blockExplorerTransactionUrl.ts +++ b/packages/explorer/src/app/(explorer)/utils/blockExplorerTransactionUrl.ts @@ -12,7 +12,7 @@ export function blockExplorerTransactionUrl({ validateChainId(chainId); const chainName = chainIdToName[chainId]; - const chain = supportedChains[chainName]; + const chain = supportedChains[chainName!]; // TODO: fix types const explorerUrl = "blockExplorers" in chain && chain.blockExplorers?.default.url; if (!explorerUrl) return undefined; return `${explorerUrl}/tx/${hash}`; diff --git a/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts b/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts index e909e6d0cd..a7c76b9361 100644 --- a/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts +++ b/packages/explorer/src/app/(explorer)/utils/indexerForChainId.ts @@ -3,16 +3,18 @@ import { MUDChain } from "@latticexyz/common/chains"; import { chainIdToName, supportedChains, validateChainId } from "../../../common"; export function indexerForChainId(chainId: number): { type: "sqlite" | "hosted"; url: string } { - // validateChainId(chainId); + validateChainId(chainId); - // TODO: || chainId === 901 || chainId === 902 - return { type: "sqlite", url: "/api/sqlite-indexer" }; + if (chainId === anvil.id) { + return { type: "sqlite", url: "/api/sqlite-indexer" }; + } - // if (chainId === anvil.id) { - // return { type: "sqlite", url: "/api/sqlite-indexer" }; - // } + // TODO: improve logic + const chainName = chainIdToName[chainId]; + if (!chainName) { + return { type: "sqlite", url: "/api/sqlite-indexer" }; + } - // const chainName = chainIdToName[chainId]; - // const chain = supportedChains[chainName] as MUDChain; - // return { type: "hosted", url: new URL("/q", chain.indexerUrl).toString() }; + const chain = supportedChains[chainName] as MUDChain; + return { type: "hosted", url: new URL("/q", chain.indexerUrl).toString() }; } diff --git a/packages/explorer/src/bin/explorer.ts b/packages/explorer/src/bin/explorer.ts index 6459e39c0b..6ba11ccc58 100755 --- a/packages/explorer/src/bin/explorer.ts +++ b/packages/explorer/src/bin/explorer.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node -import { rm, writeFile } from "fs/promises"; +import { rm } from "fs/promises"; import path from "path"; import process from "process"; import { fileURLToPath } from "url"; -import { anvil } from "viem/chains"; +// import { anvil } from "viem/chains"; import yargs from "yargs"; import { ChildProcess, spawn } from "child_process"; import { validateChainId } from "../common"; @@ -63,7 +63,11 @@ const argv = yargs(process.argv.slice(2)) }, }) .check((argv) => { - // validateChainId(Number(argv.chainId)); // TODO: skip validation if RPC provided + // skip chainId validation if custom configs are provided + if (argv.rpcHttpUrl || argv.rpcWsUrl) { + return true; + } + validateChainId(Number(argv.chainId)); return true; }) .parseSync(); @@ -87,11 +91,6 @@ async function startExplorer() { NEXT_PUBLIC_CHAIN_NAME: chainName, NEXT_PUBLIC_RPC_HTTP_URL: rpcHttpUrl, NEXT_PUBLIC_RPC_WS_URL: rpcWsUrl, - - // NEXT_PUBLIC_CHAIN_ID: chainId.toString(), - // NEXT_PUBLIC_CHAIN_NAME: chainName, - // NEXT_PUBLIC_RPC_HTTP_URL: rpcHttpUrl, - // NEXT_PUBLIC_RPC_WS_URL: rpcWsUrl, }; if (dev) { diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index cc7e5e4311..080398a769 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -1,62 +1,18 @@ -// import { defineChain } from "viem"; import { env } from "next-runtime-env"; -import { defineChain } from "viem"; +import { Chain, defineChain } from "viem"; import { anvil } from "viem/chains"; import { garnet, redstone, rhodolite } from "@latticexyz/common/chains"; -console.log("env", env("NEXT_PUBLIC_CHAIN_NAME")); - -// export const OPChainA = defineChain({ -// id: 901, -// name: "OPChainA", -// nativeCurrency: { -// decimals: 18, -// name: "Ether", -// symbol: "ETH", -// }, -// rpcUrls: { -// default: { -// http: ["http://127.0.0.1:9545"], -// webSocket: ["ws://127.0.0.1:9545"], -// }, -// }, -// }); - -// export const OPChainB = defineChain({ -// id: 902, -// name: "OPChainB", -// nativeCurrency: { -// decimals: 18, -// name: "Ether", -// symbol: "ETH", -// }, -// rpcUrls: { -// default: { -// http: ["http://127.0.0.1:9546"], -// webSocket: ["ws://127.0.0.1:9546"], -// }, -// }, -// }); - -// export const customDefinedChain = { -// id: pro -// } - -// console.log(process.env, process.env.NEXT_PUBLIC_ANALYTICS_ID); - -export const constructCustomDefinedChain = () => { - // console.log("process.env", process.env.CHAIN_ID, process.env.CHAIN_NAME, process.env.NEXT_PUBLIC_RPC_HTTP_URL); +export const internalNamespaces = ["world", "store", "metadata", "puppet", "erc20-puppet", "erc721-puppet"]; +export const constructCustomChain = (): Chain | null => { const chainId = env("NEXT_PUBLIC_CHAIN_ID"); const chainName = env("NEXT_PUBLIC_CHAIN_NAME"); const rpcHttpUrl = env("NEXT_PUBLIC_RPC_HTTP_URL"); - - console.log("chainId", chainId); - console.log("chainName", chainName); - console.log("rpcHttpUrl", rpcHttpUrl); + const rpcWsUrl = env("NEXT_PUBLIC_RPC_WS_URL"); if (!chainId || !chainName || !rpcHttpUrl) { - throw new Error("Missing environment variables"); + return null; } return defineChain({ @@ -70,52 +26,57 @@ export const constructCustomDefinedChain = () => { rpcUrls: { default: { http: [rpcHttpUrl], - // webSocket: [process.env.RPC_WS_URL], // TODO: add later + ...(rpcWsUrl ? { webSocket: [rpcWsUrl] } : {}), }, }, }); }; -const customDefinedChain = constructCustomDefinedChain(); - -export const internalNamespaces = ["world", "store", "metadata", "puppet", "erc20-puppet", "erc721-puppet"]; +export const customChain = constructCustomChain(); export const supportedChains = { anvil, rhodolite, garnet, redstone, - [customDefinedChain.name]: customDefinedChain, } as const; -export type supportedChains = typeof supportedChains; +export type supportedChains = typeof supportedChains; export type supportedChainName = keyof supportedChains; export type supportedChainId = supportedChains[supportedChainName]["id"]; +export const getChain = (chainName: supportedChainName | string) => { + if (chainName === customChain?.name) { + return customChain; + } else if (chainName in supportedChains) { + // @ts-expect-error ignore for now TODO: fix types + return supportedChains[chainName]; + } + return null; +}; + export const chainIdToName = Object.fromEntries( Object.entries(supportedChains).map(([chainName, chain]) => [chain.id, chainName]), ) as Record; -export function isValidChainId(chainId: unknown): chainId is supportedChainId { - // return typeof chainId === "number" && chainId in chainIdToName; - return true; +export function isValidChainId(chainId: unknown): chainId is supportedChainId | number { + return typeof chainId === "number" && (chainId in chainIdToName || customChain?.id === chainId); } -export function isValidChainName(name: unknown): name is supportedChainName { - // return typeof name === "string" && name in supportedChains; - return true; +export function isValidChainName(name: unknown): name is supportedChainName | string { + return typeof name === "string" && (name in supportedChains || customChain?.name === name); } export function validateChainId(chainId: unknown): asserts chainId is supportedChainId { - // TODO: make conditional skipping - // if (!isValidChainId(chainId)) { - // throw new Error(`Invalid chain ID. Supported chains are: ${Object.keys(chainIdToName).join(", ")}.`); - // } + if (!isValidChainId(chainId)) { + const supportedIds = Object.keys(chainIdToName); + throw new Error(`Invalid chain ID. Supported chains are: ${supportedIds.join(", ")}.`); + } } -export function validateChainName(name: unknown): asserts name is supportedChainName { - // TODO: make conditional skipping - // if (!isValidChainName(name)) { - // throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChains).join(", ")}.`); - // } +export function validateChainName(name: unknown): asserts name is supportedChainName | string { + if (!isValidChainName(name)) { + const supportedNames = Object.keys(supportedChains); + throw new Error(`Invalid chain name. Supported chains are: ${supportedNames.join(", ")}.`); + } } From 7cfc3e264d07eb0546472e13ebc048146c6a5c3b Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 11 Dec 2024 19:51:22 +0200 Subject: [PATCH 15/25] get world abi client --- .../explorer/src/app/(explorer)/api/world-abi/route.ts | 6 +++--- packages/explorer/src/bin/explorer.ts | 2 +- packages/explorer/src/common.ts | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/api/world-abi/route.ts b/packages/explorer/src/app/(explorer)/api/world-abi/route.ts index c8ff66429a..94bc556207 100644 --- a/packages/explorer/src/app/(explorer)/api/world-abi/route.ts +++ b/packages/explorer/src/app/(explorer)/api/world-abi/route.ts @@ -5,12 +5,12 @@ import { fetchBlockLogs } from "@latticexyz/block-logs-stream"; import { helloStoreEvent } from "@latticexyz/store"; import { getWorldAbi } from "@latticexyz/store-sync/world"; import { helloWorldEvent } from "@latticexyz/world"; -import { chainIdToName, supportedChainId, supportedChains, validateChainId } from "../../../../common"; +import { getChainById, supportedChainId, validateChainId } from "../../../../common"; export const dynamic = "force-dynamic"; async function getClient(chainId: supportedChainId) { - const chain = supportedChains[chainIdToName[chainId]]; // TODO: fix types + const chain = getChainById(chainId); const client = createClient({ chain, transport: http(), @@ -20,7 +20,7 @@ async function getClient(chainId: supportedChainId) { } function getIndexerUrl(chainId: supportedChainId) { - const chain = supportedChains[chainIdToName[chainId]!]; // TODO: fix types + const chain = getChainById(chainId); return "indexerUrl" in chain ? chain.indexerUrl : undefined; } diff --git a/packages/explorer/src/bin/explorer.ts b/packages/explorer/src/bin/explorer.ts index 6ba11ccc58..99a464528a 100755 --- a/packages/explorer/src/bin/explorer.ts +++ b/packages/explorer/src/bin/explorer.ts @@ -125,7 +125,7 @@ async function startStoreIndexer() { await rm(indexerDatabasePath, { recursive: true, force: true }); - console.log("Running SQLite indexer for anvil..."); // TODO: running for custom chain + console.log("Running SQLite indexer..."); // TODO: running for custom chain indexerProcess = spawn("sh", ["node_modules/.bin/sqlite-indexer"], { cwd: packageRoot, stdio: "inherit", diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index 080398a769..8a45be4d4a 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -55,6 +55,16 @@ export const getChain = (chainName: supportedChainName | string) => { return null; }; +export const getChainById = (chainId: supportedChainId | number) => { + if (chainId === customChain?.id) { + return customChain; + } else if (chainId in supportedChains) { + // @ts-expect-error ignore for now TODO: fix types + return supportedChains[chainId]; + } + return null; +}; + export const chainIdToName = Object.fromEntries( Object.entries(supportedChains).map(([chainName, chain]) => [chain.id, chainName]), ) as Record; From 0bf4ad7ae9d5969d2e3f2e2aff0d4646a1e8590b Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 11 Dec 2024 20:34:03 +0200 Subject: [PATCH 16/25] configure local explorer example --- examples/local-explorer/mprocs.yaml | 7 +++++-- packages/explorer/src/bin/explorer.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/examples/local-explorer/mprocs.yaml b/examples/local-explorer/mprocs.yaml index 2318f1151c..84d7bdabf9 100644 --- a/examples/local-explorer/mprocs.yaml +++ b/examples/local-explorer/mprocs.yaml @@ -8,6 +8,9 @@ procs: # anvil: # cwd: packages/contracts # shell: anvil --base-fee 0 --block-time 2 - explorer: + explorer1: cwd: packages/contracts - shell: pnpm explorer --dev --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 + shell: pnpm explorer --dev --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db + explorer2: + cwd: packages/contracts + shell: pnpm explorer --dev --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db diff --git a/packages/explorer/src/bin/explorer.ts b/packages/explorer/src/bin/explorer.ts index 99a464528a..73cea95828 100755 --- a/packages/explorer/src/bin/explorer.ts +++ b/packages/explorer/src/bin/explorer.ts @@ -55,6 +55,12 @@ const argv = yargs(process.argv.slice(2)) type: "string", default: process.env.INDEXER_DATABASE || "indexer.db", }, + indexerPort: { + alias: "ip", + description: "Port number for the indexer", + type: "number", + default: process.env.INDEXER_PORT || 3001, + }, dev: { alias: "D", description: "Run in development mode", @@ -72,7 +78,7 @@ const argv = yargs(process.argv.slice(2)) }) .parseSync(); -const { port, hostname, chainId, chainName, rpcHttpUrl, rpcWsUrl, indexerDatabase, dev } = argv; +const { port, hostname, chainId, chainName, rpcHttpUrl, rpcWsUrl, indexerDatabase, indexerPort, dev } = argv; const indexerDatabasePath = path.join(packageRoot, indexerDatabase); let explorerProcess: ChildProcess; @@ -134,6 +140,7 @@ async function startStoreIndexer() { RPC_HTTP_URL: rpcHttpUrl, FOLLOW_BLOCK_TAG: "latest", SQLITE_FILENAME: indexerDatabase, + PORT: indexerPort.toString(), ...process.env, }, }); From 9c9323621697c29b2100384938f171c69c6b208f Mon Sep 17 00:00:00 2001 From: karooolis Date: Wed, 11 Dec 2024 20:37:56 +0200 Subject: [PATCH 17/25] run local explorer example build --- examples/local-explorer/mprocs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/local-explorer/mprocs.yaml b/examples/local-explorer/mprocs.yaml index 84d7bdabf9..03e2992b34 100644 --- a/examples/local-explorer/mprocs.yaml +++ b/examples/local-explorer/mprocs.yaml @@ -10,7 +10,7 @@ procs: # shell: anvil --base-fee 0 --block-time 2 explorer1: cwd: packages/contracts - shell: pnpm explorer --dev --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db + shell: pnpm explorer --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db explorer2: cwd: packages/contracts - shell: pnpm explorer --dev --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db + shell: pnpm explorer --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db From fd54693da842c87f565227d431b1b8668a506607 Mon Sep 17 00:00:00 2001 From: karooolis Date: Thu, 12 Dec 2024 16:20:59 +0200 Subject: [PATCH 18/25] visualize crosschain records --- examples/local-explorer/mprocs.yaml | 8 +- .../[worldAddress]/explore/TablesViewer.tsx | 62 +++++++++-- .../queries/useCrosschainRecordsQuery.ts | 104 ++++++++++++++++++ .../(explorer)/queries/useTableDataQuery.ts | 2 +- 4 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 packages/explorer/src/app/(explorer)/queries/useCrosschainRecordsQuery.ts diff --git a/examples/local-explorer/mprocs.yaml b/examples/local-explorer/mprocs.yaml index 03e2992b34..febda2d91d 100644 --- a/examples/local-explorer/mprocs.yaml +++ b/examples/local-explorer/mprocs.yaml @@ -8,9 +8,13 @@ procs: # anvil: # cwd: packages/contracts # shell: anvil --base-fee 0 --block-time 2 + # explorer: + # cwd: packages/contracts + # shell: pnpm explorer --dev + explorer1: cwd: packages/contracts - shell: pnpm explorer --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db + shell: pnpm explorer --dev --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db explorer2: cwd: packages/contracts - shell: pnpm explorer --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db + shell: pnpm explorer --dev --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index 5703311358..f2762d1de3 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -1,5 +1,6 @@ import { ArrowUpDownIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react"; import { parseAsJson, parseAsString, useQueryState } from "nuqs"; +import { Hex, encodeAbiParameters, keccak256, pad } from "viem"; import { useMemo } from "react"; import { Table as TableType } from "@latticexyz/config"; import { getKeySchema, getKeyTuple } from "@latticexyz/protocol-parser/internal"; @@ -18,6 +19,7 @@ import { Button } from "../../../../../../components/ui/Button"; import { Input } from "../../../../../../components/ui/Input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../../../../components/ui/Table"; import { cn } from "../../../../../../utils"; +import { useCrosschainRecordsQuery } from "../../../../queries/useCrosschainRecordsQuery"; import { TData, TDataRow, useTableDataQuery } from "../../../../queries/useTableDataQuery"; import { EditableTableCell } from "./EditableTableCell"; import { typeSortingFn } from "./utils/typeSortingFn"; @@ -27,6 +29,13 @@ const initialRows: TData["rows"] = []; export function TablesViewer({ table, query }: { table?: TableType; query?: string }) { const { data: tableData, isLoading: isTDataLoading, isFetched, isError, error } = useTableDataQuery({ table, query }); + const { + data: crosschainRecords, + isLoading: isCrosschainRecordsLoading, + isFetched: isCrosschainRecordsFetched, + isError: isCrosschainRecordsError, + error: crosschainRecordsError, + } = useCrosschainRecordsQuery(table); const isLoading = isTDataLoading || !isFetched; const [globalFilter, setGlobalFilter] = useQueryState("filter", parseAsString.withDefault("")); const [sorting, setSorting] = useQueryState("sort", parseAsJson().withDefault(initialSortingState)); @@ -138,13 +147,52 @@ export function TablesViewer({ table, query }: { table?: TableType; query?: stri {!isError && reactTable.getRowModel().rows?.length ? ( - reactTable.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} - ))} - - )) + reactTable.getRowModel().rows.map((row) => { + if (!table) return null; + + try { + if (crosschainRecords?.rows) { + const keySchema = getKeySchema(table); + const keys = Object.keys(keySchema); + const keySchemaValues = keys.map((key) => pad(row.original[key!] as Hex)); + const keyHash = keccak256(encodeAbiParameters([{ type: "bytes32[]" }], [keySchemaValues])); + const crosschainRecord = crosschainRecords.rows.find((record) => record.keyHash === keyHash); + + if (crosschainRecord && crosschainRecord.owned) { + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + + ); + } + } + } catch (error) { + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); + } + + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); + }) ) : ( ; +export type TData = { + columns: string[]; + rows: TDataRow[]; +}; + +export function useCrosschainRecordsQuery(viewedTable: Table | undefined) { + const { chainName, worldAddress } = useParams(); + const { id: chainId } = useChain(); + + const tableName = "crosschain__crosschain_record"; + const decodedQuery = `SELECT * FROM "${worldAddress}__${tableName}"`; + const table = { + tableId: "0x746263726f7373636861696e0000000043726f7373636861696e5265636f7264", + name: "CrosschainRecord", + namespace: "crosschain", + label: "CrosschainRecord", + namespaceLabel: "crosschain", + type: "table", + schema: { + tableId: { type: "bytes32", internalType: "bytes32" }, + keyHash: { type: "bytes32", internalType: "bytes32" }, + blockNumber: { type: "uint256", internalType: "uint256" }, + timestamp: { type: "uint256", internalType: "uint256" }, + owned: { type: "bool", internalType: "bool" }, + }, + key: ["tableId", "keyHash"], + } as const; + + return useQuery({ + queryKey: ["crosschainRecords", chainName, worldAddress, decodedQuery], + queryFn: async () => { + const indexer = indexerForChainId(chainId); + const response = await fetch(indexer.url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify([ + { + address: worldAddress as Hex, + query: decodedQuery, + }, + ]), + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.msg || "Network response was not ok"); + } + + return data; + }, + select: (data: DozerResponse): TData | undefined => { + if (!data?.result?.[0]) return undefined; + + const indexer = indexerForChainId(chainId); + const result = data.result[0]; + // if columns are undefined, the result is empty + if (!result[0]) return undefined; + + const schema = Object.keys(table.schema); + const columns = result[0] + ?.map((columnKey) => { + const schemaKey = schema.find((schemaKey) => schemaKey.toLowerCase() === columnKey); + return schemaKey || columnKey; + }) + .filter((key) => schema.includes(key)); + + const rows = result + .slice(1) + .map((row) => + Object.fromEntries( + columns.map((key, index) => { + const value = row[index]; + const type = table.schema[key as keyof typeof table.schema]; + if (type?.type === "bool") { + return [key, indexer.type === "sqlite" ? value === "1" : value]; + } + return [key, value]; + }), + ), + ) + .filter((row) => row.tableId === viewedTable?.tableId); + + return { + columns, + rows, + }; + }, + refetchInterval: (query) => { + if (query.state.error) return false; + return 500; + }, + }); +} diff --git a/packages/explorer/src/app/(explorer)/queries/useTableDataQuery.ts b/packages/explorer/src/app/(explorer)/queries/useTableDataQuery.ts index 6756d0ef5f..867aad76fd 100644 --- a/packages/explorer/src/app/(explorer)/queries/useTableDataQuery.ts +++ b/packages/explorer/src/app/(explorer)/queries/useTableDataQuery.ts @@ -82,7 +82,7 @@ export function useTableDataQuery({ table, query }: Props) { enabled: !!table && !!query, refetchInterval: (query) => { if (query.state.error) return false; - return 1000; + return 500; }, }); } From d6fe4d3bb6ceb21a97350eda3f1f4d91a337af54 Mon Sep 17 00:00:00 2001 From: karooolis Date: Thu, 12 Dec 2024 16:32:43 +0200 Subject: [PATCH 19/25] blur non-owned records --- .../[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index f2762d1de3..77ac91a228 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -158,7 +158,7 @@ export function TablesViewer({ table, query }: { table?: TableType; query?: stri const keyHash = keccak256(encodeAbiParameters([{ type: "bytes32[]" }], [keySchemaValues])); const crosschainRecord = crosschainRecords.rows.find((record) => record.keyHash === keyHash); - if (crosschainRecord && crosschainRecord.owned) { + if (crosschainRecord && !crosschainRecord.owned) { return ( {row.getVisibleCells().map((cell) => ( From 373b5de786f52d6a7974f708a639168f7b387a12 Mon Sep 17 00:00:00 2001 From: karooolis Date: Thu, 12 Dec 2024 16:45:32 +0200 Subject: [PATCH 20/25] remove unused vars --- examples/local-explorer/mprocs.yaml | 4 ++-- .../worlds/[worldAddress]/explore/TablesViewer.tsx | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/local-explorer/mprocs.yaml b/examples/local-explorer/mprocs.yaml index febda2d91d..7c3ea322b9 100644 --- a/examples/local-explorer/mprocs.yaml +++ b/examples/local-explorer/mprocs.yaml @@ -14,7 +14,7 @@ procs: explorer1: cwd: packages/contracts - shell: pnpm explorer --dev --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db + shell: pnpm explorer --chainId 901 --chainName customA --rpc http://127.0.0.1:9545 --port 19545 --indexerPort 13001 --indexerDatabase indexer19545.db explorer2: cwd: packages/contracts - shell: pnpm explorer --dev --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db + shell: pnpm explorer --chainId 902 --chainName customB --rpc http://127.0.0.1:9546 --port 19546 --indexerPort 13002 --indexerDatabase indexer19546.db diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index 77ac91a228..87ac8220ba 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -29,13 +29,7 @@ const initialRows: TData["rows"] = []; export function TablesViewer({ table, query }: { table?: TableType; query?: string }) { const { data: tableData, isLoading: isTDataLoading, isFetched, isError, error } = useTableDataQuery({ table, query }); - const { - data: crosschainRecords, - isLoading: isCrosschainRecordsLoading, - isFetched: isCrosschainRecordsFetched, - isError: isCrosschainRecordsError, - error: crosschainRecordsError, - } = useCrosschainRecordsQuery(table); + const { data: crosschainRecords } = useCrosschainRecordsQuery(table); const isLoading = isTDataLoading || !isFetched; const [globalFilter, setGlobalFilter] = useQueryState("filter", parseAsString.withDefault("")); const [sorting, setSorting] = useQueryState("sort", parseAsJson().withDefault(initialSortingState)); From fc50d1c1ec9e0181eb8ac9a928267f261a76abe6 Mon Sep 17 00:00:00 2001 From: karooolis Date: Thu, 12 Dec 2024 22:24:05 +0200 Subject: [PATCH 21/25] add bridged overlay --- .../worlds/[worldAddress]/explore/TablesViewer.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx index 87ac8220ba..7502f2d16d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx @@ -1,4 +1,4 @@ -import { ArrowUpDownIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react"; +import { ArrowUpDownIcon, LoaderIcon, LockIcon, TriangleAlertIcon } from "lucide-react"; import { parseAsJson, parseAsString, useQueryState } from "nuqs"; import { Hex, encodeAbiParameters, keccak256, pad } from "viem"; import { useMemo } from "react"; @@ -160,7 +160,11 @@ export function TablesViewer({ table, query }: { table?: TableType; query?: stri {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} - + +
+ Bridged +
+
); } From 01eb9d91dadf07d717404291ab2faa6b642e131b Mon Sep 17 00:00:00 2001 From: karooolis Date: Fri, 13 Dec 2024 14:56:17 +0200 Subject: [PATCH 22/25] remove default ws setting --- packages/explorer/src/bin/explorer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/bin/explorer.ts b/packages/explorer/src/bin/explorer.ts index 73cea95828..7073ebeac4 100755 --- a/packages/explorer/src/bin/explorer.ts +++ b/packages/explorer/src/bin/explorer.ts @@ -47,7 +47,7 @@ const argv = yargs(process.argv.slice(2)) alias: ["rpcWs", "rpcWsUrl"], description: "RPC WebSocket URL", type: "string", - default: process.env.RPC_WS_URL || "ws://127.0.0.1:8545", + default: process.env.RPC_WS_URL, }, indexerDatabase: { alias: "i", From 4a88f0e5ad45561dbb11a6777819e416efb9cea6 Mon Sep 17 00:00:00 2001 From: karooolis Date: Fri, 13 Dec 2024 15:49:00 +0200 Subject: [PATCH 23/25] disable world abi query --- .../src/app/(explorer)/queries/useWorldAbiQuery.ts | 1 + packages/explorer/src/components/Navigation.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts b/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts index 96a2daace9..a54e0775c2 100644 --- a/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts +++ b/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts @@ -29,5 +29,6 @@ export function useWorldAbiQuery(): UseQueryResult { }; }, refetchInterval: 5000, + enabled: false, }); } diff --git a/packages/explorer/src/components/Navigation.tsx b/packages/explorer/src/components/Navigation.tsx index 95921087fe..359b4ca04a 100644 --- a/packages/explorer/src/components/Navigation.tsx +++ b/packages/explorer/src/components/Navigation.tsx @@ -1,10 +1,10 @@ "use client"; -import { Loader } from "lucide-react"; +// import { Loader } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useWorldUrl } from "../app/(explorer)/hooks/useWorldUrl"; -import { useWorldAbiQuery } from "../app/(explorer)/queries/useWorldAbiQuery"; +// import { useWorldAbiQuery } from "../app/(explorer)/queries/useWorldAbiQuery"; import { LatestBlock } from "../components/LatestBlock"; import { Separator } from "../components/ui/Separator"; import { cn } from "../utils"; @@ -28,7 +28,7 @@ function NavigationLink({ href, children }: { href: string; children: React.Reac } export function Navigation() { - const { data, isFetched } = useWorldAbiQuery(); + // const { data, isFetched } = useWorldAbiQuery(); return (
@@ -38,11 +38,11 @@ export function Navigation() { Observe
- {isFetched && !data?.isWorldDeployed && ( + {/* {isFetched && !data?.isWorldDeployed && (

Waiting for world deploy

- )} + )} */}
From c132111ec10d01806069ea16e5b4b990668f77d5 Mon Sep 17 00:00:00 2001 From: karooolis Date: Fri, 13 Dec 2024 16:54:47 +0200 Subject: [PATCH 24/25] increase polling interval --- .../(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx index 43357d18a9..d0827c33af 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/Providers.tsx @@ -33,7 +33,7 @@ export function Providers({ children }: { children: ReactNode }) { }, ssr: true, pollingInterval: { - [chain.id]: chain.id === 31337 ? 100 : 500, + [chain.id]: 5000000, }, }); }, [chain]); From 9d9facbe075c2884b27b533bce8779441308f1dd Mon Sep 17 00:00:00 2001 From: karooolis Date: Fri, 13 Dec 2024 16:58:37 +0200 Subject: [PATCH 25/25] increase polling interval --- .../explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts | 2 +- packages/explorer/src/components/LatestBlock.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts b/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts index a54e0775c2..2177e7810f 100644 --- a/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts +++ b/packages/explorer/src/app/(explorer)/queries/useWorldAbiQuery.ts @@ -28,7 +28,7 @@ export function useWorldAbiQuery(): UseQueryResult { isWorldDeployed: data.isWorldDeployed, }; }, - refetchInterval: 5000, + refetchInterval: 5000000, enabled: false, }); } diff --git a/packages/explorer/src/components/LatestBlock.tsx b/packages/explorer/src/components/LatestBlock.tsx index b493022b82..8ddcdfe896 100644 --- a/packages/explorer/src/components/LatestBlock.tsx +++ b/packages/explorer/src/components/LatestBlock.tsx @@ -8,7 +8,8 @@ export function LatestBlock() { watch: true, chainId, query: { - refetchInterval: 1000, + refetchInterval: 10000000, + enabled: false, }, });